begin
// цвет можно указывать в виде констант цветов (например clRed), в виде HTML кода (например $FFFF00), в виде RGB (например RGB(0,0,255));
case Field1.ValueAsInt of // если значение переменной поля равно:
1..3: Field1.Color := clRed; // от 1 до 3 - изменить цвет поля на красный;
4 : Field1.BorderColor := clGreen; // 4 - изменить цвет рамки поля на зеленый;
5 : Field1.FontColor := $FFFF00; // 5 - изменить цвет шрифта поля на желтый;
6 : Field1.Color := RGB(0,0,255); // 6 - изменить цвет поля на синий;
end;
end.
begin
// Sender - это тот объект который вызвал скрипт
if Sender is TM_Object then // если Sender это компонент то(например - уровень, поле и т.д.)
case TM_Object(Sender).ValueAsInt of // если значение переменной связанной с объектом равно:
1..5: Sender.Color := clDarkBlue; // от 1 до 5, то изменить цвет на темно синий
6, 7: Sender.Color := clGray; // 6 или 7 - изменить цвет на серый
8 : Sender.Color := clYellow; // 7 - изменить цвет на желтый
end;
end.
через событие видимость(Visible) нет.Проверяем.
Может глупый вопрос, но все же...А как помигать одним цветом?В текущей версии мигания нет. Но мы добавим всем объектам новое свойство для мигания.
а вот через событие видимость(Visible) нет.Проверили, все работает вот так (изображение скрывается при изменении переменной, которая с ним связана):
begin
Image1.Visible := False;
end.
В текущей версии мигания нет. Но мы добавим всем объектам новое свойство для мигания.
Procedure Field1_OnDoneInput(Sender: TM_Control);
begin
Time_Vibro.Value := Field1.ValueAsInt * 1000;
end.
Procedure Field2_OnDataChange(Sender: TM_Control);
begin
Field1.Value := Time_Vibro / 1000
end.
Procedure Field43_OnDoneInput(Sender: TM_Control);
var
Vvod_s: Integer;
Time_ms: Integer;
Period_ms: Integer;
begin
Vvod_s := Field43.ValueAsInt;
Time_ms := D450.ValueAsInt;
Period_ms := D451.ValueAsInt;
if Vvod_s < Period_ms/1000 then
D450.Value := Vvod_s * 1000
else
D450.Value := Period_ms;
end.
- добавляем новую переменную с OPC-сервера которая хранит значение времени в миллисекундах;Красиво. Но мне все равно нужна была проверка.
- ставим этой переменной смещение запятой = -3;
- ставим формат переменной = 0;
- ставим шкалу для этой переменной 0-20000;
Procedure Field28_OnDoneInput(Sender: TM_Control);
begin
if field28.Value = 1 then D443.Value := 0 else D443.Value := 1;
end.
begin
{ отключаем мигание установив цвет мигания «без цвета» - таким образом не нужно будет
отдельно отключать мигание отдельно для каждого условия ни-же. }
Image1.FlashColor := clNone;
case Image1.ValueAsInt of // если значение переменной равно:
1: Image1.Color := clRed; // 1 - изменить цвет на красный.
2:
begin
Image1.Color := clNone; // 2 – изменить цвет на «без цвета» - для того, чтобы мигание было только зеленым цветом, а не с красного на зеленый.
Image1.FlashColor := clGreen; // и включаем мигание зеленым цветом.
end;
3: Image1.Color := clGreen; // 3 - меняем цвет на зеленый.
end;
end.
begin
if FileOpen('Hello.sbm') then // открыть если существует или создать файл Hello.sbm (расширение вы можете использовать любое)
begin
FileWriteString(Field1.ValueAsStr); // записать в файл строку из поля
FileWriteInteger(Field2.ValueAsInt); // записать в файл целое число из поля
FileWriteSingle (Field3.ValueAsFloat); // записать в файл вещественное число из поля
FileWriteString (Text1.Text); // записать в файл содержимое текста
FileWriteDateTime(Now); // записать в файл текущую дату и время
// закрывать файл не обязательно, Simple-Scada сделает это автоматически.
end;
end.
begin
// нужно считывать все данные в той последовательности, в которой мы их записывали
if FileOpen('Hello.sbm') then // открыть файл если существует
begin
Field1.Value := FileReadString; // считать в переменную поля строку
Field2.Value := FileReadInteger; // считать в переменную поля целое число
Field3.Value := FileReadSingle; // считать в переменную поля вещественное число
Text2.Text := FileReadString; // считать в текст строку
Text3.Text := DateTimeToStr(FileReadDateTime);// считать в текст время и дату
end;
end.
procedure TForm1.Button1Click(Sender: TObject);
var
Button : array [1..20] of TButton;
begin
for i:=1 to 20 do
begin
Button[i]:=TButton.Create(Form1);
Button[i].Parent:=Form1;
Button[i].Left:=80;
Button[i].Top:=80;
Button[i].Name:='Buttons'+IntToStr(i);
end;
end;
var
i:integer;
begin
for i:=1 to 20 do
(FindComponent('Edit'+IntToStr(i)) as TEdit).Text:='0';
end;
Не получается работа с файлом из окна. Файл создается, но записи и чтения не происходит.Спасибо, сейчас займемся этим вопросом.
Возможно ли в скриптах динамически создавать объекты, что-то типа:Нет, это запрещено по многим причинам (из соображений быстродействия, безопасности в коде, работы по сети и др.). Если вы хотите, чтобы 20 кнопок появились после выполнения скрипта, то их обязательно нужно предварительно создать в редакторе и, к примеру, сделать скрытыми выключив свойство "Видимость". А скриптом придется просто включать видимость, но для каждой кнопки это придется делать отдельно:
begin
Button1.Visible := True;
Button2.Visible := True;
...
Button20.Visible := True;
end;
В версии 1 можно было настроить кнопку по которой осуществляется выход. А во 2 я такой опции не нашел.Да, мы забыли её включить. Сейчас по Shift+ESCAPE осуществляется выход.
Строчные данные имеют разную длину, поэтому не одинаковую длину будет иметь и группа параметров. Как организовать поиск и считывание из файла нужных мне параметров?Допустим Вы записываете в файл следующим образом:
begin
if FileOpen('MyFile.sfl') then
begin
FileWriteInteger(10); // первое целое число
FileWriteString('Привет, мир!'); // строка
FileWriteInteger(20); // второе целое число
// ... и т.д.
end;
end;
var
I: Integer;
S: UTF8String;
begin
if FileOpen('MyFile.sfl') then
begin
I := FileReadInteger; // первое целое число
S := FileReadString; // строка
I := FileReadInteger; // второе целое число
// ... и т.д.
end;
end;
можно просто хранить рецепты в разных файлах и всегда читать нужный рецепт, без лишних данных.
Если хранить их нужно строго в одном файле, то можно предложить ещё одно решение. Перед сохранением рецепта в файл, можно сохранять в него и длину рецепта (т.е. суммарную длину всех параметров рецепта), как целочисленное значение.Эти варианты я рассматривал. В первом варианте нужно создать может быть полсотню файлов и не понятно как искать
type
TRecipe = record //Создаем структуру типа запись
number:integer;
name :string[16];
weight:integer;
end;
var
Recipe: TRecipe; //переменная записи
RecipeF: file of TRecipe; //файловая переменная
begin
Recipe.number:= Field1.Value;//присвоение значений
Recipe.name:= Field2.Value;
Recipe.weight:= Field3.Value;
write(RecipeF,Recipe); //записать данные в файл. Каждая запись имеет строго определенную длину.
end. //Как то так. Вы и без меня знаете.
begin
case Text1.Value of
1:Text1.Text := 'Местн.Пуск';
2:Text1.Text := 'Вынос.Пульт';
3:Text1.Text := 'Автозапуск';
4:Text1.Text := 'Диспетчер';
0:Text1.Text := 'Работает';
end;
end.
begin
if Sender is TM_Text then
case TM_Text(Sender).ValueAsInt of
1:(Sender as TM_Text).Text := 'Местн.Пуск';
2:(Sender as TM_Text).Text := 'Вынос.Пульт';
3:(Sender as TM_Text).Text := 'Автозапуск';
4:(Sender as TM_Text).Text := 'Диспетчер';
0:(Sender as TM_Text).Text := 'Работает';
end;
end.
begin
if Sender is TM_Text then
with Sender as TM_Text do
case ValueAsInt of
1:Text := 'Местн.Пуск';
2:Text := 'Вынос.Пульт';
3:Text := 'Автозапуск';
4:Text := 'Диспетчер';
0:Text := 'Работает';
end;
end.
Клиент и Редактор прекрасно работает под Wine в Linux (Ubuntu 14.04)Спасибо за информацию! Такого теста мы еще не проводили.
begin
if Field2.Value = 0 then
// Кнопка не нажата и зафиксирована, значение bVal_2 = 0
Button1.States[0].Caption := 'Надпись 1'
else
// Кнопка не нажата и зафиксирована, значение bVal_2 = 1
Button1.States[0].Caption := 'Надпись 2';
if Field2.Value = 0 then
// Кнопка нажата и зафиксирована, значение bVal_2 = 0
Button1.States[1].Caption := 'Надпись 3'
else
// Кнопка нажата и зафиксирована, значение bVal_2 = 1
Button1.States[1].Caption := 'Надпись 4';
end.
1. Создаете поле, у меня это Field2.
2. Прикрепляете к этому полю переменную, от состояния которой у вас будет меняться текст на кнопке. У меня это вирт. переменная со шка............
procedure Narabotka_OnDataChange(Sender:Tm_Control);
begin
if kot_v_rab.ValueAsInt = 1 then
Time.Value := IncSecond(Now, -Interval.ValueAsInt);
if kot_v_rab.ValueAsInt = 0 then
Interval.Value := SecondsBetween(Now, Time.Value);
end.
procedure OnSecondTimer;
begin
if kot_v_rab.ValueAsInt = 1 then
Narabotka.Text := IntToStr(DaysBetween(Now, Time.Value)) + ' дней '+ TimeToStr(Now-Time.Value);
end.
procedure OnExit;
begin
//Сохраняю интервал
If FileOpen ('Init.msf') then
FileWriteInt64 (Interval.ValueAsInt)
end.
procedure OnInitialization;
{ Скрипт исполняющийся при запуске программы}
begin
// Загружаю интервал
If FileOpen ('Init.msf') then
Interval .Value := FileReadInt64;
// Смещаю текущее время на интервал времени наработки в секундах в переменной Time
Time.Value := IncSecond (Now, -Interval.ValueAsInt) ;
// Использую смещенное значение для вычисления времени наработки и отображаю время наработки
Text1.Text := IntToStr(DaysBetween(Now, Time.Value )) + ' дней ' + TimeToStr (Now-Time.Value);
end.
begin
bool1.Value := GetBit(dword.Value,0); // Забираем нужный bit из "dword" в виртуальную переменную "bool1".
case bool1.ValueAsInt of // Дальше работаем с виртуальной переменной "bool1".
0:Button1.States[0].Color := RGB(60,70,80);
1:Button1.States[0].Color := RGB(0,95,88);
end;
case bool1.ValueAsInt of
0:Button1.States[0].BorderColor := RGB(60,70,80);
1:Button1.States[0].BorderColor := RGB(0,95,88);
end;
bool2.Value := GetBit(dword.Value,1);
case bool2.ValueAsInt of
0:Button2.States[0].Color := RGB(60,70,80);
1:Button2.States[0].Color := RGB(0,95,88);
end;
case bool2.ValueAsInt of
0:Button2.States[0].BorderColor := RGB(60,70,80);
1:Button2.States[0].BorderColor := RGB(0,95,88);
end;
// и тд. и тп.
end.
begin
// если нулевой бит равен 1
if GetBit(dword.Value, 0) then
begin
Button1.States[0].Color := RGB(0,95,88);
Button1.States[0].BorderColor := RGB(0,95,88);
end else
// если нулевой бит равен 0
begin
Button1.States[0].Color := RGB(60,70,80);
Button1.States[0].BorderColor := RGB(60,70,80);
end;
// если первый бит равен 1
if GetBit(dword.Value, 1) then
begin
Button1.States[0].Color := RGB(0,95,88);
Button1.States[0].BorderColor := RGB(0,95,88);
end else
// если первый бит равен 0
begin
Button1.States[0].Color := RGB(60,70,80);
Button1.States[0].BorderColor := RGB(60,70,80);
end;
end.
Может быть мы бы могли посоветовать решение ещё лучше, но нужно подробнее понять задачу. Опишите подробно, как должна работать кнопка и как она должна менять свои состояния? Вы точно хотите менять состояние кнопки в зависимости от разных битов? Т.е. сначала в коде вы проверяете нулевой бит, потом проверяете первый бит. Так и должно быть?Сама кнопка(без фиксации) в SCADA работает c bool переменной PLC.(т.е. включает АВТО режим узла/агрегата с помощью переменной btnAUTO, ручной режим работы с помощью btnMANUAL) а вот состояние этого узла/агрегата(что он действительно переключился в тот или иной режим работы) приходит из PLC другой переменной типа DWORD. У каждого состояня того или иного узла/агригата свой bit той самой DWORD.
от куда берется состояние бита , ведь в условии только только бит с которым нужно работатьЕсли подробно, то GetBit - это функция, которая берет нужный бит из целого числа и возвращает его значение как тип Boolean. Т.е. если этот бит равен 1, то она вернёт True, если этот бит равен 0 то она вернёт False. Это позволяет писать так:
if GetBit(dword.Value, 0) then
...
else
...
if GetBit(dword.Value, 0) = True then
...
else
...
if not GetBit(dword.Value, 0) then
...
else
...
if GetBit(dword.Value, 0) = False then
...
else
...
Нет, в данном случае Вы по сути хотите вставить в окно скады другое приложение. Это не получится из-за особенностей отрисовки скады. Добавить такую возможность - можно, но это может привести к появлению недостатков связанных со скоростью и стабильностью работы и добавит в скаду несколько уязвимостей.Если вы сможете добавить какую возможность то это будет ВАУ ::)
begin
// Field1 - поле с которым связанна переменная температуры.
// Ниже приведены варианты звуковых сообщений - выберите какое требуется Вам.
case Field1.ValueAsInt of
20..30: PlayMessageSoundClient(GetClientName); // проиграть звук сообщения на текущем клиенте, если значение в диапазоне от 20 до 30.
31..40: PlayWarningSoundClient(GetClientName); // проиграть звук предупреждения на текущем клиенте, если значение в диапазоне от 31 до 40.
41..50: PlayAlarmSoundClient(GetClientName); // проиграть звук аварии на текущем клиенте, если значение в диапазоне от 41 до 50.
end;
end.
begin
StopSoundClient(GetClientName); // остановить проигрывание звука на текущем клиенте.
end.
Можно ли написать скрипт так, чтоб при изменении переменной поля, если она меньше определенного значения, то ее значение приравнивалось к нулю и выводилось в поле?Попробуйте использовать скрипт по окончанию ввода для этого поля.
begin
if Field1.Value < -10 then
Field1.Value := 0;
end.
Подскажите как вывести текущую время и дату, попробовал из примера руководства но не компилится.Создал объект Text1. В меню Проект-Скрипты использую скрипт OnSecondTimer
begin
Text1.Text := DateTimeToStr(Now);
end.
А так же хотелось бы узнать можно как то реализовать, что бы при нажатии на кнопку не закрывая сеанса(тоесть вся система активна) блокировался экран до введения заданного логина и пароля.А вот это надо в предложения внести. Сейчас если вы вошли в учетную запись, вы из нее никак не выйдете. Только сможете перейти в другую учетку или закрыть клиент.
Подскажите как вывести текущую время и дату, попробовал из примера руководства но не компилится.Как и написал TeNQ.
Можно ли написать скрипт так, чтоб при изменении переменной поля, если она меньше определенного значения, то ее значение приравнивалось к нулю и выводилось в поле?Подобные проверки безусловно должны быть сделаны на контроллере. Но если такой возможности нет и нужно сделать именно в скаде, то:
А вот это надо в предложения внести. Сейчас если вы вошли в учетную запись, вы из нее никак не выйдете. Только сможете перейти в другую учетку или закрыть клиент.Добавим в скрипты функции авторизации и закрытия сессии в ближайшем или следующем после него обновлении.
begin
IF (S21.Value =true) then S21_shape.Color := clgreen
else S21_shape.Color := clgray;
end
if Sender is TM_Image then // проверяем, что Sender это картинка:
with Sender as TM_Image do // приводим объект к типу TM_Image:
case ValueAsInt of // если значение переменной равно:
1 : Frame := 1; // 1, то показать кадр 1
0 : Frame := 0; // 0, то показать кадр 0
end;
if Sender is TM_Image then // проверяем, что Sender это картинка:
with Sender as TM_Image do // приводим объект к типу TM_Image:
case ValueAsInt of // если значение переменной равно:
1 :
begin
Color := clGreen;
Frame := 1;
end;
0 :
begin
Color := clOrange;
Frame := 0;
end;
end;
Color := RGB(255, 0, 0); // Красный
Color := RGB(0, 255, 0); // Зеленый
Color := RGB(0, 0, 255); // Синий
и т.д.
begin
if Sender is TM_Valve then // проверяем, что Sender это заслонка:
with Sender as TM_Valve do // приводим объект к типу TM_Valve:
case ValueAsInt of // если значение переменной равно:
-5..5 : // = -5 до 5 то считаем что заслонка закрыта
begin
Color := RGB(255, 0, 0); // изменить цвет на красный
end;
95..105 : // = 95 до 105 то считаем что заслонка открыта
begin
Color := RGB(0, 255, 0); // изменяем цвет на зеленый
end;
end;
end.
begin
if Field2.ValueAsInt > 0 then
Button3.Layer := 5
else
Button3.Layer := 2;
end.
Здравствуйте есть скрипт задача которого в зависимости от значения в поле менять слой кнопки, но что-то у меня не получилось, срабатывает когда ему вздумается и совсем не так как мне нужно (Попробовал, у меня так же не работает. Я создал три текстовых объекта и выводил туда значение слоя объекта в секундном скрипте.
Text1.Text := 'Слой кнопки ' + IntToStr(Button1.Layer);
Text2.Text := 'Слой изображения ' + IntToStr(Image1.Layer);
Text3.Text := 'Слой поля ' + IntToStr(Field1.Layer);
Еще вопрос по скрипту. Есть переменная с диапазоном от 0 до 100 (положение заслонки). Я выбрал объект заслонка, присвоил ей переменную, написал скрипт, что от -5 до 5 заслонка считается закрытой и меняет цвет заслонки на оранжевый, например, и от 95-105 считается открытой и меняет цвет на зелёный. скрипт выполняется только на одну секунду а потом цвет встает по умолчанию серый, как на открытии так и на закрытии.Объект заслонка работает в трех режимах (SS2 руководство пользователя - стр.62) и они дискретные. Ваш скрипт работал бы с объектом Image, но не будет работать с объектом valve. У вас переменная привязана к объекту заслонка - режим работы Простой. Когда переменная привязанная к заслонке равна 0, то она перекрашивается в серый цвет (закрыто). 1 - зеленый (открыто). 2 и 3 соответственно аварийные состояния - красный цвет.
Большое спасибо, я так уже и делаю. У меня были насчет этого мысли такие и Вы это подтвердилиДа не за что. Сервер скады подвис и клиент никак не подсоединялся. Видимо игра со слоями не прошла даром)) Пришлось перезагрузить систему, иначе никак не получалось запустить. Я изменил ваш скрипт и проверил - работает.
begin
case Test.ValueAsInt of // если значение переменной равно:
-5..5 : // = -5 до 5 то считаем что заслонка закрыта
begin
Valve_State.Value := 2; // изменить цвет на красный
end;
6..94 : // = 6 до 94 то считаем что заслонка в промежуточном состоянии
begin
Valve_State.Value := 0; // изменить цвет на серый
end;
95..105 : // = 95 до 105 то считаем что заслонка открыта
begin
Valve_State.Value := 1; // изменяем цвет на зеленый
end;
end;
end.
Сигнатура проблемы:
Имя события проблемы: APPCRASH
Имя приложения: Editor.exe
Версия приложения: 2.0.0.0
Отметка времени приложения: 573aa805
Имя модуля с ошибкой: KERNELBASE.dll
Версия модуля с ошибкой: 6.1.7601.18229
Отметка времени модуля с ошибкой: 51fb1116
Код исключения: 0eedfade
Смещение исключения: 0000c41f
Версия ОС: 6.1.7601.2.1.0.256.1
Код языка: 1049
Дополнительные сведения 1: f26e
Дополнительные сведения 2: f26e409a6454e93683a6b9f907943f95
Дополнительные сведения 3: 8c82
Дополнительные сведения 4: 8c82d1f2107cefd2c06d5af805433944
и еще в редакторе и в клиенте выскакивает ошибка и система закрывается когда заходишь в меню О программеСейчас проверим.
var
aBegin, aEnd: TDateTime;
begin
aEnd := Now; // конец интервала – текущая дата;
aBegin := IncSecond(aEnd, -10); // начало интервала – текущая дата – 10 секунд;
Try2.ToExcel ('', aBegin, aEnd, evtAll, False);
end.
В базе записей меньше, явно не с секундным интервалом, а по изменению - видимо это оптимизация.Да, это из-за оптимизации. Помимо неё, есть ещё одна оптимизация из-за которой Вы можете получать пустой файл экспорта. Дело в том, что вершины трендов не добавляются в БД сразу, т.к. это может привести к плохим последствиям, если таких трендов будет много. Поэтому вершины сначала накапливаются в специальном буфере и только затем загружаются в БД одним большим SQL-запросом. Поэтому при формировании отчета за последние 10 секунд в БД с большой вероятностью этих данных не окажется. Но здесь есть, что улучшить. Например, мы можем сделать так, чтобы в результат выборки из БД добавлялись данные из буферов... и тогда экспорт всегда будет правильным (не касается экспорта по часам/дням/месяцам).
Поэтому вершины сначала накапливаются в специальном буфере и только затем загружаются в БД одним большим SQL-запросом.А вот тогда другой вопрос, возникла мысль использовать БД для хранения массива значений. Выбрал фунцию записи в тренд при каждом изменении значения. Так вот сколько я не менял значение переменной - в БД, судя по WorkBench ни одно изменение не записалось. А второй тренд пишущийся с интервалом в 1 сек туда заносится по изменению +когда нет изменений значения пишутся каждые 30 сек.
Хотел организовать настройки цветовой темы проекта для каждого клиента, пробую записывать в файл, а потом во время инициализации читать, но как то не получается, может кто поделится идеейНу тут наверное надо разделить задачи - сохранение/чтение в файл и задание цветовой темы для конкретного клиента.
Читаю имя клиента функцией GetClientName. А она ничего не возвращает.Видимо потому что она вызывается в скрипте OnDataChange? Если так, то все верно, ведь GetClientName возвращает имя клиента, который вызвал скрипт. А клиенты не имеют отношения к OnDataChange событиям, т.к. они вызываются сервером. Поэтому имя клиента в этом событии получить не удастся. В этом плане событие OnDataChange уникально и отличается от всех других, которые вызываются клиентами (OnClick, OnDblClick и т.д.). Сейчас опишем это в справке.
Видимо потому что она вызывается в скрипте OnDataChange?В скриптах OnInitialization.
if GetClientName = 'Test' then
SetColorSchemeClient ('Test', csBrown);
begin
Text1.Text := 'Имя клиента = ' + GetClientName ;
SetColorSchemeClient (GetClientName ,csBrown);
end.
Имя не вывелось и схема не поменялась.Как Вы успели заметить SetColorSchemeClient не выполнилась, как и не выполнятся многие другие методы, т.к. этот скрипт будет вызван сервером. Как раз вчера мы сделали несколько изменений и теперь в серверных скриптах все методы будут работать, кроме тех, которые работают с данными клиента (на сегодняшний день это только одна функция GetClientName). А мы надеялись, что никто не заметит этого до очередного обновления.
Например есть три переменных типа Boolean: opened, closed, average есть изображение с тремя кадрами: 0 кадр - открыто, 1 кадр - закрыто, 2 кадр - промежуточное.Я бы так сделал в скрипте OnSecondTimer
задача при изменении состояния переменных показывался кадр изображения соответствующей этой переменной
// Открыта
If Open.Value then Image1.Frame := 0;
// Закрыта
if Close.Value then Image1.Frame := 1;
// Среднее положение
if Average.Value then Image1.Frame := 2;
// Авария
if Open.Value and Close.Value then
Image1.Frame := 4;
if (Open.Value = false) and (Close.Value = false) then Image1.Frame := 2;
if not Open.Value and not Close.Value then Image1.Frame := 2;
А так работает, как по мне неправильно - даже если переменные принимают значение True, условие все равно срабатывает.Здесь дело в том, что свойство переменной "Value" имеет тип данных Variant (изменяющийся/непостоянный тип) и его нужно явно приводить к Boolean, вот так:
if not Boolean(Open.Value) and not Boolean(Close.Value) then Image1.Frame := 2;
Вопрос о свойстве кнопке ActiveState. В редакторе состояний кнопки есть свойство Номер - порядковый номер состояния. Но порядковый номер состояния не соответствует таковому состоянию ButtonX.ActiveState...Исправили в обновлении 2.0.0.19 (http://simple-scada.com/forum/index.php?topic=178.msg1892#msg1892).
Тогда след. вопрос: что я делаю не так? ПОстоянно одна за другой ошибки сыпятся,
var
min: single;
sec: single;
begin
if Sender is TM_Text then
min:=int(TM_Text.ValueAsFloat /60);
sec:=int((frac(TM_Text.ValueAsFloat/60))*60);
TM_Text.Text:=FloatToStr(min)+'мин '+FloatToStr(sec)+'сек';
end.[/tr]
В данный момент -[i] Property "Value As Float" is inaccessible here.[/i]
При том не важно - в каком виде я переменную вбиваю, всеравно она неприемлима.[/td]
[/tr]
var
Min, Sec: integer;
begin
if Sender is TM_Text then
with Sender as TM_Text do
begin
Min := ValueAsInt div 60;
if Min > 0 then
Sec := ValueAsInt - Min * 60
else
Sec := ValueAsInt;
Text := IntToStr(Min) + ' мин. ' + IntToStr(Sec) + ' сек.';
end;
var
Min, Sec: integer;
begin
Min := MyVar.ValueAsInt div 60;
if Min > 0 then
Sec := MyVar.ValueAsInt - Min * 60
else
Sec := MyVar.ValueAsInt;
Text1.Text := IntToStr(Min) + ' мин. ' + IntToStr(Sec) + ' сек.';
да и вообще, как работать со временными переменными, и как их обрабатывать в клиенте, для последующего экспорта к примеру в ExcelДля этого у Вас должна быть переменная типа DateTime на устройстве, на OPC-сервере и соответственно в скаде. Работать с ними нужно используя эти процедуры и функции (http://simple-scada.com/scripts_manual?section=script_time_date). С их помощью Вы можете переводить дату/время в строку и наоборот, а также выполнять множество других задач. При переводе в даты/времени в строку будет использоваться стандартный формат "29.06.2016 17:35:16". Чтобы его изменить можно использовать команды SetDateFormat и SetTimeFormat, например:
SetDateFormat('yyyy.mm.dd'); // соответствует "2016.06.29"
SetTimeFormat('hh:nn:ss'); // соответствует "17:35:16"
GoToTrendsClient(GetClientName);
begin
if Sender is TM_Object then
case TM_Object(Sender).ValueAsInt of
0 : Image5.AnimSpeed := 0;
1 : Image5.AnimSpeed := 10;
end;
end.
begin
if Sender is TM_Object then
case TM_Object(Sender).ValueAsInt of
0 : Sender.AnimSpeed := 0;
1 : Sender.AnimSpeed := 10;
end;
end.
if Sender is TM_Image then // если скрипт вызван каким-то изображением
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
if ValueAsInt = 1 then
AnimSpeed := 10
else
AnimSpeed := 0;
begin
if Sender is TM_Image then
with Sender as TM_Image do
if ValueAsInt = 0 then
AnimSpeed := 0
else
AnimSpeed := 10;
end.
TextFileOpen(IntToStr(HourOf(vrTimeBegin.Value)) + ' .' + IntToStr(MinuteOf(vrTimeBegin.Value)) + '.' + IntToStr(SecondOf(vrTimeBegin.Value)) +'.txt', '' , fomRewrite, fcpUTF8);
TextFileOpen(IntToStr(HourOf(vrTimeBegin.Value)) + '.' + IntToStr(MinuteOf(vrTimeBegin.Value)) + '.' + IntToStr(SecondOf(vrTimeBegin.Value)) +'.txt', '' , fomRewrite, fcpUTF8);
А как запустить скрипт из другого скрипта?Сейчас такой возможности нет.
Хотел создать из клиента папку с именем типа yyyy.мм.дд и в ней текстовый файл с именем типа чч.мм.сс.txtСкрипты для работы с папками добавим. Для того, чтобы изменить формат даты/времени используйте процедуры SetDateFormat и SetTimeFormat.
SetDateFormat('yyyy.mm.dd'); // соответствует "2016.06.29"
SetTimeFormat('hh:nn:ss'); // соответствует "17:35:16"
Найдите одно отличие) Я догадываюсь, что добавление любого символа в первые кавычки меняет тип.Если нужно привести строку к UTF8String, то можно писать так: UTF8String('.');. Но в данной задаче проще просто задать формат времени (SetTimeFormat), чтобы не составлять имена вручную.
Скрипты для работы с папками добавим. Для того, чтобы изменить формат даты/времени используйте процедуры SetDateFormat и SetTimeFormat.Я хотел использовать эти процедуры, но в описании не было примеров работы с ними, только назначение. Спасибо за помощь)
сделайте ещё функцию "Авто авторизация пользователя"Сейчас есть автоматическая авторизация. Логин и Пароль для автоматической авторизации вводятся в Options.exe, раздел "Simple-Client".
int2246, может быть вы выбрали у уровня свойство "Переменная доступа"? Убедитесь, что она не выбрана.Да дело было в этом. Спасибо большое.
var
A: TDateTime;
begin
A := Now;
repeat
Field1.Color := clGreen;
until SecondsBetween(Now, A) > 2 ;
Field1.Color := clRed;
end.
begin
{ прерываем процедуру, если прошло меньше 2 секунд }
if SecondsBetween(Now, vrTime.AsDateTime) < 2 then
Exit;
vrTime.Value := Now;
{ Весь код далее будет выполнен с частотой 2 секунды }
// ...
// ...
end.
MyVariable.Value := GetBit(...);
begin
svet1.Value := GetBit(tag1.Value, 0);
end.
не нужно так никогда делатьЧто это значит >:( (? Вы предусмотрели возможность бесконечных (или не бесконечных while (A < B)) циклов, так будьте добры реализовать эту функцию (только в разных потоках, или один скрипт один поток ПК) и все будет ОК.
Что это значит >:( (?Как мы и написали в предыдущем сообщении, работа скриптов в Simple-Scada 2 основана на системе событий которые возникают в процессе работы сервера скады (Server.exe). Сервер сам по себе является бесконечным циклом во время работы которого возникают события и выполняются соответствующие им скрипты. Если Вам нужно обрабатывать какие-то данные постоянно (или на протяжении какого-то времени), то нужно делать это на основе таймеров (+ это хорошо скажется на производительности), а не бесконечных циклов, т.к. это противоречит системе которая используется в Simple-Scada 2.
Короче надо функцию:Исходя из того, что описано выше - ничего подобного мы не планируем и если для Вас критически важно организовать обработку в бесконечном цикле, то стоит присмотреться к другим SCADA-системам.
main
{
...
if field.value :=0 then
vrTimerState.Value := 0;
else
vrTimerState.Value := 1;
begin
if vrTimerState.ValueAsInt = 1 then
txtTimer.Text := TimeToStr(Now - vrTime.Value);
end.
vrTime.Value := IncSecond(Now, -vrIntervalInSec.ValueAsInt);
begin
{ запуск таймера }
if vrTimerState.AsInt = 1 then
vrTime.Value := Now
else
{ остановка таймера }
txtTime.Text := '00:00';
end.
var
aTime: TDateTime;
begin
{ Если таймер запущен, то обновляем время каждую секунду }
if vrTimerState.AsInt = 1 then
begin
aTime := Now - vrTime.Value;
SetTimeFormat('nn:ss'); // формат минуты:секунды
txtTime.Text := TimeToStr(aTime);
end;
end.
Таймер должен считать время в Сек в течении которого переменная принимает значение 1. При значении переменной 0 - счет останавливается и оператор снимает показание таймера.Здесь все то же самое, как в примере выше, как Вы правильно поняли нужно только заменить переменную vrTimerState.
И можно ли реализовать формат ('sss') - Секунды как 3 цифры?Если нужно представление только в секундах, то можно изменить секундный таймер вот так:
var
aTime: TDateTime;
begin
{ Если таймер запущен, то обновляем время каждую секунду }
if vrTimerState.AsInt = 1 then
txtTime.Text := IntToStr(SecondsBetween(Now, vrTime.Value)); // выводим количество секунд
end.
begin
if GetBit(Alarm_PV1.Value,1) = true or
GetBit(Alarm_PV1.Value,2) = true or
GetBit(Alarm_PV1.Value,4) = true or
GetBit(Alarm_PV1.Value,5) = true or
GetBit(Alarm_PV1.Value,6) = true or
GetBit(Alarm_PV1.Value,10) = true or
GetBit(Alarm_PV1.Value,11) = true or
GetBit(Alarm_PV1.Value,12) = true or
GetBit(Alarm_PV1.Value,13) = true or
GetBit(Alarm_PV1.Value,14) = true then
Alarm_P1.Value := 1 else
Alarm_P1.Value := 0
end.
var
aValue: Integer;
begin
{ записываем в aValue значение переменной Alarm_PV1 в виде целого числа }
aValue := Alarm_PV1.AsInt;
{ проверяем нужные биты }
if GetBit(aValue, 1) or
GetBit(aValue, 2) or
GetBit(aValue, 4) or
GetBit(aValue, 5) or
GetBit(aValue, 6) or
GetBit(aValue, 10) or
GetBit(aValue, 11) or
GetBit(aValue, 12) or
GetBit(aValue, 13) or
GetBit(aValue, 14) then
Alarm_P1.Value := 1
else
Alarm_P1.Value := 0;
end.
В OPC-сервере имеется переменная U, кот. представляет собой напряжение, снятое с АЦП и имеющее диапазон значений 0 (0.00В) до 600 (6.00В).Конструкция case..of применяется только для целых чисел. Для вещественных придется писать обычные сравнения по типу "if aVar.AsFloat > 4.56 then". Но в Вашем случае, как мы поняли. переменная adc0 это целочисленная переменная со сдвигом запятой на 2 знака влево. Если это так, то вместо "adc0.AsInt" лучше использовать "adc0.OriginalAsInt". OriginalAsInt возвращает оригинальное значение переменой, без смещения запятой. И тогда можно использовать конструкцию case..of, вот так:
begin
Text1.Text := adc0.AsStr;
case adc0.OriginalAsInt of
0..429: Text1.FontColor := clRed; // Красный
430..519: Text1.FontColor := clBlack; // Черный
520..600: Text1.FontColor := clRed; // Красный
end;
end.
var
aText: TM_Text;
begin
{ прерываем процедуру, если скрипт вызван не объектом Текст }
if not (Sender is TM_Text) then Exit;
aText := Sender as TM_Text;
aText.Text := aText.AsStr;
case aText.Variable.OriginalAsInt of
0..429: aText.FontColor := clRed; // Красный
430..519: aText.FontColor := clBlack; // Черный
520..600: aText.FontColor := clRed; // Красный
end;
end.
Фон страницы нужен Белый. Как лучше это реализовать?Здесь можно посоветовать только поставить в качестве фона объект Фигура (Shape), растянуть его на нужную зону и выбрать цвет.
Можно ли все эти значения экспортировать в таблицу EXCEL или в БД MySQL? Или экспорт работает только с Трендами?Сейчас Simple-Scada 2 может выполнять любые запросы к БД и Вы можете сделать что угодно используя все возможность языка SQL. Для выполнения пользовательских SQL команд используйте процедуру RunSQL (http://simple-scada.com/scripts_manual?section=work-db). Можно создать отдельную таблицу и добавлять в неё любые данные. Здесь главное знать как правильно написать SQL запросы. Хороший пример есть в Демо-проекте (страница Скрипты - Работа с БД). там есть и создание таблицы, запись и чтение из этой таблицы. Можно также вместо БД выводить данные в текстовые, или двоичные файлы (http://simple-scada.com/scripts_manual?section=file-work). Экспортировать в Excel сейчас можно только данные переменных, которые пишутся в тренды.
Нигде не мог найти, какие арифметические и логические функции можно использовать в скриптах.В скриптах Simple-Scada 2 используется Object Pascal компилятор. Обо всех командах и операторах присущих языкам Pascal/Delphi можно прочесть в интернете на сайтах связанных с языками Pascal/Delphi. Например, описание типов данных (http://www.delphibasics.ru/1Types.php) и ключевые слова (http://www.delphibasics.ru/1Keywords.php) (к которым и относятся такие слова как or или and и др.).
Почему имя переменной при обьявлении в скриптах начинается с буквы "a" - aValue?Это распространенный в программировании способ именования локальных переменных, или параметров методов. Буква "a" добавляется для того, чтобы имя стало уникальным, это позволяет убрать возможную путаницу в именах переменных/объектов и совпадение имён.
По ссылке к языку Delphi - не вижу оператора умножения (или его просто нет)?Yuriy оператор умнажения есть *
По ссылке к языку Delphi - не вижу оператора умножения (или его просто нет)?Обычные операторы: "+" (сложение), "-" (вычитание), "/" (деление с остатком), "*" (умножение).
И по имени локальной переменной при обьявлении в скриптах - можно ли обозвать ее не aValue, а например bValue?Да, можно использовать любое имя.
begin
with TM_Object(Sender) do // далее будем работать с объектом Sender приведенным к типу TM_Object
case AsInt of
0: Value := 1;
1: Value := 0;
end;
end.
var
aStr: string;
begin
aStr := 'Привет мир!';
Text1.Text := UTF8Copy(UTF8Encode(aStr), 1, 6); // в Text1 запишется "Привет"
end.
begin
Obem1 := V_1.AsFloat + V1_1.AsFloat * 0.0001;
end.
begin
Obem1.Value := V_1.AsFloat + V1_1.AsFloat * 0.0001;
end.
И где в редакторе посмотреть позицию 49?Это позиция курсора по горизонтали и она указывает на конец строки " Obem1 := V_1.AsFloat + V1_1.AsFloat * 0.0001;", ровно 49 символов.
Скрипт 1
begin
if PLC2_FIRST_PRG_zkh1_open_ok.AsInt = 1 then
virtZKH1_txt.Value := 'Открыта'
else
virtZKH1_txt.Value := '----'
end.
Скрипт 2
begin
if PLC2_FIRST_PRG_zkh1_close_ok.AsInt = 1 then
virtZKH1_txt.Value := 'Закрыта'
else
virtZKH1_txt.Value := '----'
end.
РЕШЕНО!Не успели, но здесь видимо нужно было что-то вроде этого кода:
begin
if (PLC2_FIRST_PRG_zkh1_open_ok.AsInt = 1) and (PLC2_FIRST_PRG_zkh1_close_ok.AsInt <> 1) then
virtZKH1_txt.Value := 'Открыта';
end.
begin
if (PLC2_FIRST_PRG_zkh1_close_ok.AsInt = 1) and (PLC2_FIRST_PRG_zkh1_open_ok.AsInt <> 1) then
virtZKH1_txt.Value := 'Закрыта';
end.
begin
if PLC2_FIRST_PRG_zkh1_close_ok.AsInt = 1 then
virtZKH1_txt.Value := 'Закрыта';
if (PLC2_FIRST_PRG_zkh1_close_ok.AsInt = 0) and (PLC2_FIRST_PRG_zkh1_open_ok.AsInt = 0) then
virtZKH1_txt.Value := '----'
end.
begin
if PLC2_FIRST_PRG_zkh1_open_ok.AsInt = 1 then
virtZKH1_txt.Value := 'Открыта';
if (PLC2_FIRST_PRG_zkh1_close_ok.AsInt = 0) and (PLC2_FIRST_PRG_zkh1_open_ok.AsInt = 0) then
virtZKH1_txt.Value := '----'
end.
Здравствуйте, можно ли как-нибудь "спросить" скриптом у группы трендов какой у нее ID?596039, здравствуйте, а для какой цели это нужно? Можно посмотреть ID нужной группы в Редакторе и указать этот ID.
Name := 'TrendGroup' + intToStr(Object.Tag);
begin
if not (Sender is TM_Image) then Exit;
Field_Speed_P.Variable := GetVariableByName('Speed_P_' + IntToStr(Sender.Tag));
Field_Speed_V.Variable := GetVariableByName('Speed_V_' + IntToStr(Sender.Tag));
Field_Three_Way_Heat.Variable := GetVariableByName('Three_Way_Heat_' + IntToStr(Sender.Tag));
Field_Set.Variable := GetVariableByName('Set_' + IntToStr(Sender.Tag));
Field_Temp_Room.Variable := GetVariableByName('Temp_Room_' + IntToStr(Sender.Tag));
Field_Temp_Air.Variable := GetVariableByName('Temp_Air_' + IntToStr(Sender.Tag));
Field_Temp_Water.Variable := GetVariableByName('Temp_Water_' + IntToStr(Sender.Tag));
Image_Mode_Cool.Variable := GetVariableByName('Mode_' + IntToStr(Sender.Tag));
Image_Mode_Heat.Variable := GetVariableByName('Mode_' + IntToStr(Sender.Tag));
Button_Mode.Variable := GetVariableByName('Mode_' + IntToStr(Sender.Tag));
Image_Filtr1.Variable := GetVariableByName('Status_P_' + IntToStr(Sender.Tag));
Image_Filtr2.Variable := GetVariableByName('Status_P_' + IntToStr(Sender.Tag));
Image_Vent_P.Variable := GetVariableByName('Status_P_' + IntToStr(Sender.Tag));
Image_Vent_V.Variable := GetVariableByName('Status_V_' + IntToStr(Sender.Tag));
Image_Trend.Variable := GetVariableByName(IntToStr(Sender.Tag)); //Присваиваем значение переменной
Text_PV.Text := 'ПВ_' + IntToStr(Sender.Tag);
case Image_Vent_P.AsInt of
0, 2, 4..7: Image_Vent_P.AnimSpeed := 0;
1, 3: Image_Vent_P.AnimSpeed := GetVariableByName('Speed_P_' + IntToStr(Sender.Tag)).AsInt;
end;
case Image_Vent_V.AsInt of
0, 2, 4..7: Image_Vent_V.AnimSpeed := 0;
1, 3: Image_Vent_V.AnimSpeed := GetVariableByName('Speed_V_' + IntToStr(Sender.Tag)).AsInt;
end;
if GetVariableByName('Status_P_' + IntToStr(Sender.Tag)).AsInt = 3 then
begin
Image_Filtr2.Visible := True;
Image_Filtr1.Visible := False;
Text1_Filtr.FlashColor := clRed;
Text2_Filtr.Text := 'Требует замены';
Text2_Filtr.FlashColor := clRed;
end else
begin
Image_Filtr2.Visible := False;
Image_Filtr1.Visible := True;
Text1_Filtr.FlashColor :=clNone;
Text2_Filtr.Text := 'В работе';
Text2_Filtr.FlashColor :=clNone;
end;
end.
var
Name : String;
Trend : TM_Variable;
begin
Name := GetClientName;
Trend := GetVariableByName('Image_Trend');
GoToTrendsGroupClient (Name, Trend.Value + 3); //Подставляем нужный номер в идентификатор
end.
begin
if not (Sender is TM_Image) then Exit;
{ записываем в Тег кнопки ID-группы в которую нужно будет перейти }
ButtonToTrend.Tag := StrToInt(Sender.Hint); // эта строка
{ далее код Вашего скрипта без изменений }
Field_Speed_P.Variable := GetVariableByName('Speed_P_' + IntToStr(Sender.Tag));
Field_Speed_V.Variable := GetVariableByName('Speed_V_' + IntToStr(Sender.Tag));
...
begin
GoToTrendsGroupClient (GetClientName, Sender.Tag);
end.
begin
if vrInit.AsBool then
begin
{ получаем значение, накопленное за прошедший час }
vrByHour.Value := Int1.AsInt - vrStartCount.AsInt;
// ... здесь можно разместить код сохранения значения за прошедший час в БД, или файл
vrStartCount.Value := Int1.AsInt;
end else
begin
vrInit.Value := True;
vrStartCount.Value := Int1.AsInt;
end;
end.
procedure Text1_OnDataChange(Sender: TM_Control);
begin
// если реле выключается, то записываем vrP в vrPmax
if vrI.AsBool = False then
vrPmax.Value := vrP.Value;
end.
begin
vrBool.Archived := not vrBool.AsBool;
end.
А как толщину линии добавитьСейчас это невозможно, из-за некоторых особенностей отрисовки. После внедрения системы слоёв в тренды рассмотрим также возможность изменения толщины линии тренда.
Если так, то можно просто запоминать в начале каждого часа текущее значение и записывать измеренное за прошедший час. Для корректной работы нужно три виртуальных переменных:Далее создаем скрипт с типом события "Прошел час" и пишем такой код:
- vrInit (тип данных Boolean, можно без шкалы);
- vrByHour (тип данных тот же, что и у переменной счетчика, шкала должна быть достаточно большой). Для хранения результата за прошедший час;
- vrStartCount (тип данных тот же, что и у переменной счетчика, шкала должна быть достаточно большой). Хранит значение счетчика на начало часа;
Кодbegin
if vrInit.AsBool then
begin
{ получаем значение, накопленное за прошедший час }
vrByHour.Value := Int1.AsInt - vrStartCount.AsInt;
// ... здесь можно разместить код сохранения значения за прошедший час в БД, или файл
vrStartCount.Value := Int1.AsInt;
end else
begin
vrInit.Value := True;
vrStartCount.Value := Int1.AsInt;
end;
end.
При этом расчеты появятся в переменной vrByHour только после того, как пройдет один полный час после старта сервера скады. И соответственно после перезапуска сервера также придется ждать один полный час, чтобы скада показала накопленное значение за этот час.
begin
Temp3.Value := Temp1.Value - Temp2.Value;
end.
var
Alarm_Bit : TM_Variable;
b : byte;
Begin
for b := 0 to 15 do
begin
Alarm_bit := GetVariableByName('Alarm_Bit_' + IntToStr(b));
Alarm_bit.Value := GetBit(Alarm_1.Value , b);
end;
End.
var
Alarm_Bit : TM_Variable;
b : byte;
Begin
for b := 0 to 15 do
begin
Alarm_bit.Value := GetBit(Alarm_1.Value , b);
Alarm_bit := GetVariableByName('Alarm_Bit_' + IntToStr(b));
end;
End.
var
Alarm_Bit : TM_Variable;
b : byte;
Begin
for b := 0 to 15 do
begin
Alarm_bit := GetVariableByName('Alarm_Bit_' + IntToStr(b));
if Alarm_bit <> nil then // если переменная найдена, то работаем с ней
Alarm_bit.Value := GetBit(Alarm_1.Value , b);
end;
End.
var
Alarm_Bit : TM_Variable;
b : byte;
Begin
Alarm_bit := Alarm_1; // теперь Alarm_bit инициализирован и указывает на переменную Alarm_1
for b := 0 to 15 do
begin
Alarm_bit.Value := GetBit(Alarm_1.Value , b);
Alarm_bit := GetVariableByName('Alarm_Bit_' + IntToStr(b));
end;
End.
aQuery := 'UPDATE `program_table` ' +
'SET `TRM1_ProgramDuration` = `2017.02.03 16:48:36`, ' +
'`TRM2_ProgramDuration` = NOW() WHERE `ID` = 1;';
update TENQ.program_table SET TRM1_ProgramDuration = '2017.02.03 17:48:36', TRM2_ProgramDuration = Now() where ID = 1;
aTRM1_ProgramDuration := QuotedStr('2017.02.03 16:48:36');
aQuery := 'UPDATE `program_table` ' +
'SET `TRM1_ProgramDuration` = ' + aTRM1_ProgramDuration + ', ' +
'`TRM2_ProgramDuration` = NOW() WHERE `ID` = 1;';
aTRM1_ProgramDuration := QuotedStr(DateTimeToStr(vrTRM1_ProgramDuration.AsDateTime));
aTRM2_ProgramDuration := QuotedStr(DateTimeToStr(vrTRM2_ProgramDuration.AsDateTime));
aQuery := 'UPDATE `program_table` ' +
'SET `TRM1_ProgramDuration` = ' + aTRM1_ProgramDuration + ', ' +
'`TRM2_ProgramDuration` = ' + aTRM2_ProgramDuration + ' WHERE `ID` = 1;';
В MySQL WorkBench работает такая последовательность.Дело в том, что в редакторе скриптов текст заключается в одинарные кавычки (апострофы), например: 'текст'. В SQL текст также заключается в одинарные кавычки. И если мы выполним запрос: RunSQL('my_text');, то в СУБД придет текст без кавычек, т.е. my_text. И если мы хотим передать текст с кавычками, то мы можем использовать функцию QuotedStr, которая заключает строку в одинарные кавычки, например: RunSQL(QuotedStr('my_text'));. Можно также обойтись без QuotedStr и чтобы добавить в строку одинарную кавычку её в строке нужно удвоить. Т.е. без QuotedStr наш скрипт будет таким: RunSQL('''my_text'''); и в СУБД придет 'my_text'. Или ещё пример: RunSQL('''hello'' my ''friend''');, в этом случае в СУБД будет передана строка 'hello' my 'friend'.
И еще вопрос. Это шаманство с апострофами/одинарными кавычками для названий таблиц, столбцов можно убрать?Имена таблиц и столбцов в MySQL часто заключают в другие одинарные кавычки, которые находятся на клавише "ё" и выглядят так: ``. Их основное предназначение в том, чтобы избежать путаницы между зарезервированными ключевыми словами и названиями переменных, столбцов или таблиц. Например в SQL есть ключевое слово SELECT и мы хотим назвать столбец также. Для этого мы пишем так: SELECT * FROM `select` WHERE ...;
Я инициализировал переменные vrTRM1_ProgramDuration через функцию StrToTime.StrToTime запишет в переменную только время, без даты и дата останется 30 декабря 1899. Поэтому нужно инициализировать используя StrToDateTime (дата + время), например:
var
D: TDateTime;
begin
D := StrToDateTime('03.02.2017 16:48:36');
end.
aQuery := 'UPDATE `program_table` ' +
'SET `TRM1_ProgramDuration` = `2017.02.03 16:48:36`, ' +
'`TRM2_ProgramDuration` = NOW() WHERE `ID` = 1;';
aQuery := 'UPDATE program_table ' +
'SET TRM1_ProgramDuration = ''2017.02.03 16:48:36'', ' +
'TRM2_ProgramDuration = NOW() WHERE id = 1;';
Такой вопрос: есть несколько полей со значениями и нужно вывести наибольшее из них в отдельное поле.Пусть это поля fldA, fldB, fldC, а поле с максимумом fldMax. Каждое поле должно быть связано со своей переменной, а поле fldMax связано с виртуальной переменной. Тогда переходим к скрипту OnDataChange для поля fldA и пишем такой код:
var
aMax: Double;
begin
aMax := fldA.AsFloat;
if fldB.AsFloat > aMax then aMax := fldB.AsFloat;
if fldC.AsFloat > aMax then aMax := fldC.AsFloat;
fldMax.Value := aMax;
end.
И еще использую процедуру "Button_OnClick" со скриптом MinimizeApplicationClient(GetClientName) на сворачивание клиента, и если запущено несколько клиентов то сворачивание происходит на всех клиентах, то же самое и с закрытием. Использую версию Simple-Scada 2.1.2.0.Значит на всех клиентских компьютерах используется одинаковое имя (или оно не задано). Изменить имя клиента Вы можете через Options.exe (раздел Simple-Client, поле "Имя этого клиента").
var
aVar: TM_Variable;
begin
// сначала проверяем что скрипт вызван объектом
if Sender is TM_Object then
begin
// получаем переменную с которой связан объект
aVar := TM_Object(Sender).Variable;
// если не удалось получить переменную объекта, то прерываем выполнение скрипта
if not Assigned(aVar) then Exit;
if aVar.IsGoodQuality then
TM_Object(Sender).Color := clLime
else
TM_Object(Sender).Color := clRed;
end;
end.
огромное спасибо...возможно ли к данному скрипту прибавить сообщение..которое будет появляться в логе (+звук), при плохом признаке качества переменной... естественно сообщение должно соответствовать тому полю которое окрасилось в красный цвет..Да, можно. Вот так:
var
aVar: TM_Variable;
begin
// сначала проверяем что скрипт вызван объектом
if Sender is TM_Object then
begin
// получаем переменную с которой связан объект
aVar := TM_Object(Sender).Variable;
// если не удалось получить переменную объекта, то прерываем выполнение скрипта
if not Assigned(aVar) then Exit;
if aVar.IsGoodQuality then
TM_Object(Sender).Color := clLime
else
begin
TM_Object(Sender).Color := clRed;
AddMessage(Now, mkWarning, Sender.Name + '. Плохое качество тега!', TRUE, TRUE);
end;
end;
end.
Здравствуйте. В Simple Scada 2 имеется двадцать полей, в которые выводятся значения температур. Как можно периодически (по таймеру) подсвечивать поле с максимальным значением?Здравствуйте. Правильнее будет не по таймеру, а по изменению (OnDataChange). Все поля придется перебирать в скрипте (в будущем сможем предложить лучшее решение, но пока только так). Далее подробное описание реализации.
var
aMaxField: TM_Field;
procedure GetMax(AField: TM_Field);
begin
AField.Color := clSilver; // сбрасываем цвет поля на стандартный
if AField.AsInt > aMaxField.AsInt then
aMaxField := AField;
end;
begin
aMaxField := Field1;
GetMax(Field1);
GetMax(Field2);
//... и так далее
GetMax(Field20);
aMaxField.Color := clIndianRed;
end.
Вопрос такой - не получается при нажатии на определенный чекбох деактивировать остальные, связанные с данной переменной.Флажок всегда отображает состояние переменной, с которой он связан. И если все флажки связаны с одной и той же переменной, то они всегда будут отображать одно и то же состояние, т.е. будут либо все включены, било все выключены. А чтобы отделить их друг от друга сейчас придется использовать разные переменные.
Да, они связаны с одной переменной, но с разными ее битами (чтобы съэкономить на переменных).Теперь понятно, но к сожалению с битами ситуация такая же, ведь нужно при установке одного бита сбрасывать остальные. Сейчас этого нормально не реализовать даже скриптом, т.к. нажатие на флажок будет "перебивать" действие скрипта. Поэтому мы добавим объекту "Флажок" новое свойство "Значение", тогда можно будет реализовать радиокнопки на основе значения одной переменной, а также и на основе битов одной переменной. Т.е. получится максимальная гибкость и без использования скриптов. А пока нормально реализовать это можно только объектом "Список".
Еще вопрос. Как в скрипте задать иконку кнопке?Кнопка представляет собой набор состояний и работать нужно не с самой кнопкой, а с её состояниями (http://simple-scada.com/scripts_manual?section=script_instrument_button). Свойство Button54.Icon доступно только для чтения, оно возвращает номер иконки для активного состояния кнопки. Вот так можно изменить иконку для первых двух состояний кнопки:
begin
Button1.States[0].Icon := 5;
Button1.States[1].Icon := 5;
end.
Приложил картинку. Есть пять чекбоксов. Одновременно может быть нажат только один. Да, они связаны с одной переменной, но с разными ее битами (чтобы съэкономить на переменных). Допустим нажат первых чекбокс. Переменная равна 1 (бит 0). Если нажать чекбокс 2 переменная будет равна 3 (бит 1 + бит 0), а мне нужно чтобы отжался чекбокс 1 и переменная была равна 2. Надеюсь объяснил понятноЭкономить можно и нужно внешние переменные :) Внутренние то зачем?
1) при потере признака качества переменных (Connect и Connect_1) - отпал OPC сервер;Guchi , эта проверка никогда не будет достоверной, т.к. скада получает качество тегов и их значения с OPC-сервера и при потере связи скады с OPC-сервером она не получит BAD-статус на переменные Connect и Connect_1. Скада в этом случае просто начнет попытки переподключения к OPC-серверу. Вы работаете с удаленным OPC-сервером, или локальным? Если OPC-сервер на том же компьютере, что и скада, то связь между ними может потеряться только если OPC-сервер принудительно закроет пользователь.
2) значение 0 - нет связи с устройством, 1- есть связь с устройством (по каждому устройству соответственно)... сделать задержку (например на 1 минуту)Для этого удобнее всего сделать подсчет секунд в секундном таймере. Создать две внутренние переменные (одна на каждое устройство), допустим vrTimer_1 и vrTimer_2 с типом данных Integer и шкалой от 0 до 2147483647. И в секундном скрипте написать такой код:
begin
{ обрабатываем первый таймер }
if Connect.AsInt = 0 then
begin
vrTimer_1.Value := vrTimer_1.Value + 1;
if vrTimer_1.AsInt = 60 then
AddMessage(Now, mkAlarm, 'Нет связи с первым устройством!', TRUE, TRUE);
end else
vrTimer_1.Value := 0;
{ обрабатываем второй таймер }
if Connect_1.AsInt = 0 then
begin
vrTimer_2.Value := vrTimer_2.Value + 1;
if vrTimer_2.AsInt = 60 then
AddMessage(Now, mkAlarm, 'Нет связи со вторым устройством!', TRUE, TRUE);
end else
vrTimer_2.Value := 0;
end;
.с возможностью автоквитирования сообщения при возобновлении связиАвтоквитирование сделать не получится, т.к. данные неквитированных сообщений недоступны из скриптов.
var
aGoodCount, aBadCount: Byte;
a61447, a61493: Byte;
procedure Check(AQuality: Boolean; var ATag: TM_Variable);
begin
{ если нужно проверять качество }
if AQuality = TRUE then
if ATag.IsGoodQuality then
Inc(aGoodCount)
else
Inc(aBadCount);
{ если нужно проверять значение }
if AQuality = FALSE then
if ATag.AsInt = 61447 then
Inc(a61447)
else
if ATag.AsInt = 61493 then
Inc(a61493);
end;
{ проверяет восемь тегов на качество (при AQuality = TRUE)
или на значение (при AQuality = FALSE) }
procedure GoodOrBad(AQuality: Boolean; var T1, T2, T3, T4, T5, T6, T7, T8: TM_Variable);
begin
aGoodCount := 0;
aBadCount := 0;
a61447 := 0;
a61493 := 0;
Check(AQuality, T1);
Check(AQuality, T2);
Check(AQuality, T3);
Check(AQuality, T4);
Check(AQuality, T5);
Check(AQuality, T6);
Check(AQuality, T7);
Check(AQuality, T8);
end;
begin
{ проверяем качество 8 тегов первого устройства }
GoodOrBad(TRUE, Tag1, Tag2, Tag3, Tag4, Tag5, Tag6, Tag7, Tag8);
{ если все 8 тегов с плохим качеством }
if aBadCount = 8 then
AddMessage(Now, mkAlarm, 'Устройство 1. Нет связи!', TRUE, TRUE);
{ если все 8 тегов с хорошим качеством }
if aGoodCount = 8 then
begin
{ проверяем значение тегов 17..24 }
GoodOrBad(FALSE, Tag17, Tag18, Tag19, Tag20, Tag21, Tag22, Tag23, Tag24);
if a61447 = 8 then // все равны 61447
AddMessage(Now, mkAlarm, 'Устройство 1. Датчик отключен!', TRUE, TRUE);
if a61493 = 8 then // все равны 61493
AddMessage(Now, mkAlarm, 'Устройство 1. Обрыв датчика!', TRUE, TRUE);
end;
////// далее тот же код, но для тегов второго устройства //////
{ проверяем качество 8 тегов второго устройства }
GoodOrBad(TRUE, Tag9, Tag10, Tag11, Tag12, Tag13, Tag14, Tag15, Tag16);
{ если все 8 тегов с плохим качеством }
if aBadCount = 8 then
AddMessage(Now, mkAlarm, 'Устройство 2. Нет связи!', TRUE, TRUE);
{ если все 8 тегов с хорошим качеством }
if aGoodCount = 8 then
begin
{ проверяем значение тегов 25..32 }
GoodOrBad(FALSE, Tag25, Tag26, Tag27, Tag28, Tag29, Tag30, Tag31, Tag32);
if a61447 = 8 then // все равны 61447
AddMessage(Now, mkAlarm, 'Устройство 2. Датчик отключен!', TRUE, TRUE);
if a61493 = 8 then // все равны 61493
AddMessage(Now, mkAlarm, 'Устройство 2. Обрыв датчика!', TRUE, TRUE);
end;
end.
Код:
begin
if Field1.AsInt > 0 then
Image1.AnimSpeed := 16
else
Image1.AnimSpeed := 0;
end.
begin
if Image1.AsInt > 0 then
Image1.AnimSpeed := 16
else
Image1.AnimSpeed := 0;
end.
begin
if Field1.AsInt > 0 then
Image1.AnimSpeed := 16
else
Image1.AnimSpeed := 0;
end.
Анимация начинает срабатывать только в таком случае, при var1 - Field1, Image1
Задача: написать скрипт таймера наработки оборудования с функциями ПУСК, СТОП и ПАУЗА, работающего от системного времени.
Как будет выглядеть код?
begin
{ обрабатываем первый таймер }
if Tag11.IsGoodQuality = False then
if Tag12.IsGoodQuality = False then
if Tag13.IsGoodQuality = False then
if Tag14.IsGoodQuality = False then
if Tag15.IsGoodQuality = False then
if Tag16.IsGoodQuality = False then
if Tag17.IsGoodQuality = False then
if Tag18.IsGoodQuality = False then
begin
vrTimer_1.Value := vrTimer_1.Value + 1;
begin
if vrTimer_1.AsInt = 1 then
AddMessage(Now, mkMessage, 'Отсутсвует связь. Устройство 1!', TRUE, FALSE);
if vrTimer_1.AsInt = 60 then
AddMessage(Now, mkWarning, 'Отсутсвует связь. Устройство 1!', TRUE, FALSE);
if vrTimer_1.AsInt = 180 then
AddMessage(Now, mkAlarm, 'Нет связи. Устройство 1 !', TRUE, TRUE);
end;
end else
vrTimer_1.Value := 0;
{ обрабатываем второй таймер }
if Tag21.IsGoodQuality = False then
if Tag22.IsGoodQuality = False then
if Tag23.IsGoodQuality = False then
if Tag24.IsGoodQuality = False then
if Tag25.IsGoodQuality = False then
if Tag26.IsGoodQuality = False then
if Tag27.IsGoodQuality = False then
if Tag28.IsGoodQuality = False then
begin
vrTimer_2.Value := vrTimer_2.Value + 1;
begin
if vrTimer_2.AsInt = 1 then
AddMessage(Now, mkMessage, 'Отсутсвует связь. Устройство 2!', TRUE, FALSE);
if vrTimer_2.AsInt = 60 then
AddMessage(Now, mkWarning, 'Отсутсвует связь. Устройство 2!', TRUE, FALSE);
if vrTimer_2.AsInt = 180 then
AddMessage(Now, mkAlarm, 'Нет связи. Устройство 2 !', TRUE, TRUE);
end;
end else
vrTimer_2.Value := 0
end.
var
aGoodCount, aBadCount: Byte;
a61447, a61493: Byte;
procedure Check(AQuality: Boolean; var ATag: TM_Variable);
begin
{ если нужно проверять качество }
if AQuality = TRUE then
if ATag.IsGoodQuality then
Inc(aGoodCount)
else
Inc(aBadCount);
{ если нужно проверять значение }
if AQuality = FALSE then
if ATag.AsInt = 61447 then
Inc(a61447)
else
if ATag.AsInt = 61493 then
Inc(a61493);
end;
{ проверяет восемь тегов на качество (при AQuality = TRUE)
или на значение (при AQuality = FALSE) }
procedure GoodOrBad(AQuality: Boolean; var T1, T2, T3, T4, T5, T6, T7, T8: TM_Variable);
begin
aGoodCount := 0;
aBadCount := 0;
a61447 := 0;
a61493 := 0;
Check(AQuality, T1);
Check(AQuality, T2);
Check(AQuality, T3);
Check(AQuality, T4);
Check(AQuality, T5);
Check(AQuality, T6);
Check(AQuality, T7);
Check(AQuality, T8);
end;
begin
{ проверяем качество 8 тегов первого устройства }
GoodOrBad(TRUE, Tag1, Tag2, Tag3, Tag4, Tag5, Tag6, Tag7, Tag8);
{ если все 8 тегов с плохим качеством }
if aBadCount = 8 then
AddMessage(Now, mkAlarm, 'Устройство 1. Нет связи!', TRUE, TRUE);
{ если все 8 тегов с хорошим качеством }
if aGoodCount = 8 then
begin
{ проверяем значение тегов 17..24 }
GoodOrBad(FALSE, Tag17, Tag18, Tag19, Tag20, Tag21, Tag22, Tag23, Tag24);
if a61447 = 8 then // все равны 61447
AddMessage(Now, mkAlarm, 'Устройство 1. Датчик отключен!', TRUE, TRUE);
if a61493 = 8 then // все равны 61493
AddMessage(Now, mkAlarm, 'Устройство 1. Обрыв датчика!', TRUE, TRUE);
end;
////// далее тот же код, но для тегов второго устройства //////
{ проверяем качество 8 тегов второго устройства }
GoodOrBad(TRUE, Tag9, Tag10, Tag11, Tag12, Tag13, Tag14, Tag15, Tag16);
{ если все 8 тегов с плохим качеством }
if aBadCount = 8 then
AddMessage(Now, mkAlarm, 'Устройство 2. Нет связи!', TRUE, TRUE);
{ если все 8 тегов с хорошим качеством }
if aGoodCount = 8 then
begin
{ проверяем значение тегов 25..32 }
GoodOrBad(FALSE, Tag25, Tag26, Tag27, Tag28, Tag29, Tag30, Tag31, Tag32);
if a61447 = 8 then // все равны 61447
AddMessage(Now, mkAlarm, 'Устройство 2. Датчик отключен!', TRUE, TRUE);
if a61493 = 8 then // все равны 61493
AddMessage(Now, mkAlarm, 'Устройство 2. Обрыв датчика!', TRUE, TRUE);
end;
end.
Пример таймера наработки с функциями ПУСК, СТОП и ПАУЗА Вы можете найти в демо-проекте: Страница "Скрипты" -> подстраница "Простые скрипты(1)" -> пример №8 "Пример секундомера".
begin
if vrTimerState.AsInt = 0 then
vrTime.Value := IncSecond(Now, -vrIntervalInSec.AsInt)
else
vrIntervalInSec.Value := SecondsBetween(Now, vrTime.Value);
end.
begin
vrTimerState.Value := 0;
vrIntervalInSec.Value := 0;
txtTimer.Text := '0 дней 00:00:00';
end.
begin
{ Код для таймера. Если таймер запущен выводим накопленное время }
if vrTimerState.AsInt = 1 then
txtTimer.Text := IntToStr(DaysBetween(Now, vrTime.Value)) + ' дней ' + TimeToStr(Now - vrTime.Value);
end.
5. Написал скриптДа я ввел этот скрипт, в надежде, что он как-то выведет информацию о текущем времени, но к чему его "прикрутить" не знаю ???
А когда этот скрипт и чем вызывается?
Да я ввел этот скрипт, в надежде, что он как-то выведет информацию о текущем времени, но к чему его "прикрутить" незнаю ???Например сделать его секундным, или часовым, или по событию (по кнопке). Я ж не знаю как вам надо :)
Например сделать его секундным, или часовым, или по событию (по кнопке). Я ж не знаю как вам надо :) Но если скрипт не вызывать, он точно не исполнится.
Например сделать его секундным, или часовым, или по событию (по кнопке). Я ж не знаю как вам надо :) Но если скрипт не вызывать, он точно не исполнится.
При условии, когда две переменных становятся больше нуля, включается таймер, если в процессе условие не соблюдается - таймер переходит в режим ПАУЗЫ.
begin
if vr_4.Value > 0 and vr_5.Value > 0 then
begin
{ если выполнено условие, то включается таймер }
vrIntervalInSec_2.Value := SecondsBetween(Now, vrTime_2.Value);
{ При включении таймера выводим накопленное время }
txtTimer_2.Text := IntToStr(DaysBetween(Now, vrTime_2.Value)) + ' дней ' + TimeToStr(Now - vrTime_2.Value);
end;
else
{ если таймер ещё не включен }
vrTime_2.Value := IncSecond(Now, -vrIntervalInSec_2.AsInt);
end.
...
txtTimer_2.Text := IntToStr(DaysBetween(Now, vrTime_2.Value)) + ' дней ' + TimeToStr(Now - vrTime_2.Value);
end else
{ если таймер ещё не включен }
...
вышеописанный скрипт с Check процедурой выдавал каждую секунду что "Нет связи с Устройством 1", "Нет связи с Устройством 2".Да, забыли учесть таймеры. Если добавить таймер и убрать проверку на значение, то скрипт будет таким:
Насчет проверки по значению- эту часть скрипта можно убрать...только по качеству
var
aGoodCount, aBadCount: Byte;
procedure Check(var ATag: TM_Variable);
begin
if ATag.IsGoodQuality then
Inc(aGoodCount)
else
Inc(aBadCount);
end;
procedure GoodOrBad(var T1, T2, T3, T4, T5, T6, T7, T8: TM_Variable);
begin
aGoodCount := 0;
aBadCount := 0;
Check(T1); Check(T2);
Check(T3); Check(T4);
Check(T5); Check(T6);
Check(T7); Check(T8);
end;
begin
{ проверяем качество 8 тегов первого устройства }
GoodOrBad(Tag1, Tag2, Tag3, Tag4, Tag5, Tag6, Tag7, Tag8);
{ если все 8 тегов с плохим качеством }
if aBadCount = 8 then
begin
vrTimer_1.Value := vrTimer_1.Value + 1;
case vrTimer_1.AsInt of
1: AddMessage(Now, mkMessage, 'Отсутствует связь. Устройство 1!', TRUE, FALSE);
60: AddMessage(Now, mkWarning, 'Отсутствует связь. Устройство 1!', TRUE, FALSE);
180: AddMessage(Now, mkAlarm, 'Нет связи. Устройство 1!', TRUE, TRUE);
end;
end else
vrTimer_1.Value := 0;
////// далее тот же код, но для тегов второго устройства //////
{ проверяем качество 8 тегов второго устройства }
GoodOrBad(Tag9, Tag10, Tag11, Tag12, Tag13, Tag14, Tag15, Tag16);
{ если все 8 тегов с плохим качеством }
if aBadCount = 8 then
begin
vrTimer_2.Value := vrTimer_2.Value + 1;
case vrTimer_2.AsInt of
1: AddMessage(Now, mkMessage, 'Отсутствует связь. Устройство 2!', TRUE, FALSE);
60: AddMessage(Now, mkWarning, 'Отсутствует связь. Устройство 2!', TRUE, FALSE);
180: AddMessage(Now, mkAlarm, 'Нет связи. Устройство 2!', TRUE, TRUE);
end;
end else
vrTimer_2.Value := 0;
end.
В Делфи есть AsTime и TimeToStr, а как тут сделать?Можно так же как в Delphi перевести в строку только время используя TimeToStr и затем сделать сравнение строк. TimeToStr вытащит из TDateTime только время, без даты. Например:
var
dt1, dt2: TDateTime;
Time1, Time2: string;
begin
dt1 := Now;
dt2 := Now;
Time1 := TimeToStr(dt1);
Time2 := TimeToStr(dt2);
if Time1 = Time2 then
begin
// время в dt1 равно времени dt2!
end;
end.
var
dt1, dt2: TDateTime;
begin
dt1 := Now;
dt2 := Now;
{ если часы, минуты и секунды у dt1 и dt2 равны }
if (HourOf(dt1) = HourOf(dt2)) and
(MinuteOf(dt1) = MinuteOf(dt2)) and
(SecondOf(dt1) = SecondOf(dt2)) then
begin
// время в dt1 равно времени dt2!
end;
end.
const
Time_1 =(08, 00, 00);
var Time_1: TDataTime;
begin
Time_1 := (08, 00, 00);
var
Time_1 : TDateTime;
begin
Time_1.Value := EncodeTime(8, 0, 0, 0);
end.
Но возможно Вашу задачу можно решить проще. Можете описать то, что нужно сделать в скрипте? Мы предложим решение.
Простите, но это работа контроллера, а не скады...Да мне бы пока понять как включить и выключить таймер по определенному времени. А с контроллером...увы, в этом проекте я занимаюсь только скадой.
Таймер будет включаться не только от времени, но еще и от дополнительных переменных. Как управлять таймером от переменных на данный момент времени я разобрался.Ну так, а время это что, не переменная? Задайте в секундном скрипте проверку времени, и зависимо от смены переменной смена присваивайте значение 1,2,3.
необходимо сделать таймер наработки оборудования с тремя интервалами во времени. Например, таймер формирует отдельные отрезки времени с 8.00 до 12.00, далее с 12.00 до 16.00 и т.д. При этом компонент таблица регистрирует эти данные в соответствующие ячейки. В результате, чтобы можно было проследить сколько работало оборудование в трех сменах в течении суток, скажем, за месяц.Но ведь в этом случае все накопленные в таблице записи будут потеряны при первом перезапуске сервера скады или проекта. К тому же, чтобы они добавлялись в таблицу придется на каждый интервал времени выделить переменные и связать ячейки таблицы с этими переменными. Причем если планируется выводить наработку за месяц добавляя по 3 новых строки в день, то получается что нужно создать 90 строк в таблице и заполнить все строки переменными. Плюс остается проблема с потерей данных при перезапусках. Такая система совсем не подходит для нормальной работы.
Но ведь в этом случае все накопленные в таблице записи будут потеряны при первом перезапуске сервера скады или проекта. К тому же, чтобы они добавлялись в таблицу придется на каждый интервал времени выделить переменные и связать ячейки таблицы с этими переменными. Причем если планируется выводить наработку за месяц добавляя по 3 новых строки в день, то получается что нужно создать 90 строк в таблице и заполнить все строки переменными. Плюс остается проблема с потерей данных при перезапусках. Такая система совсем не подходит для нормальной работы.C БД я займусь немного позже. А пока не пойму как сделать правильным сравнение по отрезку времени в четвертой строке:
Правильнее было бы выводить наработки получая их из архива. Т.е. включить у нужных тегов архивацию а затем выполнять выборку из БД за нужный интервал времени и подсчитывать время наработки. Тогда даже после перезапуска сервера все наработки сохранятся. Только сейчас все это нужно делать в запросе и готовых функций для вычисления наработок средствами скады нет. Постараемся их добавить в ближайших обновлениях.
begin
Timer_1.Value := Now;
txtTimer_4.Text := TimeToStr(Now);
if(Timer_1.Value > 07:00:00) and (Timer_1.Value < 15:00:00) then
begin
{ если выполнено условие, то включается таймер }
vrIntervalInSec_3.Value := SecondsBetween(Now, vrTime_3.Value);
{ При включении таймера выводим накопленное время }
txtTimer_3.Text := TimeToStr(Now - vrTime_3.Value);
Image8.Color := clRed
end
else
begin
{ если таймер ещё не включен }
Timer_1.Value := IncSecond(Now, -vrIntervalInSec_3.AsInt);
Image8.Color := clNone
end;
end.
А пока не пойму как сделать правильным сравнение по отрезку времени в четвертой строке:Т.е. нужно проверить, что час из значения Timer_1 больше 7 и меньше 15? Тогда нужно делать так:
var
aHour: Byte;
begin
aHour := HourOf(Timer_1.AsDateTime); // достаём час из переменной Timer_1 в переменную aHour
if (aHour > 7) and (aHour < 15) then
// ...
end.
Тогда нужно делать так:Спасибо за ответ.
begin
if FileOpen ('Hello.sbm') then
begin
FileWriteString (Field9.ValueAsStr);
FileWriteInteger (Field7.ValueAsInt);
FileWriteSingle (Field8.ValueAsFloat);
FileWriteString (Text13.Text);
FileWriteDateTime (Now);
end;
end.
begin
if FileOpen ('Hello.sbm', '') then // если файл по стандартному пути успешно открыт, то …
begin
FileWriteString (Field9.ValueAsStr);
FileWriteInteger (Field7.ValueAsInt);
FileWriteSingle (Field8.ValueAsFloat);
FileWriteString (Text13.Text);
FileWriteDateTime (Now);
end;
end.
С момента записи видеоурока функции FileOpen, FileRecreate, FileExists получили еще один параметр APath - задает полный путь к файлу. Если этот параметр оставить пустым (= ''), то будет взят стандартный путь: «..\Simple-Scada 2\Projects\Папка_проекта\User files\». Важно! При указании пути к файлу в конце обязательно должен быть символ «\».
begin
if FileOpen ('Hello.sbm', '') then
begin
FileWriteString (Field9.AsStr);
FileWriteInteger (Field7.AsInt);
FileWriteSingle (Field8.AsFloat);
FileWriteString (Text13.Text);
FileWriteDateTime (Now)
end;
end.
begin
if FileOpen('Hello.sbm', 'D:\Simple-Scada 2\Папка_файла\') then
begin
FileWriteString (Field9.AsStr);
FileWriteInteger (Field7.AsInt);
FileWriteSingle (Field8.AsFloat);
FileWriteString (Text13.Text);
FileWriteDateTime (Now);
end;
end.
Понял. Спасибо. Можно, конечно, без этого обойтись. Но новых версий будем ждать ))) И про редактор не забудьте. Всё-таки не очень удобно случайно удалив кусок кода не иметь возможности отменить это действие.Перед редактированием скриптов сохраняйтесь... ф9 кроме компиляции сохраняет скрипт... В случае аврала снимаете процесс и запускаете по новой... Сохранится последняя компиляция :)
var z : byte;
procedure Test(var x : byte);
begin
x := 1;
end;
//------------------------------------------------------------------------------------------------------
// ОСНОВНОЙ СЦЕНАРИЙ
//------------------------------------------------------------------------------------------------------
begin
Test(z); //Компилируется
Test(BIT_0.Value ); //Не компилируется (BIT_0 - глобальная переменная типа byte)
//Сообщение об ошибке: [uGlobal] Types of actual and formal var parameters must be identical
end.
procedure Test(var x :TM_Variable);
begin
x.Value := 1;
end;
//------------------------------------------------------------------------------------------------------
// ОСНОВНОЙ СЦЕНАРИЙ
//------------------------------------------------------------------------------------------------------
begin
Test(Тest3 );
end.
var
i:byte;
procedure Test(var x : byte);
begin
x := 1;
end;
//------------------------------------------------------------------------------------------------------
// ОСНОВНОЙ СЦЕНАРИЙ
//------------------------------------------------------------------------------------------------------
begin
i:=Тest3.Value;
Test(i);
end.
var z : byte;
procedure Test(var x : byte);
begin
x := 1;
end;
//------------------------------------------------------------------------------------------------------
// ОСНОВНОЙ СЦЕНАРИЙ
//------------------------------------------------------------------------------------------------------
begin
z := BIT_0.AsInt
Test(z);
BIT_0.Value := z;
end.
Процедура принимает тип Byte. AsInt возвращает переменную типа Integer, что шире типа Byte. Вот и ругается компилятор. Поэтому надо явно довести тип Integer до типа Byte. А это пока видимо можно сделать только через присвоение локальной переменной вашей глобальной переменной, потому что метода AsByte нет)А ну задайте-ка инты и там и там? :)
Как правильно заметил TeNQ есть несоответствие типов byte и integer. Всё дело в том, что в подпроцедуре Test параметр задан с меткой var. Это передача параметра по-ссылке и она требует строгого соответствия типов данных. Поэтому нужно делать как написал TeNQ.Окей. А если в процедуре указать Single, и воспользоваться методом .AsSingle переменной, то получится ли строгое совпадение типов? НЕТ...
procedure Test(x : byte);
begin
x := 1;
end;
begin
Test (Test3.AsInt64);
end.
А если в процедуре указать Single, и воспользоваться методом .AsSingle переменной, то получится ли строгое совпадение типов? НЕТ...Оно получится, но нужно будет писать также как писали в примере выше, через отдельную переменную, т.к. нужно передавать ссылку:
var
X: Single;
begin
X := vrMy.AsSingle;
Test(X);
end;
Можно лечить? Да! Спокойно компилитсяКонечно компилится, т.к. параметр больше не передается по ссылке, но подпроцедура теряет смысл и код свою задачу выполнять не будет, т.к. в подпроцедуре будет создана новая локальная переменная и значение равное 1 будет задано именно для неё, а не для той переменной, которая была передана в подпроцедуру.
procedure Test(x : byte);
begin
x := 1;
end;
begin
Test(Test3.AsInt64);
// здесь переменная Test3 будет равна тому же, чему была равна до выполнения Test,
// т.к. в подпроцедуре работа велась с локальной копией.
end.
var
X: Single;
begin
X := vrMy.AsSingle;
Test(X);
vrMy.Value:= X; // иначе, ничем от того что написал я, оно не отличается
end;
А так как я с саааамого начала написал нельзя? В плане передачи сразу ТМ_Variable и не плясать с бубном ?Как в сообщении #262? Тоже нельзя, т.к. в var параметр нельзя передавать свойство переменной, как Test(BIT_0.Value) или Test(BIT_0.AsInt), нужно обязательно передать переменную соответствующего типа.
procedure Test(AVariable: TM_Variable);
begin
AVariable.Value := 1;
end;
begin
Test(vrMy);
end.
сообщение 262, первый пример :) Да, вар там лишний(с разгону не вытер, у меня в коде его нету). Но в общем смысл тот-же. :-[ЦитироватьА так как я с саааамого начала написал нельзя? В плане передачи сразу ТМ_Variable и не плясать с бубном ?Как в сообщении #262? Тоже нельзя, т.к. в var параметр нельзя передавать свойство переменной, как Test(BIT_0.Value) или Test(BIT_0.AsInt), нужно обязательно передать переменную соответствующего типа.
Мы бы, в данной задаче, когда нужно в подпроцедуре менять значение переменной - не использовали бы подобных конструкций которые предложены в сообщениях выше, потому что проще передавать ссылку на переменную, вот так например:Кодprocedure Test(AVariable: TM_Variable);
begin
AVariable.Value := 1;
end;
begin
Test(vrMy);
end.
case x of
1: begin
y := func1;
z := func2;
end;
end;
case x of
1: proc(y, z);
end;
begin
{AUTO}
if GetBit(PLC2_FIRST_PRG_status.Value, 13) then
begin // если бит равен 1
bt_ZKH1_auto.States[0].Color := RGB(0,95,88);
bt_ZKH1_auto.States[0].BorderColor := RGB(0,95,88);
bt_ZKH1_auto.States[0].FontStep := 0;
bt_ZKH1_auto.States[0].Caption := 'Авто вкл.';
img_ZKH1_manual.Visible := FALSE ;
end
else
begin // если бит равен 0
bt_ZKH1_auto.States[0].Color := RGB(60,70,80);
bt_ZKH1_auto.States[0].BorderColor := RGB(60,70,80);
bt_ZKH1_auto.States[0].FontStep := 3;
bt_ZKH1_auto.States[0].Caption := 'Авто';
img_ZKH1_manual.Visible := TRUE ;
end;
end.
Все скрипты выполняются от одной переменной. Используя биты этой переменной.Т.е. есть одна переменная и множество объектов. Все объекты должны реагировать на изменение битов в переменной, причем каждый объект реагирует только на изменение какого-то определённого бита. Мы правильно поняли?
begin
{Скрипт меняет состояние кнопок и индикаторов PLC2}
// Marsh *************************************************************************
{SILOS 1}
if GetBit(PLC2_FIRST_PRG_status.Value, 0) then
begin //если бит равен 1
bt_silos1.States[0].Color := RGB(0,95,88);
bt_silos1.States[0].BorderColor := RGB(0,95,88);
end
else
begin //если бит равен 0
bt_silos1.States[0].Color := RGB(60,60,60);
bt_silos1.States[0].BorderColor := RGB(60,60,60);
end;
{SILOS 2}
if GetBit(PLC2_FIRST_PRG_status.Value, 1) then
begin //если бит равен 1
bt_silos2.States[0].Color := RGB(0,95,88);
bt_silos2.States[0].BorderColor := RGB(0,95,88);
end
else
begin //если бит равен 0
bt_silos2.States[0].Color := RGB(60,60,60);
bt_silos2.States[0].BorderColor := RGB(60,60,60);
end;
{on TSB}
if GetBit(PLC2_FIRST_PRG_status.Value, 2) then
begin //если бит равен 1
bt_onTSB.States[0].Color := RGB(0,95,88);
bt_onTSB.States[0].BorderColor := RGB(0,95,88);
end
else
begin //если бит равен 0
bt_onTSB.States[0].Color := RGB(60,60,60);
bt_onTSB.States[0].BorderColor := RGB(60,60,60);
end;
// TSH ***************************************************************************
{AUTO}
if GetBit(PLC2_FIRST_PRG_status.Value, 5) then
begin //если бит равен 1
bt_tsh_auto.States[0].Color := RGB(0,95,88);
bt_tsh_auto.States[0].BorderColor := RGB(0,95,88);
bt_tsh_auto.States[0].FontStep := 0;
bt_tsh_auto.States[0].Caption := 'Авто вкл.';
manual_tsh.Visible := FALSE;
end
else
begin //если бит равен 0
bt_tsh_auto.States[0].Color := RGB(60,70,80);
bt_tsh_auto.States[0].BorderColor := RGB(60,70,80);
bt_tsh_auto.States[0].FontStep := 3;
bt_tsh_auto.States[0].Caption := 'Авто';
manual_tsh.Visible := TRUE;
end;
{MANUAL ON}
if GetBit(PLC2_FIRST_PRG_status.Value, 6) then
begin //если бит равен 1
bt_tsh_on.States[0].Color := RGB(0,95,88);
bt_tsh_on.States[0].BorderColor := RGB(0,95,88);
bt_tsh_on.States[0].Caption := 'Включено';
end
else
begin //если бит равен 0
bt_tsh_on.States[0].Color := RGB(60,70,80);
bt_tsh_on.States[0].BorderColor := RGB(60,70,80);
bt_tsh_on.States[0].Caption := 'Включить';
end;
{MANUAL OFF}
if GetBit(PLC2_FIRST_PRG_status.Value, 7) then
begin //если бит равен 1
bt_tsh_off.States[0].Color := RGB(0,95,88);
bt_tsh_off.States[0].BorderColor := RGB(0,95,88);
bt_tsh_off.States[0].Caption := 'Выключено';
end
else
begin //если бит равен 0
bt_tsh_off.States[0].Color := RGB(60,70,80);
bt_tsh_off.States[0].BorderColor := RGB(60,70,80);
bt_tsh_off.States[0].Caption := 'Выключить';
end;
{REVERS ON/OFF}
if GetBit(PLC2_FIRST_PRG_status.Value, 4) then
img_tsh_revers.Visible := true
else
img_tsh_revers.Visible := false;
// NORIAH ************************************************************************
{AUTO}
if GetBit(PLC2_FIRST_PRG_status.Value, 10) then
begin //если бит равен 1
bt_noriah_auto.States[0].Color := RGB(0,95,88);
bt_noriah_auto.States[0].BorderColor := RGB(0,95,88);
bt_noriah_auto.States[0].FontStep := 0;
bt_noriah_auto.States[0].Caption := 'Авто вкл.';
manual_noriah.Visible := FALSE;
end
else
begin //если бит равен 0
bt_noriah_auto.States[0].Color := RGB(60,70,80);
bt_noriah_auto.States[0].BorderColor := RGB(60,70,80);
bt_noriah_auto.States[0].FontStep := 3;
bt_noriah_auto.States[0].Caption := 'Авто';
manual_noriah.Visible := TRUE;
end;
{MANUAL ON}
if GetBit(PLC2_FIRST_PRG_status.Value, 11) then
begin //если бит равен 1
bt_noriah_on.States[0].Color := RGB(0,95,88);
bt_noriah_on.States[0].BorderColor := RGB(0,95,88);
bt_noriah_on.States[0].Caption := 'Включено';
end
else
begin //если бит равен 0
bt_noriah_on.States[0].Color := RGB(60,70,80);
bt_noriah_on.States[0].BorderColor := RGB(60,70,80);
bt_noriah_on.States[0].Caption := 'Включить';
end;
{MANUAL OFF}
if GetBit(PLC2_FIRST_PRG_status.Value, 12) then
begin //если бит равен 1
bt_noriah_off.States[0].Color := RGB(0,95,88);
bt_noriah_off.States[0].BorderColor := RGB(0,95,88);
bt_noriah_off.States[0].Caption := 'Выключено';
end
else
begin //если бит равен 0
bt_noriah_off.States[0].Color := RGB(60,70,80);
bt_noriah_off.States[0].BorderColor := RGB(60,70,80);
bt_noriah_off.States[0].Caption := 'Выключить';
end;
// ZKH1 ***********************************************************************
{AUTO}
if GetBit(PLC2_FIRST_PRG_status.Value, 13) then
begin // если бит равен 1
bt_ZKH1_auto.States[0].Color := RGB(0,95,88);
bt_ZKH1_auto.States[0].BorderColor := RGB(0,95,88);
bt_ZKH1_auto.States[0].FontStep := 0;
bt_ZKH1_auto.States[0].Caption := 'Авто вкл.';
img_ZKH1_manual.Visible := FALSE ;
end
else
begin // если бит равен 0
bt_ZKH1_auto.States[0].Color := RGB(60,70,80);
bt_ZKH1_auto.States[0].BorderColor := RGB(60,70,80);
bt_ZKH1_auto.States[0].FontStep := 3;
bt_ZKH1_auto.States[0].Caption := 'Авто';
img_ZKH1_manual.Visible := TRUE ;
end;
{MANUAL OPEN}
if GetBit(PLC2_FIRST_PRG_status.Value, 15) then
begin // если бит равен 1
bt_ZKH1_open.States[0].Color := RGB(0,95,88);
bt_ZKH1_open.States[0].BorderColor := RGB(0,95,88);
bt_ZKH1_open.States[0].Caption := 'Открыта';
end
else
begin // если бит равен 0
bt_ZKH1_open.States[0].Color := RGB(60,70,80);
bt_ZKH1_open.States[0].BorderColor := RGB(60,70,80);
bt_ZKH1_open.States[0].Caption := 'Открыть';
end;
{MANUAL CLOSE}
if GetBit(PLC2_FIRST_PRG_status.Value, 14) then
begin // если бит равен 1
bt_ZKH1_close.States[0].Color := RGB(0,95,88);
bt_ZKH1_close.States[0].BorderColor := RGB(0,95,88);
bt_ZKH1_close.States[0].Caption := 'Закрыта';
end
else
begin // если бит равен 0
bt_ZKH1_close.States[0].Color := RGB(60,70,80);
bt_ZKH1_close.States[0].BorderColor := RGB(60,70,80);
bt_ZKH1_close.States[0].Caption := 'Закрыть'
end;
// ZKH2 ***********************************************************************
{AUTO}
if GetBit(PLC2_FIRST_PRG_status.Value, 16) then
begin // если бит равен 1
bt_ZKH2_auto.States[0].Color := RGB(0,95,88);
bt_ZKH2_auto.States[0].BorderColor := RGB(0,95,88);
bt_ZKH2_auto.States[0].FontStep := 0;
bt_ZKH2_auto.States[0].Caption := 'Авто вкл.';
img_ZKH2_manual.Visible := FALSE ;
end
else
begin // если бит равен 0
bt_ZKH2_auto.States[0].Color := RGB(60,70,80);
bt_ZKH2_auto.States[0].BorderColor := RGB(60,70,80);
bt_ZKH2_auto.States[0].FontStep := 3;
bt_ZKH2_auto.States[0].Caption := 'Авто';
img_ZKH2_manual.Visible := TRUE ;
end;
{MANUAL OPEN}
if GetBit(PLC2_FIRST_PRG_status.Value, 18) then
begin // если бит равен 1
bt_ZKH2_open.States[0].Color := RGB(0,95,88);
bt_ZKH2_open.States[0].BorderColor := RGB(0,95,88);
bt_ZKH2_open.States[0].Caption := 'Открыта';
end
else
begin // если бит равен 0
bt_ZKH2_open.States[0].Color := RGB(60,70,80);
bt_ZKH2_open.States[0].BorderColor := RGB(60,70,80);
bt_ZKH2_open.States[0].Caption := 'Открыть';
end;
{MANUAL CLOSE}
if GetBit(PLC2_FIRST_PRG_status.Value, 17) then
begin // если бит равен 1
bt_ZKH2_close.States[0].Color := RGB(0,95,88);
bt_ZKH2_close.States[0].BorderColor := RGB(0,95,88);
bt_ZKH2_close.States[0].Caption := 'Закрыта';
end
else
begin // если бит равен 0
bt_ZKH2_close.States[0].Color := RGB(60,70,80);
bt_ZKH2_close.States[0].BorderColor := RGB(60,70,80);
bt_ZKH2_close.States[0].Caption := 'Закрыть'
end;
end.
begin
{SILOS 1}
if GetBit(PLC2_FIRST_PRG_status.Value, 0) then
begin //если бит равен 1
bt_silos1.States[0].Color := RGB(0,95,88);
bt_silos1.States[0].BorderColor := RGB(0,95,88);
end else
begin //если бит равен 0
bt_silos1.States[0].Color := RGB(60,60,60);
bt_silos1.States[0].BorderColor := RGB(60,60,60);
end;
{SILOS 2}
if GetBit(PLC2_FIRST_PRG_status.Value, 1) then
begin //если бит равен 1
bt_silos2.States[0].Color := RGB(0,95,88);
bt_silos2.States[0].BorderColor := RGB(0,95,88);
end else
begin //если бит равен 0
bt_silos2.States[0].Color := RGB(60,60,60);
bt_silos2.States[0].BorderColor := RGB(60,60,60);
end;
end.
begin
if not (Sender is TM_Button) then Exit;
with Sender as TM_Button do
if GetBit(AsInt, Tag) then
begin
States[0].Color := RGB(0,95,88);
States[0].BorderColor := RGB(0,95,88);
end else
begin
States[0].Color := RGB(60,60,60);
States[0].BorderColor := RGB(60,60,60);
end;
end.
Жаль что для разных кнопок выполняются уникальные изменения.....Да действительно жаль. Но как не крути в этом то и загвоздка :'(. Причем я предполагаю что со временем они("уникальные изменения") будут изменятся в зависимости от смены конфигурации маршрутов.
Этот вариант подойдет Вам? Если да, то мы можем написать как переделать на универсальные скрипты оставшуюся часть кода.Спасибо большое за помощь !!!!!!
Его можно заменить универсальным. Назовем его "uniButtonState" и напишемЯ не знаю по какой причине, но данный способ вообще отказывается работать с кнопкой(по крайней мере у меня).
begin
if not (Sender is TM_Button) then Exit;
with Sender as TM_Button do // далее работаем с Sender'ом как с кнопкой
if Assigned(VariableEx) then // если кнопка связана с до. переменной
if GetBit(VariableEx.AsInt, Tag) then // достаем из доп. переменной бит равный номеру тега кнопки
begin
States[0].Color := RGB(0,95,88);
States[0].BorderColor := RGB(0,95,88);
end else
begin
States[0].Color := RGB(60,60,60);
States[0].BorderColor := RGB(60,60,60);
end;
end.
необходимо сделать таймер наработки оборудования с тремя интервалами во времени. Например, таймер формирует отдельные отрезки времени с 8.00 до 12.00, далее с 12.00 до 16.00 и т.д. При этом компонент таблица регистрирует эти данные в соответствующие ячейки. В результате, чтобы можно было проследить сколько работало оборудование в трех сменах в течении суток, скажем, за месяц.Но ведь в этом случае все накопленные в таблице записи будут потеряны при первом перезапуске сервера скады или проекта. К тому же, чтобы они добавлялись в таблицу придется на каждый интервал времени выделить переменные и связать ячейки таблицы с этими переменными. Причем если планируется выводить наработку за месяц добавляя по 3 новых строки в день, то получается что нужно создать 90 строк в таблице и заполнить все строки переменными. Плюс остается проблема с потерей данных при перезапусках. Такая система совсем не подходит для нормальной работы.
Правильнее было бы выводить наработки получая их из архива. Т.е. включить у нужных тегов архивацию а затем выполнять выборку из БД за нужный интервал времени и подсчитывать время наработки. Тогда даже после перезапуска сервера все наработки сохранятся. Только сейчас все это нужно делать в запросе и готовых функций для вычисления наработок средствами скады нет. Постараемся их добавить в ближайших обновлениях.
Это баг или всё-таки я что-то делаю не так?Здравствуйте. Вы всё делаете правильно. Событие двойного клика перекрывается обработкой ввода. Исправим в будущих обновлениях.
Здравствуйте. Вы всё делаете правильно. Событие двойного клика перекрывается обработкой ввода. Исправим в будущих обновлениях.
В документации ничего не сказано про функцию Inc. То есть её как бы нет.Inc и Dec стандартные функции инкремента и декремента, которые можно заменить на: Value := Value + 1; и Value := Value - 1;
Но если эту функцию использовать в скрипте, то при компиляции не выдаётся никаких ошибок
То есть она как бы есть, хотя и не работает.Желательно тоже либо реализовать её, либо сделать так, чтобы компилятор сообщал об ошибке.Она есть и работает без ошибок, а если компилятор сообщает об ошибке, значит не просто так. Приведите пример кода, который у Вас не получается скомпилировать.
Она есть и работает без ошибок, а если компилятор сообщает об ошибке, значит не просто так. Приведите пример кода, который у Вас не получается скомпилировать.Я как раз и говорил, что ошибок компилятор не выдаёт, а функция не работает. Вот код
CurrCmdTime.Value := CurrCmdTime.Value + 1;
Inc(CurrCmdTime.Value);
А вот так: не работает.Правильно, и не должна. В функцию нужно передавать конкретную переменную, а не свойство (как CurrCmdTime.Value). Например так:
var
I: Integer;
begin
I := CurrCmdTime.Value;
Inc(I);
CurrCmdTime.Value := I;
end.
И всё-таки хотелось бы иметь возможность объявлять глобальные переменные и константы именно как обычные переменные/константы, а не как объекты.Такая возможность планируется, но не в ближайшее время.
А вообще в Паскале в функцию можно передавать в том числе и свойство объекта.В паскале, как и в делфи нельзя передавать свойства объекта в функции Inc, Dec. У нас в компиляторе то же самое, ведь наш компилятор по сути является компилятором Паскаль, с незначительными отличиями.
begin
if Sender is TM_Valve then // проверяем, что Sender это заслонка:
with Sender as TM_Valve do // приводим объект к типу TM_Valve:
case TM_Valve(Sender).AsInt of // если значение переменной равно:
-5..5 : // = -5 до 5 то считаем что заслонка закрыта
begin
Sender.Color := RGB(255, 128, 0); // меняем цвет на оранжевый
end;
6..95 : // = 6 до 94 то считаем что заслонка в промежуточном
begin
Sender.Color := RGB(255, 255, 0); // меняем цвет на желтый
end;
95..105 : // = 95 до 105 то считаем что заслонка открыта
begin
Sender.Color := RGB(0, 255, 0); // меняем цвет на зеленый
end;
end;
end.
В паскале, как и в делфи нельзя передавать свойства объекта в функции Inc, Dec. У нас в компиляторе то же самое, ведь наш компилятор по сути является компилятором Паскаль, с незначительными отличиями.Ну вот зацепили вы меня прям этим вопросом. Специально проверил:
type MyObj = object
Svoistvo : integer;
end;
var MyVar : MyObj;
begin
MyVar.Svoistvo := 0;
Inc(MyVar.Svoistvo);
end.
type MyObj = object
Svoistvo : integer;
end;
type MyRec = record
Svoistvo : integer;
end;
type
MyObj = class
private
function GetSvoistvo: Integer;
procedure SetSvoistvo(AValue: Integer);
public
property Svoistvo: integer read GetSvoistvo write SetSvoistvo;
end;
var
MyVar : MyObj;
// здесь нужно описать функцию GetSvoistvo и SetSvoistvo, мы пропустим описание
begin
MyVar.Svoistvo := 0;
Inc(MyVar.Svoistvo); // здесь будет ошибка компиляции
end.
Кстати, в вашей системе вот такая конструкция:Не компилируется, т.к. ключевое слово object использовалось для объявления классов в объектной модели Turbo Pascal. В современных компиляторах используется объектная модель Object Pascal и ключевое слово class (http://www.delphibasics.ru/Class.php). Подробнее про объектные модели здесь (https://ru.wikipedia.org/wiki/Object_Pascal). Поэтому объявление класса должно быть таким:Код: (delphi)вообще не компилируется - выдаётся ошибка. В то время как вот это:type MyObj = object
Svoistvo : integer;
end;
type
MyObj = class
Svoistvo: integer;
end;
begin
Text1.Text := GetUserName;
end.
Подскажите, как можно менять положение окна (x и y) из скрипта? Если это свойство "read only", то вопрос почему?В будущих обновлениях откроем данные свойства для записи.
1 begin
2 if BUT_bit2_3.Value
3 then
4 begin
5 CMD_RUN.Value:=SetBit(CMD_RUN.Value,2,true) ;
6 CMD_RUN.Value:=SetBit(CMD_RUN.Value,3,true) ;
7 end
8 else
9 begin
10 CMD_RUN.Value:=SetBit(CMD_RUN.Value,2,False) ;
11 CMD_RUN.Value:=SetBit(CMD_RUN.Value,3,False) ;
12 end
13 end.
Здравствуйте!
Ситуация следующая - есть некое командное слово, в нем, по нажатию кнопки, должны менять два бита. Пишу следующий код:Код1 begin
2 if BUT_bit2_3.Value
3 then
4 begin
5 CMD_RUN.Value:=SetBit(CMD_RUN.Value,2,true) ;
6 CMD_RUN.Value:=SetBit(CMD_RUN.Value,3,true) ;
7 end
8 else
9 begin
10 CMD_RUN.Value:=SetBit(CMD_RUN.Value,2,False) ;
11 CMD_RUN.Value:=SetBit(CMD_RUN.Value,3,False) ;
12 end
13 end.
При запуске клиента принципиально игнорируются строки 5 и 10.
Переменная BUT_bit2_3 - локальная и связана с кнопкой
Переменная CMD_RUN - подтягивается с OPC сервера и является командным словом.
Подскажите почему не отрабатывает составной оператор "begin...end"?
begin
if BUT_bit2_3.Value
then
begin
CMD_RUN.Value:=SetBit(CMD_RUN.Value,2,true) ;
CMD_RUN.Value:=SetBit(CMD_RUN.Value,3,true) ;
end
else
begin
CMD_RUN.Value:=SetBit(CMD_RUN.Value,2,False) ;
CMD_RUN.Value:=SetBit(CMD_RUN.Value,3,False) ;
end
end.
var
aValue: Integer;
begin
aValue := CMD_RUN.AsInt;
if BUT_bit2_3.Value then
begin
aValue := SetBit(aValue, 2, true);
aValue := SetBit(aValue, 3, true);
end else
begin
aValue := SetBit(aValue, 2, False);
aValue := SetBit(aValue, 3, False);
end;
CMD_RUN.Value := aValue;
end.
var
aValue: Integer;
begin
aValue := CMD_RUN.AsInt;
if
BUT_bit1_2.Value
then
begin
aValue := SetBit(aValue, 1, true);
aValue := SetBit(aValue, 2, true);
Button11.Enabled:=true;
image2.Color:=RGB(255,0,0);
Button11.Color:=RGB(255,0,0);
end
else
begin
aValue := SetBit(aValue, 1, False);
aValue := SetBit(aValue, 2, False);
Button11.Enabled:=false;
image2.Color:=RGB(0,100,0) ;
Button11.Color:=RGB(0,100,0) ;
end ;
CMD_RUN.Value := aValue;
end.
var
aValue: Integer;
begin
aValue := CMD_RUN.AsInt;
if BUT_bit1_2.Value then
begin
aValue := SetBit(aValue, 1, true);
aValue := SetBit(aValue, 2, true);
Button11.Enabled:=true;
image2.Color:=RGB(255,0,0);
Button11.States[0].Color:=RGB(255,0,0);
end
else
begin
aValue := SetBit(aValue, 1, False);
aValue := SetBit(aValue, 2, False);
Button11.Enabled:=false;
image2.Color:=RGB(0,100,0) ;
Button11.States[0].Color:=RGB(0,100,0) ;
end ;
CMD_RUN.Value := aValue;
end.
Teodor, тогда бы уж скрипт приложили бы) а то заинтриговали)Искать надо :) Если есть реальная необходимость, то найду и подчищу от лишнего.
begin
if Sender is TM_Button then
with Sender as TM_Button do
if VariableEx.Value < 3 then
VariableEx.Value:= VariableEx.Value + 1
else
VariableEx.Value:= 0;
end.
begin
if Sender is TM_Button then
with Sender as TM_Button do
case VariableEx.Value of
0: States[0].Color:=clGray;
1: States[0].Color:=clGreen;
2: States[0].Color:=clYellow;
3: States[0].Color:=clRed;
end;
end.
0: States[0].Color:=clGray;
0: begin
States[0].Color:=clGray;
.....
end;
begin
if Sender is TM_Button then
with Sender as TM_Button do
if VariableEx.Value = true then
States[1].Color:=clGreen
else
States[1].Color:=clRed;
end.
Кстати, я привязал к нефиксируемой кнопке скриптик, который при нажатии инкрементирует переменную на 1, и задает цвет (значок) зависимо от ее нового значения. При достижении граничного значения переменная скидывается в 0. Получилась кнопка с несколькими положениями :)Но ведь для этого можно сделать просто кнопку с фиксацией и множеством состояний, без скриптов, как на скрине во вложении. Такая кнопка есть в Демо-проекте на странице "Инструменты", блок 7.
var
aQuery: string;
begin
aQuery := 'SELECT * FROM `my_table`;';
myTable.RunSQL(aQuery, tsAll);
end.
var
aQuery: string;
begin
aQuery := 'SELECT * FROM `my_table`;';
myTable.RunSQL(aQuery, tsSaveFixRow);
end.
var
aQuery: string;
begin
aQuery := 'SELECT `col1` as `Столбец 1`, `col2` as `Столбец 2` FROM `my_table`;';
myTable.RunSQL(aQuery, tsAll);
end.
var
aQuery:string;
begin
aQuery := 'SELECT AR_NAM, AR_ARTICLECODE, DP_TOTALWEIGHT, DP_BATCHESSET'+
'FROM CFG_ARTICLE INNER JOIN CFG_DAYPROGRAM'+
'ON CFG_ARTICLE.AR_ARTICLE_ID = CFG_DAYPROGRAM.DP_PRODUCT_ID'+
'WHERE DP_STATUS_ID IN (1,2) AND DP_PRODUCT_ID <> 42'+
'ORDER BY DP_PRODUCTIONNUMBER;';
tblPlan.RunSQL(aQuery, tsSaveFixRow);
end.
var
aQuery:string;
begin
aQuery := 'SELECT AR_NAM, AR_ARTICLECODE, DP_TOTALWEIGHT, DP_BATCHESSET '+
'FROM CFG_ARTICLE INNER JOIN CFG_DAYPROGRAM '+
'ON CFG_ARTICLE.AR_ARTICLE_ID = CFG_DAYPROGRAM.DP_PRODUCT_ID '+
'WHERE DP_STATUS_ID IN (1,2) AND DP_PRODUCT_ID <> 42 '+
'ORDER BY DP_PRODUCTIONNUMBER;';
tblPlan.RunSQL(aQuery, tsSaveFixRow);
end.
Const
Cname : array[1..52] of string = ('T111', 'T112', 'T113', 'T114', 'T115', 'T116', 'T121', 'T122', 'T123', 'T124', 'T125', 'T126',
'T133', 'T134', 'T135', 'T136', 'T141', 'T142', 'T143', 'T144', 'T145', 'T151', 'T152', 'T153', 'T154', 'T155',
'T211', 'T212', 'T213', 'T214', 'T215', 'T216', 'T221', 'T222', 'T223', 'T224', 'T225', 'T226', 'T231', 'T232', 'T233', 'T234',
'T241', 'T242', 'T243', 'T244', 'T245', 'T251', 'T252', 'T253', 'T254', 'T255');
Var
Tmin, Tmax, Tavg, pp : Double;
i, j, n : Word;
Tname : string;
p: ^string;
begin
for i:=1 to 52 do
begin
n := 29;
Tname := Cname[i] + '_01';
p := @Tname;
pp := Double(p);
Tmin := pp;
Tmax := pp;
Tavg := pp;
for j := 2 to n do
begin
Tname := Silosname[i] + '_' + IntToStr(j);
p := @Tname;
pp := Double(p);
if pp < Tmin then Tmin := pp;
if pp > Tmax then Tmax := pp;
Tavg := Tavg + pp;
end;
Tavg := Tavg/n;
//как теперь присвоить Тегам T111_avg, T112_avg, ..., T255_avg значение переменной Tavg?
end;
end.
var
I, J: Integer;
aName, aNumber: string;
aVar: TM_Variable;
aVarMin, aVarMax, aVarAvg: TM_Variable;
aVal: Double;
aCount: Integer;
aMin, aMax, aSum: Double;
begin
{ проход по подвескам от 111 до 255 }
for I := 111 to 255 do
begin
{ формируем приставку к имени текущей подвески, например "T111_" }
aName := CharToStr('T') + IntToStr(I) + CharToStr('_');
{ сброс счетчика переменных }
aCount := 0;
{ проход по 29 датчикам текущей подвески }
for J := 1 to 29 do
begin
{ получаем переменную датчика, по имени, например "T111_01" }
if J < 10 then
aVar := GetVariableByName(aName + '0' + IntToStr(J))
else
aVar := GetVariableByName(aName + IntToStr(J));
{ если переменная с таким именем найдена }
if aVar <> nil then
begin
aVal := aVar.AsFloat; // получаем значение переменной приведённое к типу Double
Inc(aCount); // увеличиваем счетчик переменных
{ далее операции для вычисления мин./макс./среднего }
if aCount = 1 then
begin
aMin := aVal;
aMax := aVal;
aSum := aVal;
end else
begin
if aMin > aVal then aMin := aVal;
if aMax < aVal then aMax := aVal;
aSum := aSum + aVal;
end;
end;
end;
if aCount > 0 then
begin
{ ищем по имени переменные мин., макс. и среднего для текущей подвески }
aVarMin := GetVariableByName(aName + 'min');
aVarMax := GetVariableByName(aName + 'max');
aVarAvg := GetVariableByName(aName + 'avg');
{ записываем результаты вычислений в переменные мин., макс. и среднего }
if aVarMin <> nil then aVarMin.Value := aMin;
if aVarMax <> nil then aVarMax.Value := aMax;
if aVarAvg <> nil then aVarAvg.Value := aSum / aCount;
end;
end;
end.
select * from tableA;
SqlCommand command = new SqlCommand(queryString, connection);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
// Что-то делаем, например, заполняем записями ComboBox
}
begin
{ выполнять цикл пока не достигнем конца набора данных }
while not DataSet.EOF do
begin
// здесь работаем с текущей строкой
DataSet.Next; // переходим на следующую строку
end;
end.
var
I: Integer;
aStr: string;
begin
{ выполнять цикл пока не достигнем конца набора данных }
while not DataSet.EOF do
begin
{ проходим по каждой ячейке текущей строки }
for I := 0 to DataSet.FieldCount - 1 do
aStr := DataSet[I].AsStr; // получаем значение текущей ячейки переведённое в строку и записываем его в переменную aStr.
DataSet.Next; // переходим на следующую строку
end;
end.
var
aQuery: string;
begin
aQuery := 'SELECT * FROM `my_table`'; // формируем запрос к БД на выборку всех данных из таблицы `my_table`
Table1.RunSQL(aQuery, tsAll); // выполнить запрос и заполнить таблицу результатом выполнения
end.
Динамически, как я прочел, но не пробовал еще, можно добавлять строки.Да, можно, например так:
begin
ComboBox1.AddItem('Моя строка');
end.
могу ли я динамически строку создать и присвоить свойству "Значение", собственно, значение динамическиА вот динамически сменить значение не получится и строка будет принимать значение по-умолчанию равное номеру строки. В будущем постараемся разрешить смену значения.
begin
{ выполнять цикл пока не достигнем конца набора данных }
while not DataSet.EOF do
begin
// здесь работаем с текущей строкой
DataSet.Next; // переходим на следующую строку
end;
end.
Когда выходишь за пределы окна редактирования кода (набрал длинную строку кода), то не появляется полоса горизонтальной прокрутки. Или ее нет?Пока нет, но скоро будет.
Возможно ли добавить подсветку синтаксиса и сам синтаксис SQL?Здравствуйте. Для Simple-Scada SQL-запросы это просто текст, который никак не проверяется и не компилируется, а просто передается к БД. Поэтому подсветка SQL-кода не будет добавлена. Но вместо этого возможно добавим выполнение SQL-запросов из файла.
2. Очень нахватает TAB для выделенных строкДля смещения выделенного текста влево/вправо можно использовать комбинации клавиш Ctrl+Sift+U / Ctrl+Sift+I. Либо выполнять смещение через меню "Действия - Увеличить отступ", "Действия - Уменьшить отступ".
При вставке кода из другого редактора , типа Notepad++ , пропадаю TAB-ыИсправим.
Как в скрипте отследить нажатие мышкой на конкретной строке таблицы? Есть ли инструмент отслеживания курсора в таблице?Здравствуйте. Отследить курсор сейчас не получится. Это связано с тем, что на каждом клиенте может быть выбрана разная строка таблицы, а все скрипты выполняются на сервере и он не знает о выделении на клиентах. Но в ближайшем будущем данный вопрос планируется решить и у таблиц появится возможность выделения строк и получения номера выделенной строки.
а есть ли такая же функция для обращения к объектам программы по имени?Такой функции нет, с объектами можно работать только обращаясь по имени напрямую.
Есть функция GetVariableByName() для обращения к переменной по имени, а есть ли такая же функция для обращения к объектам программы по имени?Предложу движняк "через анал" для тех кому оооочень надо. Если вам нужно сменить свойство объекта EditXX:
Спасибо большое, для меня пока сложновато это все переварить... Буду копать дальше..Например, пишем по 2 температуры в 1 регистр(DW). Но с учетом того, что они могут изменится и снаружи, потому есть еще одна "лишняя" переменная. Процедурой т.к. там этих строк еще много есть, а процедура одна на всех.
procedure WriteTemp(VarOPC, VarIn, VarOut, Temp1, Temp2, Change :TM_Variable);
begin
if VarOPC.IsGoodQuality = true then begin
VarIn.Value := VarOPC.Value;
end;
if (VarIn.AsInt <> VarOut.AsInt ) and (Change.AsBool = false) then
begin
Temp1.Value:= (VarIn.Value and 8191);
Temp2.Value:= ((VarIn.Value shr 16) and 8191);
end;
if (VarIn.AsInt <> VarOut.AsInt ) and (Change.AsBool = True) then
VarOPC.Value := VarOut.Value;
if (VarIn.AsInt = VarOut.AsInt) and (Change.AsBool = True) then
Change.Value := false;
end;
begin
WriteTemp(TempSet2_1 ,SetTempBathIn1 ,SetTempBathOut1 ,SetBathTemp_1 ,SetTambourTemp_1,Change2_1 );
...........
begin
case Sender.Tag of
1: begin
SetTempBathOut1.Value := SetBathTemp_1.Value*10 + (SetTambourTemp_1.Value SHL 16)*10;
Change2_1.Value := True;
end;
.....
end;
end.
begin
ChangeХХ.Value := True;
end.
Спасибо Вам огромное, буду пробовать...Не надо!!!
if IntVar1.Value <> ExtVar.Value then
ExtVar1.Value := IntVar.Value;
IntVar.Value := 15; (* 0х00001111 или какие там у вас биты*)
Вопрос в следующем. При нажатии кнопок с некоторой задержкой все отрабатывает нормально, но стоит нажать 2 кнопки без паузы, отрабатывает только тот выход, кнопка которого была нажата последней. Кнопки привязаны к одному тегу, но через разные биты. В чем может быть проблема?В данной ситуации нет никакого блокирования переменных и дело только в работе с битами одной и той же переменной. Всё очень просто. Присвоение внешних переменных происходит не моментально, а с какой-то задержкой. Вот последовательность шагов:
begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
VariableEx.Value := Value;
end.
begin
if sysUpTime_0.IsGoodQuality = False then
AddMessage(Now, mkAlarm, 'Потеря связи с контроллером!', True, True);
end.
if sysUpTime_0.IsGoodQuality = TRUE then
Online.Value := TRUE
else
Online.Value := FALSE;
А можете теперь все то-же расписать, но с условием, что ExtVar может измениться "с той стороны"? Например есть еще один сервер работающий с тем-же регистром и нам надо переключить кнопки в актуальное положение? При том что щелкать по ним могут фактически одновременно. :-\Для этого достаточно написать обратный универсальный скрипт на событие OnDataChangeEx для кнопок:
begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
Value := VariableEx.Value;
end.
Но проблема в том, что не знаю к какому событию привязать данный скрипт, если привязать к посекундному, то каждую секунду будут появляться сообщения, что неправильно. К чему еще можно привязать данный скрипт?Для данной задачи действительно лучше всего делать проверку в секундном скрипте. А чтобы сообщение не выдавалось каждую секунду, можно создать отдельную внутреннюю переменную с типом данных Boolean и именем, например, vrShown. Тогда секундный скрипт будет таким:
begin
if sysUpTime_0.IsGoodQuality = False then
begin
if vrShown.Value = False then // если сообщение не было показано
begin
AddMessage(Now, mkAlarm, 'Потеря связи с контроллером!', True, True);
vrShown.Value := True; // отмечаем, что сообщение было показано
end;
end else // если качество хорошее, то
vrShown.Value := False; // разрешаем выдачу сообщения
end.
Добрый день, ув. форумчане. Помогите разобраться с таблицами. Есть переменная (счетчик), которая записывается в базу данных mysql. Стоит задача: вывести значение счетчика накопленное за час и общее количество за сутки. Т.е. в таблице 24 строки (для каждого часа) и столбец со значением счетчика. значение счетчика передается из контроллера в формате word.Т.е. Вам нужно взять данные счетчика из БД и на основе этих данных подсчитать накопленное за час и общее за сутки, а затем вывести полученный результат в таблицу?
И еще как организовать перезапись данных в таблице, значения, полученного в этом же часу но днем раньше???Можно просто связать ячейку таблицы с переменной. По изменению переменной, значение в ячейке будет обновляться.
begin
Button5.Value := 1;
delay := 1000;
Button6.Value := 1;
delay := 2000;
Button2.Value := 1;
delay := 3000;
Button1.Value := 1;
end.
if Started.Value = TRUE then
Counter.Value := Counter.Value+1
else
Counter.Value := -1;
case Counter.Value of
0:Button1.Value := 1;
1:Button2.Value := 1;
2:Button3.Value := 1;
...
x:ButtonХ.Value := 1;
y:Started.Value :=FALSE;
end;
..Прям сейчас так и попробую... А то мои методы явный "оверкил". ;D ;)
Для этого достаточно написать обратный универсальный скрипт на событие OnDataChangeEx для кнопок:
...
var
Mask:byte;
begin
if not (Sender is TM_Object) then Exit;
Mask:=7; //3bit
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
VariableEx.Value := VariableEx.Value and (Mask shl Tag xor 65535) or (Value shl tag);
end.
var
Mask:byte;
begin
if not (Sender is TM_Object) then Exit;
Mask:=7; //3bit
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
Value := (VariableEx.Value shr Tag) and Mask;
end.
Teodor, мы планируем в ближайшем будущем сделать сохранение присвоенного значения в оперативной памяти на определенное время. Тогда при быстром клике двух кнопок с разными битами одной переменной все присвоения будут корректными и данный вопрос не придется решить скриптами. Но разве у Вас именно такая проблема? Кажется Вы писали, что иногда присвоение у Вас просто не проходит с первого раза. Или тогда речь как раз шла о присвоении разных битов одной переменной?Мы ж не просто так у вас 4 лицензии купили :) Это уже третий проект в работе, на котором такой проблемы не стоит, а все летает четенько и шустренько. Зато тут свои "заморочки" в виде подготовки под ханивел с ДИКОЙ ценой точки, потому каждый бит на счету, а тема просто удачно подвернулась :)
Доброе утро, примерно так, только не получится связать ячейку с переменной, т.к. одна и та же переменная должна появляться в своей строке: примерДобрый день, ув. форумчане. Помогите разобраться с таблицами. Есть переменная (счетчик), которая записывается в базу данных mysql. Стоит задача: вывести значение счетчика накопленное за час и общее количество за сутки. Т.е. в таблице 24 строки (для каждого часа) и столбец со значением счетчика. значение счетчика передается из контроллера в формате word.Т.е. Вам нужно взять данные счетчика из БД и на основе этих данных подсчитать накопленное за час и общее за сутки, а затем вывести полученный результат в таблицу?И еще как организовать перезапись данных в таблице, значения, полученного в этом же часу но днем раньше???Можно просто связать ячейку таблицы с переменной. По изменению переменной, значение в ячейке будет обновляться.
Загрузить проект, пожалуйста, сделайте это.посмотрите. Я сделал отдельно кнопки, чтобы не поломать ваши и вывел счетчики, чтобы понятно было что происходит в скрипте. Цифры в Case, это секунды задержки включения кнопок после нажатия на АВТО.
Но на следующий день ячейка со значением переменной для 01:00 должна перезаписаться новым значениемТо есть у вас фиксированный размер таблицы? Тогда вам надо просто 24 переменных и забудьте о базах и таблицах. Выведите их в 24 поля. :)
тогда я не очень понимаю как это сделать без базыНо на следующий день ячейка со значением переменной для 01:00 должна перезаписаться новым значениемТо есть у вас фиксированный размер таблицы? Тогда вам надо просто 24 переменных и забудьте о базах и таблицах. Выведите их в 24 поля. :)
А можете теперь все то-же расписать, но с условием, что ExtVar может измениться "с той стороны"? Например есть еще один сервер работающий с тем-же регистром и нам надо переключить кнопки в актуальное положение? При том что щелкать по ним могут фактически одновременно. :-\Для этого достаточно написать обратный универсальный скрипт на событие OnDataChangeEx для кнопок:КодТогда виртуальная переменная будет всегда иметь актуальное значение и работа будет корректной.begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
Value := VariableEx.Value;
end.
Загрузить проект, пожалуйста, сделайте это.посмотрите. Я сделал отдельно кнопки, чтобы не поломать ваши и вывел счетчики, чтобы понятно было что происходит в скрипте. Цифры в Case, это секунды задержки включения кнопок после нажатия на АВТО.
вам надо будет поставить для вашей задержки:
0:
30:
60:
90:
Напомните почему так работает: VariableEx.Value := VariableEx.Value and ((Mask shl Tag) xor 65535) + (Value shl tag);Потому что сложение (http://www.reshinfo.com/primer_bin_slozenije1.php) (1 + 1 = 10 (единица переносится в следующий разряд)) и операция ИЛИ (1 + 1 = 1) это разные операции.
А так нет: VariableEx.Value := VariableEx.Value and ((Mask shl Tag) xor 65535) or (Value shl tag);
А можно все-же попросить... При создании переменной привязывать ее значение не к 0..100, а все-же к ее реальным рамкам? Ну или хоть дать возможность выбора шкалы по умолчанию в настройках?Здесь вся проблема в том, что реальные рамки у разных переменных - разные, поэтому и используется система шкал. Иначе можно было бы обойтись без неё. А вот выбор шкалы по-умолчанию вполне можем добавить. Кстати. вчера мы закончили работу над новой системой присвоений. Теперь ситуация с битами описанная в этом сообщении (http://simple-scada.com/forum/index.php?topic=145.msg3689#msg3689) не будет возникать и писать доп. скрипты не придется.
В этой ситуации, когда у них есть выходы Arduino?Все так-же. Только вместо внутренних переменных подставьте те что вам надо.
Где можно установить паузу между кнопок ?
Именно здесь 1 в первой части исключена т.к. она перед тем вычищена на с AND 0. Посему у нас тут 0+0=0or0 или 0+1=0or1 :) А вообще, проблема была со шкалой в которую вписывалось окончательное значение.Напомните почему так работает: VariableEx.Value := VariableEx.Value and ((Mask shl Tag) xor 65535) + (Value shl tag);Потому что сложение (http://www.reshinfo.com/primer_bin_slozenije1.php) (1 + 1 = 10 (единица переносится в следующий разряд)) и операция ИЛИ (1 + 1 = 1) это разные операции.
А так нет: VariableEx.Value := VariableEx.Value and ((Mask shl Tag) xor 65535) or (Value shl tag);
Добрый всем день. В продолжении темы. Столкнулся с очередной задачей. Вопрос практически такой же только с одной поправкой . Имеется не один модуль а например 10. и нужно нажатием кнопки активировать 1 выход каждого модуля (выхода вытаскиваю из ОРС сервера в виде word, где каждый бит отвечает за свой выход). Как решить такую задачу? Да выхода каждого модуля имеют свой тег соответственно : bout, bout_1, bout_2 и т.д.
Для решения этого вопроса можно сделать синхронизацию через внутреннюю переменную. Т.е., дано: наша внешняя переменная в которой нужно менять биты. Она называется vrMy. Мы создаем ещё одну внутреннюю переменную с именем vrSync. Связываем обе кнопки с переменной vrSync. Также у этих кнопок в качестве доп. переменной выбираем vrMy. Пишем следующий универсальный скрипт:Кодbegin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
VariableEx.Value := Value;
end.
Теперь ставим данный скрипт на событие OnDataChange у наших кнопок (причем этот скрипт подойдет для всех подобных кнопок в проекте). Готово. Теперь кнопки работают с внутренней переменной и присвоения выполняются моментально, поэтому проблемы с битами нет. При этом все изменения отправляются во внешнюю переменную.
Посмотри здесь: http://simple-scada.com/forum/index.php?topic=404.msg3739#newЕсли я все правильно понял, то в данном примере каждый элемент управления меняет бит своего тега. А мне нужно 1 кнопкой поменять 1 бит у каждой идентичной переменной
begin
myVar1.Value := SetBit(MyVar1.Value, 0, TRUE); // меняем нулевой бит в переменной myVar1
myVar2.Value := SetBit(MyVar2.Value, 0, TRUE); // меняем нулевой бит в переменной myVar2
end.
st-legal, а простое присвоение по событию OnClick в Вашем случае не подходит? Например:Ув. разработчики, в очередной раз огромное человеческое спасибо. Ответы всегда на поверхностиКодbegin
myVar1.Value := SetBit(MyVar1.Value, 0, TRUE); // меняем нулевой бит в переменной myVar1
myVar2.Value := SetBit(MyVar2.Value, 0, TRUE); // меняем нулевой бит в переменной myVar2
end.
Все проще... На всех нужных переменных ставите галку "Архивировать/по изменению" и читаете как работать с системой отчетов :)спасибо) а я тут что-то выдумываю
Как изменить скрипт, чтобы остановить кнопки ?Тут надо не менять, а еще один создавать (не на изменение, а тоже секундный). Со своим счетчиком и своим флагом. Я бы еще предложил блокировать вторую кнопку, если нажата первая. Поскольку эти 2 скрипта будут работать одновременно... и можно запутаться в том что мы включаем, а что выключаем.
Я добавил кнопку, чтобы остановить, но не работает ?
Все проще... На всех нужных переменных ставите галку "Архивировать/по изменению" и читаете как работать с системой отчетов :)Дело в том, что изменением у меня является срабатывание клапана (входа мультиплексора грубо говроря)..да такой подход позволит зафиксировать красивый график, но вот определить, с какого входа мультиплексора я получил значение и в какой момент времени,- не получится или я пока не догоняю как...
Мы видимо о разном... Я не про тренды, а про систему отчетов. Там можно выбрать период и данные, которые вам нужны за этот период. Хоть таблицей, хоть графиком, хоть одновременно.в принципе можно создать отчет и открывать его потом из скады...но хочется видеть в скаде сразу в виде таблицы, к примеру, или просто набора нескольких полей...
Точно о разном. ;)Мы видимо о разном... Я не про тренды, а про систему отчетов. Там можно выбрать период и данные, которые вам нужны за этот период. Хоть таблицей, хоть графиком, хоть одновременно.в принципе можно создать отчет и открывать его потом из скады...но хочется видеть в скаде сразу в виде таблицы, к примеру, или просто набора нескольких полей...
затем, выбрав определенный промежуток времени, читать их..и видеть уже - вещи разные... Тогда вам таки в БД надо писать и выводить в таблицу(заполняется выборкой). А в поля, если и выводить, то только актуальное значение, и последнее изменение, иначе заманаетесь.
Как изменить скрипт, чтобы остановить кнопки ?Тут надо не менять, а еще один создавать (не на изменение, а тоже секундный). Со своим счетчиком и своим флагом. Я бы еще предложил блокировать вторую кнопку, если нажата первая. Поскольку эти 2 скрипта будут работать одновременно... и можно запутаться в том что мы включаем, а что выключаем.
Я добавил кнопку, чтобы остановить, но не работает ?
Уважаемые специалисты! со скриптами туго совсем, только начал разбираться, натолкните на мысль, как организовать архив, где имеется :Обычно для подобных задач используются именно тренды. Не совсем понятно о каких разрывах на графике Вы говорите. Разрывов быть не должно, если клапан открыт, то график просто будет выше, если закрыт, то ниже. Может быть мы что-то недопоняли? Если нужно отобразить данные в таблице, то можно сделать запрос на выборку данных из БД в соответствующую таблицу, например:
Фактически мультиплексор из клапанов ( производится отбор проб газа из 9 точек на один газоанализатор ), управляет которым контроллер в ручном и автоматическом режиме,Соответственно имеются все переменные с этим связанные...нужно сохранять данные с каждого канала в БД, а затем, выбрав определенный промежуток времени, читать их.. Логично предположить, что сработал клапан 1 - записали значение на выходе в базу данных с меткой времени, сработал клапан 2 - записали значение и тд, а вот как выборку сделать не понятно...отсюда и тренды не подходят, т.к.,к примеру, клапан 3 в ручном режиме открыт может быть дольше, чем в автомате и на графике разрывы будут, что не наглядно и не очень удобно при просмотре..лучше как-то таблицей
var
aQuery: string;
begin
aQuery := 'SELECT * FROM `my_table`'; // формируем запрос к БД на выборку всех данных из таблицы `my_table`
Table1.RunSQL(aQuery, tsAll); // выполнить запрос и заполнить таблицу результатом выполнения
end.
Как реализовать проекты с помощью этих кнопок во время запуска?Если Вам нужно, чтобы скрипт выполнялся во время запуска проекта, то используйте скрипт с типом события "Полностью запущен".
Существует ли универсальный скрипт для последовательного включения объектов.Универсальный скрипт для последовательного включения не подойдёт.
А почему из скриптов нельзя менять свойство объекта Таблица "Title"? Выводится ошибка, что это свойство только для чтения.Откроем это свойство для записи в ближайшее время.
Как из скрипта удалить привязку переменной к объекту?Сейчас это невозможно. Можно только изменить ссылку на другую, например так:
begin
Text1.Variable := vrMy;
end.
Как из скрипта обраться к трендам объекта TrendViewer ("Временной тренд")?Тренды по окнам, показывать-скрывать окна?
Мне нужно настроить видимость определенных трендов.
Тренды по окнам, показывать-скрывать окна?У объекта типа TM_TimeTrendViewer есть свойство "Тренды", в котором имеется список трендов со своими свойствами, включая "Видимость". Мне нужно его менять в скрипте.
begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
Value := VariableEx.Value;
end.
begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
VariableEx.Value := Value;
end.
Simple-Scada, а ориентировочную дату внедрения данного функционала можете назвать?Точную дату трудно назвать. Скорее всего в ближайшие две недели.
Что происходит при старте в случае если ЕХ в ОРС не 0? Какова вероятность того, что она может быть перезаписана 0 из свежеинициализированной внутренней переменной?Точно! Есть вероятность перезаписи на ноль, причем довольно большая, т.к. виртуальная переменная скорее всего сделает OnChange первой. Поэтому здесь нужно игнорировать первый DataChange по внутренней переменной. Вот только как это сделать не используя доп. переменных.
Тогда еще вопрос. Качество OPC переменной. Когда оно установится в TRUE? Точнее, когда на ней пропадет nil? Это одновременные явления? Теоретически, хоть вирт. переменная и установится в 0 первой, а ОРС еще считать надо, то этот скрипт не выполнится, ибо ОРС все еще nil (или уже не nil, но еще не считана?) В общем если в условие поставить качество, а не иннициализацию, что либо изменится?Simple-Scada, а ориентировочную дату внедрения данного функционала можете назвать?Точную дату трудно назвать. Скорее всего в ближайшие две недели.Что происходит при старте в случае если ЕХ в ОРС не 0? Какова вероятность того, что она может быть перезаписана 0 из свежеинициализированной внутренней переменной?Точно! Есть вероятность перезаписи на ноль, причем довольно большая, т.к. виртуальная переменная скорее всего сделает OnChange первой. Поэтому здесь нужно игнорировать первый DataChange по внутренней переменной. Вот только как это сделать не используя доп. переменных.
begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
if VariableEx.IsGoodQuality then
VariableEx.Value := Value;
end.
А что будет если присвоить нечто некачественной ОРС переменной?Просто будет попытка выполнить присвоение в эту переменную, а пройдёт она, или нет, зависит от OPC-сервера.
Зачем проверять и качество и нил, она может быть качественной и нил одновременно?Проверка ""if Variable <> nil then" не имеет отношения к качеству переменной, или к её значению. Эта проверка позволяет убедиться в том, что разработчик проекта не забыл выбрать переменную у данного объекта. Допустим есть два поля Field1 и Field2. Поле Field1 связано с переменной A, а поле Field2 не связано с переменными. На событие OnDataChange у этих полей ставим такой универсальный скрипт:
begin
if not (Sender is TM_Object) then Exit;
TM_Object(Sender).Variable.Value := 10;
end.
begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_object do
if(Variable <> nil) and (VariableEx <> nil) then
VariableEx.Value:= Value;
end.
begin
if not (Sender is TM_object) then Exit;
with Sender as TM_object do
if (Variable <> nil) and (VariableEx <> nil) then
if Variable.IsGoodQuality then
Value:=VariableEx.Value;
end.
begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
Value := VariableEx.Value;
end.
и ещё кое-что. Имеется кнопка, которая активирует сразу все 4 выхода модуля. Так вот она сначала устанавливает все биты в 1 (слово =15), а затем сразу сбрасывает все выходы в ноль, кроме первого (слово =1).Конечно сбрасывает. Это же кнопка и в этом принцип её работы. Если вы создаёте кнопку и связываете её с переменной, то по нажатию на кнопку в переменную будет производиться запись единицы. У вас так и получается. Вы нажимаете кнопку, выполняется скрипт Value := 15 и сразу после этого кнопка выполняет своё предназначение и записывает в эту же переменную единицу. Если Вы хотите выполнять присвоения только скриптом, то нужно разорвать связь кнопки с любыми переменными.
begin
bout.Value := 15;
end.
begin
bout.Value := 0;
end.
Вы уверены, что проблема с быстрым присвоением битов в одну переменную существует после того как Вы убрали внутреннюю переменную? Мы сейчас делаем очень быстрые клики в Вашем проекте по кнопкам "Выход 1" .. "Выход 4" и присвоения всегда выполняются правильно. Может быть Вы во время кликов по кнопкам производите нажатие в зоне кнопки, а отпускаете мышь уже за её пределами? В таком случае присвоение не выполнится и может создастся впечатление, будто нажатие было, а присвоение не выполнилось.Итоговая картинка, сделал скрины. Нажимал все 4 кнопки последовательно, на последнем скрине итоговый результат. Не работает быстрое присвоение тегу..(((
А попробуй десяток полей сделать, и скриптик срабатывающий на изменение переменной:Приветствую Teodor, в том то и прикол, что с внутренней переменной одновременное присвоение проходит, а вот как заявлено что "улучшенная работа с внешними переменными (значение на присвоение временно хранится в оперативной памяти);" не работает. Это не особо критично, можно операторам объяснить, что необходимо дождаться пока загорится лампочка и затем нажимать другую кнопку. Но просто хотелось добиться результата при одновременном нажатии
Увеличивающий на 1 переменную счетчик и присваивающий в кейсе значения полям по очереди.
Тогда можно будет отследить все изменения переменной.
Еще лучше, если рядом будет еще колоночка полей для внутренней переменной прицепленной как Екс для кнопок. Тогда можно будет точно отследить сам факт нажатия.
begin
Log_Add(Sender.Name + '.Click: ' + bout.AsUTF8String);
end.
"Переменная должна быть объявлена в редакторе переменных." Переменную типа TM_Variable не объявишь в редакторе.Здесь говорится не о типе данных, а о самой переменной. Любая переменная, которая создается в редакторе переменных представляет собой объект типа TM_Variable. Тип данных у переменной должен быть как раз DateTime, чтобы на её основе можно было создать секундомер.
begin
TimerStart(vrTime, 0); // запускаем таймер с нуля
end.
И второй вопрос, а что за параметр AfromЭто время с которого секундомер начнёт отсчет. Т.е. можно стартовать не с нуля.
var {Заполняем таблицу tblOtchet "Отчет производства"}
aQuery:string;
aHome:string;
aEnd:string;
begin
aHome := QuotedStr(vOtchet_home.AsStr);
aEnd := QuotedStr(vOtchet_end.AsStr);
aQuery := 'SELECT'+
' AR_NAME'+
', AR_ARTICLECODE'+
', BD_STARTTIME'+
', BD_ENDTIME'+
' FROM CFG_ARTICLE, CFG_BATCHES'+
' WHERE (AR_ARTICLE_ID = BD_PRODUCT_ID'+
' AND BD_STARTTIME >= '+aHome+''+
' AND BD_STARTTIME <= '+aEnd+')'+
' ORDER BY BD_STARTTIME;';
tblOtchet.RunSQL(aQuery, tsSaveFixRow);
end.
var {Заполняем таблицу tblOtchet "Отчет производства"}
aQuery:string;
begin
aQuery := 'SELECT'+
' AR_NAME'+
', AR_ARTICLECODE'+
', BD_STARTTIME'+
', BD_ENDTIME'+
' FROM CFG_ARTICLE, CFG_BATCHES'+
' WHERE (AR_ARTICLE_ID = BD_PRODUCT_ID'+
' AND BD_STARTTIME >= '+vOtchet_home.AsSQLDateTime+''+
' AND BD_STARTTIME <= '+vOtchet_end.AsSQLDateTime+')'+
' ORDER BY BD_STARTTIME;';
tblOtchet.RunSQL(aQuery, tsSaveFixRow);
end.
function GetCell(ACol, ARow: Integer): TM_TableCell;
Описание: Возвращает ячейку на пересечении столбца с номером ACol и строки с номером
ARow. Возвращает nil, если такой ячейки не существует.
Есле в запросе присутствует дата которой нет в таблице, то выдает это:Здесь всё дело в формате даты/времени и код QuotedStr(vOtchet_home.AsStr); возвращает дату время в формате, который не подходит для запроса. Поэтому, как Вы и сделали, нужно использовать метод AsSQLDateTime.
Хотя в СУБД запрос проходит на ура и таблица формируется.
Подскажите пожалуйста как пользоваться этой функциейПосмотрите пример использования данной функции в руководстве по скриптам (https://simple-scada.com/help/script/tgetcell.html). Эта функция возвращает указанную ячейку. Ячейка имеет тип TM_TableCell, все свойства ячейки можно посмотреть здесь (https://simple-scada.com/help/script/tmtablecell.html) (доступны только для чтения). Если Вы не уверены, что ячейка существует в заданных "координатах", то для большей надежности кода можно сначала делать проверку на nil и только потом работать с ячейкой, например:
var
aCell: TM_TableCell;
begin
aCell := Table1.GetCell(1, 1); // получаем ячейку на пересечении столбца 1 и строки 1
if aCell <> nil then // если ячейка в столбце 1 и строке 1 существует, то
Text1.Text := aCell.Text; // извлекаем из ячейки текст
end.
Посмотрите пример использования данной функцииСпасибо огромное. К большому сожалению не знал что есть новая версия мана по скриптам.
var
aCell: TM_TableCell;
I: Integer;
begin
aCell := Table1.GetCell(1, 1); // получаем ячейку на пересечении столбца 1 и строки 1
if aCell <> nil then // если ячейка в столбце 1 и строке 1 существует, то
I := StrToInt(aCell.Text); // извлекаем из ячейки число
end.
var
aQuery: string;
begin
aQuery := 'SELECT * FROM `my_table`;'; // формируем запрос на выборку всех строк из таблицы `my_table`
RunSQL(aQuery, nil, 77); // запускаем запрос на выполнение с тегом 77
end.
var
aField: TM_DBField;
begin
if DataSet.IsEmpty then Exit; // прерываем выполнение если набор данных пуст
{ если это набор данных с тегом 77 }
if DataSet.Tag = 77 then
begin
{ ищем в наборе данных столбец с именем "val_date" }
aField := DataSet.FieldByName('val_date');
{ если столбец найден }
if aField <> nil then
vrDate.Value := aField.AsDateTime; // читаем дату из набора данных в vrDate
end;
end.
var
aCount : integer;
begin
aCount := aCount + 1;
Text1.Text := IntToStr(aCount);
end.
Если я правильно понимаю, как работают локальные переменные (каждый раз при запуске скрипта для локальной переменной ищется свободное место в памяти, выделяется и инициализируется по умолчанию нулем)Локальные переменные в Pascal, Delphi, C, C++, C# и т.п. языках не инициализируются компилятором и это забота программиста. При каждом очередном вызове скрипта переменной выделяется область памяти и её структура может быть любой (зависит от того, что в ней хранилось ранее), соответственно и значение переменных после выделения памяти может быть каким угодно. Компилятор в Simple-Scada работает аналогично, но при первой инициализации все-таки обнуляет переменную, а при последующих выделяет ту же самую область памяти, что и в первый раз. Поэтому в переменной значение сохраняется (не касается динамических типов данных, например строк). При этом мы рекомендовали бы работать с локальными переменными как и в других языках, т.е. сначала инициализировать, а затем использовать, чтобы значение переменной всегда было очевидным.
И второй вопрос. У меня вызывается процедура AddMessageToGroup(), далее по программе присваивается значение переменной связанной со списком сообщений. Но в журнал первой попадает сообщение из списка, а уже потом сообщение созданное процедурой, иногда и с задержкой в секунду. Почему так получается?Т.е. первое сообщение Вы создаёте процедурой AddMessageToGroup, а второе это обычное сообщение из меню "Проект -> Сообщения" которое генерирует скада при изменении переменной, мы правильно поняли? Причина, возможно, в последовательности записи в БД, хотя у нас такого не наблюдается и последовательность правильная. Вы уверены что событие OnDataChange переменной не наступает раньше чем Вы вызываете AddMessageToGroup?
При этом мы рекомендовали бы работать с локальными переменными как и в других языках, т.е. сначала инициализировать, а затем использовать, чтобы значение переменной всегда было очевидным.Ясно. Значит буду явно ициализировать.
Вы уверены что событие OnDataChange переменной не наступает раньше чем Вы вызываете AddMessageToGroup?Да. Это один длинный скрипт. Вначале идет условный вызов AddMessageToGroup, далее в операторе case идут варианты сообщений из списка. Скрипт выполняется один раз по-изменению.
далее в операторе case идут варианты сообщений из спискаА как именно выглядит этот case? Можете показать код скрипта?
А то я не знаю, как сворачивать текст на форуме.Кнопка с иконкой решетки в форме ответа.
Подвисание при выделении - выслал проект.Спасибо. Исправили. Скачать обновлённую версию можно по последней ссылке (2.2.3.0). Проект нужно пересохранить обновленной версией.
begin
AddMessage(IncMilliSecond(Now, -2), mkAlarm, 'Первое сообщение', True, True);
AddMessage(IncMilliSecond(Now, -1), mkAlarm, 'Второе сообщение', True, True);
AddMessage(Now, mkAlarm, 'Третье сообщение', True, True);
end.
Сразу вопрос: что делать если захочется использовать 64 бита переменную?)Сейчас GetBit работает с 32-битными переменными, поэтому нужно сначала получить последние 32 бита из 64-битной переменной и уже из них извлекать конкретные биты. Сделать это можно следующим образом:
var
aOriginal: Int64; // здесь будем хранить оригинальное значение 64-битной переменной
aLast32: Integer; // переменная для последних 32 битов из 64-битной переменной
begin
aOriginal := myVariable.AsInt64; // получаем оригинальное значение 64-битной переменной
aLast32 := aOriginal shr 32; // смещаем биты вправо на 32 бита, оставляя только старшие 32 бита
{ теперь 0 бит переменной aLast32 соответствует 33 биту оригинальной переменной,
а 31 бит переменной aLast32 соответствует 64 биту оригинальной переменной }
if GetBit(aLast32, 0) = TRUE then
Text1.Text := 'бит номер 33 равен 1';
end.
Они должны быть настроенны синхронно на сервере и на том клиенте, с которого запускаете. ИМХО!По расположению папок ограничений нет, синхронность не нужна, можно на каждом ПК выбирать своё расположение.
GEW, сегодня нашли возможную причину данной проблемы с отчетами. Завтра вечером опубликуем обновление с исправлением.я имею в виду, внутри проекта. А сам проект хай лежит себе где хочет... Или я могу создать проект, указать папку проекта, скопировать, создать в одном подпапку 1 и сохранять туда файл. Потом с другого клиента, указав куда сохранить у меня эта подпапка сама появится?ЦитироватьОни должны быть настроенны синхронно на сервере и на том клиенте, с которого запускаете. ИМХО!По расположению папок ограничений нет, синхронность не нужна, можно на каждом ПК выбирать своё расположение.
Столкнулся с проблемой при редактировании настроек аварийных и предупредительных пределов внешней переменной...Пришлите папку с проектом (\Simple-Scada\Projects\Имя проекта) на support@simple-scada.com и укажите в каких полях не обновляются границы переменной - проверим.
Ввиду того что в моём случае кнопки используются только для подачи управляющих команд "true" в контроллер, а сброс в "false" организует сам контроллер, постоянно пустует слот под главную переменную (действия прописываются в универсальный скрипт с VariableEx по OnClick). При этом довольно часто нужно знать (например для цветовой индикации кнопки) или писать состояние двух переменных. А в слот Variable вставить переменную уже нельзя.Если нужно, чтобы кнопка записывала в переменную только "true"(1), то у кнопки в свойстве "Переменная" необходимо указать переменную, в которую необходимо записывать "true", затем выбрать тип кнопки (https://simple-scada.com/help/manual/button.html) "С фиксацией" и оставить одно состояние(остальные удалить) со свойством "Значение" = 1. В таком случае, при нажатии на кнопку она всегда будет записывать в основную переменную "true". Для цветовой индикации кнопки по второй переменной используйте доп. переменную кнопки - пример можно найти здесь (https://simple-scada.com/help/script/buttondbl.html).
Думал ввести в свойство кнопки - подсказка - но для чтения оно из скрипта не доступно.Почему недоступно? Подсказку можно использовать для Вашей задачи. Например так:
var
s: string;
begin
s := Button1.Hint; // получаем текст подсказки кнопки Button1 в переменную s
end.
var
aVariable: TM_Variable;
begin
aVariable := GetVariableByName(Button1.Hint);
if aVariable <> nil then
aVariable.Value := 10;
end.
Искал по руководству скриптов в свойствах кнопки подсказку, наверно забыли добавить в руководство.Все компоненты в Simple-Scada 2 имеют свои уникальные свойства, а остальные свойства наследуют из базовых классов TM_Control (https://simple-scada.com/help/script/tm_control.html) и TM_Object (https://simple-scada.com/help/script/tm_object.html). Свойство Hint как раз является базовым и описано здесь (https://simple-scada.com/help/script/hint.html). Прочтите эту короткую статью (https://simple-scada.com/help/script/object-properties.html) в руководстве по скриптам, чтобы лучше понять организацию объектов в Simple-Scada 2.
Однако, ни в той ни в другой папке нет файла Client. exe.В архиве с любой версией имеются все необходимые для работы файлы, в том числе и клиент. Проверьте внимательно - может архив не до конца скачался или при распаковке Вы выделяете все содержимое кроме клиента. Либо файл клиента удаляется сторонним По (например антивирусом) после распаковки.
скада пишет об отсутствии активных БД, хотя я переустанавливал MySQL.В настройках Вашего проекта на вкладке "База данных" укажите имя БД, а также имя пользователя и пароль, которые были заданы при установке MySQL. Нажмите кнопку "Проверить" - если
var
DateStart, DateEnd: TDateTime;
begin
if bn_1.AsBool = true then //bn_1 - кнопка формирования выборки
begin
DateEnd:= End_.AsDateTime;
DateStart:= Start.AsDateTime;
end;
ArchiveTimeOn(MyVariable, MyVariableRes, DateStart, DateEnd);
end.
begin
End_.Value := Now;
Start.Value := Now;
end.
begin
if bn_1.AsBool = true then //bn_1 - кнопка формирования выборки
ArchiveTimeOn(MyVariable, MyVariableRes, Start.AsDateTime, End_.AsDateTime);
end.
Вероятно Вы создали внутренние переменные "End_" и "Start" с типом DateTime для указания периода. Внутренние переменные при запуске проекта инициализируются нулем, поэтому и выводится дата 30.12.1899 - она соответствует нулю. Вам нужно инициализировать переменные - для этого создайте скрипт "Запуск проекта" и напишите скрипт:
begin
Time_start.Value := (Now -1);
Time_end.Value := Now;
end.
begin
if btn_resultat.AsBool = true then
ArchiveTimeOn(Start_time, Time_resultat, Time_start.AsDateTime, Time_end.AsDateTime) ;
end.
Теперь понятно о чем Вы говорите. Вы имеете ввиду, что в календаре, в который выводится переменная наработки "Time_resultat" отображается дата 30.12.1899?
Дата, которую Вы видите - верна, т.к. ArchiveTimeOn возвращает результат в переменную типа DateTime (http://www.delphibasics.ru/TDateTime.php). Нулевой значение в этом формате соответствует дате-времени "30.12.1899 00:00:00" (т.е. отсчет у него ведётся от 30.12.1899) и его можно привести к совершенно любому формату простейшими операциями. Просто напишите в каком формате Вы хотите видеть время наработки и мы напишем Вам как его вывести.
var
aText: TM_Text;
aDay, aMonth, aYear: string;
function myFormat(AVal: Integer; aSymbols: Byte): string;
begin
Result := IntToStr(AVal);
while Length(Result) < aSymbols do
Result := '0' + Result;
end;
begin
if not (Sender is TM_Text) then Exit;
aText := TM_Text(Sender);
SetTimeFormat('hh:mm');
if YearOf(aText.AsDateTime) < 2016 then
begin
aDay := myFormat(DaysBetween(aText.AsDateTime, 0), 2) + '.';
aMonth := myFormat(MonthsBetween(aText.AsDateTime, 0), 2) + '.';
aYear := myFormat(YearsBetween(aText.AsDateTime, 0), 4) + ' ';
aText.Text := aDay + aMonth + aYear + TimeToStr(aText.AsDateTime);
end else
aText.Text := '00.00.0000 00:00';
end.
begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
begin
{ проверяем 5 и 6 биты доп. переменной }
if GetBit(VariableEx.AsInt, 6) then
Color := clCrimson // красный цвет
else
if GetBit(VariableEx.AsInt, 5) then
Color := clLightGreen // зелёный цвет
else
Color := clNone; // обычный цвет
{ проверяем 5 и 6 биты основной переменной }
if GetBit(Variable.AsInt, 6) then
FlashColor := clCrimson // мигание красным
else
if GetBit(Variable.AsInt, 5) then
FlashColor := clLightGreen // мигание зелёным
else
FlashColor := clNone; // без мигания
end;
end.
и надо ли присваивать переменным какую-либо шкалу?не обязательно, т.к. шкала используется только для записи значений в переменную и для отображения.
if (in1.Value and in2.Value) > 1 then
out.Value := 10
else out.Value := 0;
if (in1.Value > 1) and (in2.Value > 1) then
out.Value := 10
else out.Value := 0;
AutoCY, все правильно, а что именно Вы хотите сравнить? Это два разных условия. В первом случае для двух чисел выполняется логическое умножение (https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D1%8A%D1%8E%D0%BD%D0%BA%D1%86%D0%B8%D1%8F) и полученный результат сравнивается с единицей. Во втором случае два значения сравниваются с единицей и условие выполняется если оба больше 1. Вам скорее всего нужен второй способ.
0011 // число 3 в двоичном виде
0110 // число 6 в двоичном виде
=
0010 // результат. Равен 2.
Это я сделал в проекте. Но пользователи теперь хотят с помощью интервала времени увидеть во сколько оборудование начало работать, когда закончило и еще при этом некоторые технологические параметры (среднее арифметическое).Обычно именно эти задачи решают тренды. Оператор переходит в меню просмотра трендов (https://simple-scada.com/help/manual/client-trends-view.html), задаёт интересующий его интервал времени и видит когда оборудование начало/закончило работать, а при наведении мыши на легенду тренда видит мин., макс., и среднее по тренду за просматриваемый интервал времени. Архивные процедуры (https://simple-scada.com/help/script/work-with-archive.html) не подходят для решения такой задачи, т.к. они работают с данными на всём интервале, а в Вашем случае требуется взять весь интервал заданный пользователем, найти на нём другие "подинтервалы" (например когда оборудование было включено) и для каждого "подинтервала" выполнить действие (например получить время начала/конца работы). Т.е. результат может быть не один, а несколько (для каждого "подинтервала").
Хочу получить СМС и отобразить его текст в сообщении.Здравствуйте. Да, Вы всё поняли правильно. Достаточно создать внутреннюю переменную с типом Integer и именем например vrTimerSMS. Далее создается новый скрипт с типом события "Прошла секунда" и следующим кодом:
Нужно по таймеру вызывать процедуру GetSMS или скада сама опрашивает модем ?
const
INTERVAL = 60; // интервал таймера 60 сек.
begin
vrTimerSMS.Value := vrTimerSMS.Value + 1;
if vrTimerSMS.AsInt >= 60 then
begin
GetSMS(TRUE, FALSE); // запрос на чтение и удаление SMS сообщений из памяти SIM-карты
vrTimerSMS.Value := 0;
end;
end.
begin
Text1.Text := Text1.Text + smsTime + ' | ' + smsPhone + ' | ' + smsMsg + Chr(10);
end.
Можно включить архивацию у переменной "Пуск/Стоп" оборудования, тогда можно скадой по запросу пользователя делать выборку из БД за любые интервалы времени и выводить результат в таблицу на мнемосхеме. Т.е. получится таблица с перечислением моментов пуска/остановки оборудования за заданный оператором интервал. Но такой способ не позволяет вывести доп. данные (например средн. арифметическое).
begin
if in_1.Value > 1 then
out_1.Value := 10
else out_1.Value := 0;
ArchiveCountOn(out_1, Rez_n_1, Start_time_1.AsDateTime, End_time_1.AsDateTime);
ArchiveCountOff(out_1, Rez_n_2, Start_time_1.AsDateTime, End_time_1.AsDateTime);
end.
var
time_h_1, time_m_1, time_h_2, time_m_2 : Integer;
begin
// Подсчет количества включений
//Первое включение
if (Rez_n_1.Value = 1) and (m_1.Value = 0) then
begin
time_h_1 := HourOf(Now);
time_m_1 := MinuteOf(Now);
t_1.Value := (IntToStr(time_h_1) + ':' + IntToStr(time_m_1)); //Время начала включения
m_1.Value := 1;
end;
//Первое выключение
if (Rez_n_2.Value = 1) and (m_2.Value = 0) then
begin
time_h_2 := HourOf(Now);
time_m_2 := MinuteOf(Now);
t_2.Value := (IntToStr(time_h_1) + ':' + IntToStr(time_m_1)); //Время выключения
m_2.Value := 1;
end;
end.
Вопрос такой: возможно ли сделать нечто подобное, как на рисунке?Да, можно получить очень близкий результат. И проще всего это сделать используя систему отчетов. Достаточно правильно настроить архивацию в БД времени старта и остановки оборудования. Кроме того система отчетов позволяет строить Master-Detail отчеты, которые близки к тому, о чем Вы писали изначально. В таком отчете можно вывести данные каждого подинтервала и доп. расчеты для него (среднее, мин., макс., кол-во, сумму). Для примера мы взяли процесс который периодически запускается и останавливается и построили для него Master-Detail отчет. В отчет выводится отдельно период каждого цикла работы и расчетные данные для каждого цикла (см. скрин во вложении). Если вариант с отчетами подходит, то можем в ближайшее время написать отдельную статью с примером создания такого отчета.
А вот если в СМС присылать какой-либо код, вида "ABS.123.1" , что означает "Цех ABC, механизм 123 занял положение 1" - как проще разложить такое по переменным ?Разложить можно используя например такой скрипт:
var
aWord: string;
I, aNumber, aLength: Integer;
procedure OnWord;
begin
case aNumber of
1: Text1.Text := aWord; // первое слово записываем в Text1
2: Text2.Text := aWord; // второе слово записываем в Text2
3: Text3.Text := aWord; // третье слово записываем в Text3
end;
end;
begin
aWord := '';
aNumber := 1;
aLength := Length(smsMsg); // получаем длину текста сообщения
if aLength > 0 then
for I := 1 to aLength do // проходим по символам сообщения
begin
if smsMsg[I] <> '.' then
aWord := aWord + smsMsg[I];
if (smsMsg[I] = '.') or (I = aLength) then
begin
OnWord;
aWord := '';
Inc(aNumber);
end;
end;
end.
Если вариант с отчетами подходит, то можем в ближайшее время написать отдельную статью с примером создания такого отчета.
Master-Detail - это новый параметр ОТЧЕТА? Есть ли он в описании руководства?Это отчет, в котором одному выводимому значению из Master источника данных, соответствует какое-то количество значений из Detail источника данных. В руководстве его нет, нам нужно будет написать статью с примером создания такого отчета. Кроме того нужно будет внести мелкие изменения в редактор отчетов, поэтому потребуется время. Внесём изменения и опубликуем статью к концу недели.
Внесём изменения и опубликуем статью к концу недели.
begin
GoToNextPageClient(GetClientName); // переход на следующую страницу на клиенте, который вызвал скрипт
end.
begin
GoToPrevPageClient(GetClientName); // переход на предыдущую страницу на клиенте, который вызвал скрипт
end.
Tab перемещает между полями ввода,но не очень явно видно какое поле выбрано, а тем более через несколько секунд выделение пропадает.Добавим опцию постоянного выделения в следующем обновлении.
Сочетание клавиш CTRL + и CTRL - не перемещает между страницамиЕсли панель страниц включена, то для перехода по страницам используются сочетания клавиш Ctrl и стрелок влево/вправо:
Хотелось иметь возможность обработки нажатых клавиш.Такой возможности нет и пока не планируется.
if (aVariable <> nil) or (aVariable.IsGoodQuality = false) then...
if(aVariable <> nil) then ...
if(aVariable.IsGoodQuality = true) then ...
else
else
Думаю следует в процедуру проверки качества включить проверку на nil в автоматическом режиме.Это невозможно. Вы в коде работаете с указателем (aVariable), он может быть либо nil (это указатель на первый байт в памяти), либо указывать на какие-то реальные данные. В приведенном коде Вы обращаетесь к указателю и компилятор не может просто взять и не выполнить это обращение. Поэтому Вам нужно либо не обращаться к указателю вовсе, либо обращаться предварительно проверяя его на равенство nil.
var
aVar: TM_Variable;
begin
aVar := GetVariableByName('myVariable'); // ищем переменную
if aVar <> nil then // если переменная найдена
aVar.Value := 1; // работаем с ней
end.
var
q:single;
i:int64;
begin
i:=Field2.AsInt64 mod 222;
q:=w/10;
Field2.Text := FloatToStr(q,1);
end.
var
q: Single;
i: Int64;
begin
i := Text1.AsInt64 mod 222;
q := i / 10;
Text1.Text := FloatToStr(q, 1);
end.
var
q: Single;
i: Int64;
aText: TM_Text;
begin
if not (Sender is TM_Text) then Exit;
aText := Sender as TM_Text;
i := aText.AsInt64 mod 222;
q := i / 10;
aText.Text := FloatToStr(q, 1);
end.
begin
if Sender is TM_Image then // проверяем, что Sender это Изображение
with Sender as TM_Image do // приводим Sender к типу "TM_Image"
if AsInt = 1 then // если значение переменной объекта равно 1, то
Angle := 90 // изменить угол на 90
else // иначе
Angle := 45; // изменить угол на 45
end.
Это отчет, в котором одному выводимому значению из Master источника данных, соответствует какое-то количество значений из Detail источника данных. В руководстве его нет, нам нужно будет написать статью с примером создания такого отчета. Кроме того нужно будет внести мелкие изменения в редактор отчетов, поэтому потребуется время. Внесём изменения и опубликуем статью к концу недели.
Здравствуйте, получается ли у вас с дополнением в редакторе отчета?Здравствуйте. Да, получается, версию скады уже обновили. На выходных опубликуем статью.
можно ли скриптами (или может еще как) сделать так, чтобы при аварии помимо сообщения еще вверху мигала бы страница на которой это произошло. если да, поделитесь примером.Это можно сделать только создав прямоугольник с помощью компонента "Фигура (https://simple-scada.com/help/manual/figure.html)" и разместив его поверх вкладки страницы. Затем, создать скрипт с типом события (https://simple-scada.com/help/script/event-types.html) "Изменились переменные"(пример создания такого скрипта можно найти здесь (https://simple-scada.com/help/script/changemulvar.html)). И в этом скрипте описать условия, по которым должен мигать наш прямоугольник, например:
begin
if (aVar1.AsInt = 1) or (aVar2.AsInt = 2) or (aVar3.AsInt = 3) then
Shape1.FlashColor := clRed // включить мигание красным цветом
else // иначе
Shape1.FlashColor := clNone; // отключить мигание
end.
Да, получается, версию скады уже обновили. На выходных опубликуем статью.
понял. спасибо. а не планируется ли добавить такую "плюшку" штатноМы планируем добавить возможность привязки сообщений к объектам в будущем, но это будет не в ближайшее время. Сейчас основной задачей является разработка web-клиента для скады.
попробовал сделать как вы посоветовали. но для прямоугольника не имеет значения находится он на странице или выше на вкладке, его видно только если открыта эта страница, соответственно смысл теряется. мигание нужно видеть не зависимо от какая страница открыта.. может еще что то посоветуете?Да верно, т.к. объекты принадлежат страницам/подстраницам на которых созданы, поэтому нужно создавать прямоугольники на каждой странице/подстранице и в скрипте включать/выключать мигание сразу нескольких прямоугольников. Сейчас возможно только такое решение.
Здравствуйте.Использую для этого nircmd.
Отдельной процедуры для закрытия приложения нет. Можно создать .bat-файл который выполняет закрытие приложения и запускать его из скады через RunApplication.
begin
RunApplication(GetClientName, 'nircmd.exe' , 'killprocess notepad.exe');
end.
Возникает проблема когда скролл улетает вниз (вручную пролистывается), а на следующем запросе выдаётся, например 3 строки и их из-за скрола внизу не видно. Существует ли возможность скриптом вернуть скролл на первую строку (MiveFirst)?Т.е. обновляются данные в таблице через метод Table.RunSQL(...), верно? Скролл должен автоматически перемещаться в самую верхнюю позицию после выполнения метода Table.RunSQL. У Вас этого не происходит?
При работе с таблицей приходится использовать переменные. Существует ли возможность работы с ячейками из скриптов без подвязки переменой?Сейчас менять значение в ячейках можно только через переменные. Считывать текст из ячейки можно следующим образом:
var
aText: UTF8String;
begin
aText := Table1.GetCell(0, 0).Text; // получить текст из первой ячейки первого столбца
end.
begin
MinimizeApplicationClient(GetClientName);
end.
Добрый день!Cкрипт с условием "Прошла секунда":
Прошу Вас помочь в написании простого скрипта. Имеется связка ПЛК(сервер)-GPRS модем- на объекте ,GPRS модем-Lectus-SimpleScada2 в диспетчерской. Задача - отследить разрыв связи на стороне ПЛК. То есть, необходимо чтобы СКАДА записывала в ПЛК периодически меняющуюся переменную типа boolean. На стороне ПЛК организовать проверку не составляет проблем, а вот со скриптами пока не разберусь... Спасибо!!
begin
Tag.Value := InverseBit(Tag.Value, 0);
end.
Room measure temperature(high byte), data range :0-199 / Room set temperature (low byte), data range :0-199
begin
MyVariable.Value := 10; // записываем значение 10 в переменную MyVariable
end.
Здравствуйте.Это Вы мне написали? Если да то спасибо, но моя проблема лежит в другой плоскости. Основы я знаю, есть рабочие проекты. Я не пойму как с помощью скриптов получить high byte и low byte из переменной с OPC-сервера...
Импортируйте переменную с OPC-сервера (https://simple-scada.com/help/manual/variable-import-opc.html) в проект и затем используйте. Например разместите в проекте Поле и свяжите его с переменной. После запуска проекта Вы увидите в поле значение переменной. Также Вы сможете обращаться к переменной через скрипты, например:Кодbegin
MyVariable.Value := 10; // записываем значение 10 в переменную MyVariable
end.
Room_measure_temperature.Value := Temperature.AsInt shr 8;
Room_set_temperature.Value := Temperature.AsInt and $FF;
aText.Text := FloatToStr((aText.AsInt shr 8), 1);
11550 = 0010 1101 0001 1110
старший байт = 0010 1101 = 45
младший байт = 0001 1110 = 30
Standard Modbus RTU protocol, serial RS485 with two lines, communicate technical parameter:
Baud rate 9600, 8 data bits, no parity data check , 2 stop bits Please keep Register command 03,
write single register command 06.
1 .Key-lock, power on off, register address 00:
Key-lock (high byte), 0: unlock, 1:lock;
Power switch: (low byte), 0: power off, 1: power on.
2.Room measure temperature , room set temperature, register address 01:
Room measure temperature(high byte), data range :0-199, relative temperature : 0-99.5, at this byte “write” No valid;
Room set temperature (low byte), data range :0-199, relative temperature: 0-99.5,
var
aHiWord, aLowWord: Word;
begin
aHiWord := 30*2; // т1
aLowWord := 20*2; // т2
varDWord.Value := LongWord(aHiWord shl 8) or aLowWord;
begin
if btnTEST.AsInt = 1 then
test1.Value:=true
else
test1.Value:=false
end.
begin
if line3.Y<900 then
line3.Y:=line3.Y+80
else
line3.Y:=0;
end.
var
{Заполняем таблицу tblSpisok_TH03 "Перечень компонентов ручного ввода"}
aQuery:string;
begin
aQuery := 'SELECT AR.AR_ARTICLE_ID AS AR_ARTICLE_ID '+
',AR.AR_NAME AS AR_NAME'+
',SUM(TMP.TDB_DOSINGWEIGHT) AS TDB_DOSINGWEIGHT'+
',CASE TMP.TDB_ACTIVE WHEN 1 THEN ''Добавлен'' ELSE '''' end AS ACTIVE '+
'FROM TMP_DOSINGBATCHES TMP '+
'INNER JOIN CFG_BATCHES BD ON BD.BD_PLC_BATCHNUMBER = TMP.TDB_PLC_BATCHNUMBER '+
'INNER JOIN CFG_RAWMATERIAL RM ON RM.RM_RAWMAT_ID = TMP.TDB_RAWMAT_ID '+
'INNER JOIN CFG_ARTICLE AR ON AR.AR_ARTICLE_ID = RM.RM_RAWMAT_ID '+
'WHERE BD_BATCHNUMBER = (SELECT MAX(BD_BATCHNUMBER) '+
'FROM CFG_BATCHES '+
'INNER JOIN TMP_DOSINGBATCHES ON TDB_PLC_BATCHNUMBER = BD_PLC_BATCHNUMBER '+
'INNER JOIN CFG_WEIGHER ON WG_WEIGHER_ID = TDB_WEIGHER_ID '+
'INNER JOIN CFG_UNITS ON UN_BATCHNUMBER = BD_BATCHNUMBER '+
'INNER JOIN CFG_UNIT_DEVICES ON UN_UNIT_ID = UDS_UNIT_ID AND WG_DEVICE_ID = UDS_DEVICE_ID '+
'AND UN_BATCHNUMBER > 1 '+
'AND TDB_WEIGHER_ID = 6) '+
'AND TMP.TDB_WEIGHER_ID = 6 '+
'AND TMP.TDB_MODIFYTYPE <> ''D'' '+
'GROUP BY TMP.TDB_PLC_BATCHNUMBER'+
',RTRIM(AR.AR_ARTICLECODE)'+
',AR.AR_NAME '+
',AR.AR_ARTICLE_ID '+
',TMP.TDB_WEIGHER_ID '+
',TMP.TDB_ACTIVE '+
'ORDER BY TMP.TDB_PLC_BATCHNUMBER, MIN(TDB_SEQUENCENUMBER) ';
tblSpisok_TH03.RunSQL(aQuery, tsSaveFixRow);
end.
Подскажите пожалуйста, как в редакторе скриптов отменить изменения в коде (обычный Ctrl+Z не работает).Комбинация Ctrl+Z отвечает за отмену действия. Может быть Вы используете старую версию скады? (отмена действий доступна с версии 2.2.2.0 и выше).
Расскажите пожалуйста более подробно, как (последовательно/параллельно) исполняются скрипты.Скрипты выполняются последовательно в том порядке, в котором были вызваны. Запросы к БД ставятся в очередь, а затем выполняются в отдельных потоках параллельно. После выполнения любого из запросов вызываются скрипты с типом "Выполнен SQL-запрос". При возникновении ошибки во время выполнения SQL-запроса вызываются скрипты с типом "Ошибка SQL-запроса".
Может ли быть ситуация, когда один из этих запросов не обработается по причине занятости СУБД обработкой предидущего запроса?Нет, иначе работа с СУБД была бы крайне ненадежной.
Когда возникает событие "выполнен SQL-запрос" в этой ситуации? Если после выполнения каждого, тода получается, что это событие может прерывать выполнение цикла в текущем скрипте, что врядли. Или же это событие будет наступать только после отправки последнего запроса из цикла и завершения выполнения текущего скрипта.В этом случае сначала пройдет весь цикл, запросы добавятся в очередь и скрипт завершится (это займёт миллисекунды). Далее скада будет брать сразу несколько запросов и выполнять их в отдельных потоках параллельно. Тот, который выполнится первым вызовет событие "выполнен SQL-запрос" первым. Выполнится вторым, вызовет "выполнен SQL-запрос" вторым и т.д.
А есть какая-нибудь команда для открытия(показа) окна?https://simple-scada.com/help/script/tmwindow.html
Например, хочу сделать, чтоб при наступлении события ошибки исполнения определенных SQL-запросов скрипт открывал окно с текстом ошибки.
Возможно ли в скрипте получить текущее количество неподтвержденных или активных сообщений?Нет. Пожалуйста опишите для чего это может потребоваться?
Например, хочу сделать, чтоб при наступлении события ошибки исполнения определенных SQL-запросов скрипт открывал окно с текстом ошибки.Victor_P., для этой цели лучше подойдут процедуры ShowMessageClient (https://simple-scada.com/help/script/showmessageclient.html)и ShowMessageAll (https://simple-scada.com/help/script/showmessageall.html).
Нет. Пожалуйста опишите для чего это может потребоваться?После подтверждения сообщения оператор может забыть(или не передать по смене), что событие ещё активно.
Для этого можно использовать компонент "Список сообщений (https://simple-scada.com/help/manual/message-viewer.html)", выбрав в свойстве "Группа" пункт "Активные сообщения", тогда в списке будут отображаться только активные сообщения.Это понятно. Но хочется сделать как на картинке, компактную кнопку, появляющуюся, если есть активные сообщения.
if not GetBit(Val.AsInt, Sender.Tag) then Val.Value := 0;
Val.Value := Sender.Tag;
но в этом случае при нажатии одной кнопки остальные становились не активными (прикрепил скриншоты).Правильно, потому что у этих кнопок нет состояния для текущего значения переменной. Т.е. кнопка находится в одном из недопустимых состояний, поэтому она блокируется.
Пример ваш не открылся, пишет сделан в версии 2.2.7.1, а у меня 2.2.7.0Скачать последнюю версию можно с сайта. Если у Вас лицензионная версия, то напишите на support@simple-scada.com.
Я так понял, вы предлагаете вообще не привязывать переменную к кнопкам, а значение присваивать скриптом.Нет, все делается как обычно, только у кнопок оставляется только одно состояние. Проще понять если посмотреть пример, он очень простой.
А вот сам пример ведет себя не адекватно (возможно только у меня на машине),- при выборе в комбобоксе все норм,Это из-за того что мы сохранили его своей отладочной версией редактора. Откройте проект и пересохраните его в Редакторе на Вашем ПК. Тогда он должен работать корректно.
а вот при клике по кнопке вываливается с ошибкой, при этом при попытке нажать PrintScreen уводит винду в BSOD.
А можно ли на один объект с одной булевой переменной написать 2 или более скрипта ? Например - при переменной = 1 начинает моргать и воспроизводит звуковой файл . Или лучше от доп. переменной ?Можно, но для описанной Вами задачи это не имеет смысла. Если нужно, чтобы объект при изменении значения связанной с ним переменной начинал мигать цветом, то Вам необходимо написать скрипт на событие (https://simple-scada.com/help/script/event-types.html) OnDataChange объекта - пример скрипта мигания цветом можно найти здесь (https://simple-scada.com/help/script/flashing.html). Воспроизвести/остановить воспроизведение звукового файла можно при помощи процедур для работы со звуком (https://simple-scada.com/help/script/sound.html).
И ещё . Есть где скачать изображения и анимации ?В этой (https://simple-scada.com/forum/index.php?topic=153.0) и этой (https://simple-scada.com/forum/index.php?topic=12.0) темах можно найти изображения и анимацию, которыми поделились пользователи форума.
А можно ли сделать звуковую сигнализацию
begin
if MyVar.IsGoodQuality = False then // Где MyVar Ваша переменная на ПЛК
AddMessage(Now, mkAlarm, 'Нет связи с ПЛК!', True, True);
end.
var
aImg: TM_Image;
begin
if not (Sender is TM_Image) then Exit;
aImg := Sender as TM_Image;
if aImg.AsBool then aImg.AnimSpeed := 12
else
begin
aImg.AnimSpeed := 0;
aImg.Frame := 4;
end;
end.
PlayUserSound('KHP','du1_0.ogg',false);
Я не знал что в фоне звук не воспроизводитсяА вот это кстати плохо, что в фоне не воспроизводится
Victor_P., а может оператор отчёт в Экселе просматривает, звук в фоне однозначно должен быть.поддерживаю. Либо разработчику должен быть предоставлен выбор: воспроизводить звук в фоне или нет.
Помогите с кодом.Создаем скрипт с запуском по изменению переменной. Добавляем в него все необходимые переменные, как описано здесь (https://simple-scada.com/help/script/changemulvar.html).
{ Variable - это та переменная, которая изменилась }
if Variable.AsInt = -1 then
begin
// ...
end;
Создаем скрипт с запуском по изменению переменной.Это не совсем то, что нужно. Например:
begin
if Variable.AsInt = -1 then
text1.FontColor := clRed;
end.
begin
if Variable.AsInt = -1 then
text1.FontColor := clRed
else
text1.FontColor := clGreen;
end.
begin
if (aVar1 = -1) or (aVar2 = -1) or (aVar3 = -1) or (aVar4 = -1) or (aVar5 = -1) or (aVar6 = -1) then
Text1.fontColor := clRed
else Text1.FontColor := clBlack;
end;
Сделайте все то-же самое, но в скрипте по изменению переменной.Именно, а секундный скрипт будет впустую расходовать ресурсы.
Пишу дипломный проект, в котором присутствует автоматизированное управление котлоагрегатом и параметрическая диагностика.Вы можете просто связать поле, заслонку и кнопки "Открыть" и "Закрыть" (кнопки должны быть без фиксации) с одной целочисленной переменной. Первая кнопка должна записывать единицу в переменную. Вторая - ноль. Заслонка будет автоматически менять цвет на зелёный когда открыта и на серый когда закрыта.
Возник вопрос: как написать скрипт для управления над заслонкой?
еcolor=red]Есть заслонка, к нему прикрепил Поля(Field)- значения параметров и три кнопки ( Откр, Стоп, Закр) - нужно чтобы заслонка меняла цвет в зависимости от нажатия кнопок и значение в Поле(Field) увеличивалась/уменьшалась при открытии/закрытии заслонки, а при нажатии СТОП сохраняла последнее значение. СКРИНШОТ 1
Если DI_1 = 1 DI_2 = 0 - зелёный цвет;
Если DI_0 = 1 DI_2 = 1 - серый цвет;
Если DI_1 = 1 DI_2 = 1 - красный цвет;
Если DI_1 = 0 DI_2 = 0 - серый цвет, жёлтое моргание;
var
Color1_0, Font1_0, Color0_1, Font0_1, Color1_1, Font1_1, Color0_0, Font0_0, Flash1_1, Flash0_0: Cardinal;
begin
Color1_0 := $00CE00; //Green
Font1_0 := clBlack;
Color0_1 := clLightGray;
Font0_1 := ClBlack;
Color1_1 := ClRed; //Green
Font1_1 := clBlack;
Color0_0 := clLightGray;
Font0_0 := clBlack;
Flash1_1 := ClNone;
Flash0_0 := clYellow;
//Если вызвавший объект - изображение
if Sender is TM_Image then
with Sender as TM_Image do
begin
if ((Variable.AsBool = true) AND (VariableEX.AsBool = false)) then
begin
Alpha := 255; Color := Color1_0;
FlashColor := clNone;
end;
if ((Variable.AsBool = false) AND (VariableEX.AsBool = true)) then
begin
Alpha := 255; Color := Color0_1;
FlashColor := clNone;
end;
if ((Variable.AsBool = true) AND (VariableEX.AsBool = true)) then
begin
Alpha := 255; Color := Color1_1;
FlashColor := Flash1_1;
end;
if ((Variable.AsBool = false) AND (VariableEX.AsBool = false)) then
begin
Alpha := 255; Color := Color0_0;
FlashColor := Flash0_0;
end;
end;
//Если вызвавший объект - клапан
if Sender is TM_Valve then
with Sender as TM_Valve do
begin
if ((Variable.AsBool = true) AND (VariableEX.AsBool = false)) then
begin
Alpha := 255; Color := Color1_0;
FlashColor := clNone;
end;
if ((Variable.AsBool = false) AND (VariableEX.AsBool = true)) then
begin
Alpha := 255; Color := Color0_1;
FlashColor := clNone;
end;
if ((Variable.AsBool = true) AND (VariableEX.AsBool = true)) then
begin
Alpha := 255; Color := Color1_1;
FlashColor := Flash1_1;
end;
if ((Variable.AsBool = false) AND (VariableEX.AsBool = false)) then
begin
Alpha := 255; Color := Color0_0;
FlashColor := Flash0_0;
end;
end;
end.
Как ещё один вариант - сделать событие - изменилось значение переменных из списка.Такое событие есть начиная с версии 2.2.6.0. Пример реализации подробно описан здесь (https://simple-scada.com/help/script/changemulvar.html).
Я имел ввиду сделать подобное в свойствах объекта. Скриптом мы сейчас так и будем переделывать проект, просто когда несколько сотен таких связанных логикой переменных, заводить под них сотню дополнительных Result не слишком удобно.Рассмотрим вариант с добавлением произвольного количества доп. переменных для каждого объекта.
У меня есть кнопка с аналоговым входом AI15 .получение сигнала AI15 для активации других кнопок какой скрипт следует писать для первой кнопки ?Не понятен Ваш вопрос. Нужно нажать другие кнопки при включении переменной AI15?
как добавить паузу между Button14 и Button1 Я хочу, чтобы пауза составляла около 10 секундТолько через таймеры. Т.е. создать новую внутреннюю переменную vrTimer с типом данных Integer. На скрипт Button20_OnDataChange пишем код:
begin
Button14.Value := 0;
vrTimer.Value := 10;
end.
begin
if vrTimer.AsInt > 0 then
begin
vrTimer.Value := vrTimer.Value - 1;
if vrTimer.AsInt = 0 then
Button1.Value := 0;
end;
end.
Подскажите , как переменную типа REAL (codesys ) вывести на скаду ?а через какой opc передаете в скаду переменную? как она представлена в opc? Скажу на своем примере - у меня базис-21 контроллеры передают тоже в основном данные в формате реал, но через свой родной opc, в скаде вообще ничего не трогаю за исключением шкал. А modbus tcp устройства уже идут через aropc и вот для них нужно каждую переменную настраивать: масштабировать, подбирать порядок байт. Эт все к чему - скада всего лишь отображает то что ей передают, поэтому смотрите на opc сервер как он передает данные скаде
Хочу вывести показания температуры с двумя знаками после запятой . В codesys есть переменная тип REAL , а в simple-scada SINGLE . Думаю , надо в codesys real умножить на 100 и преобразовать в integer , а в скаде integer преобразовать в single и разделить на 100 . Не могу разобраться со скриптом преобразования типов (Преобразует целое число Value в строку).
Или как-то делается по другому .
Подскажите , как переменную типа REAL (codesys ) вывести на скаду ? Хочу вывести показания температуры с двумя знаками после запятой . В codesys есть переменная тип REAL , а в simple-scada SINGLE . Думаю , надо в codesys real умножить на 100 и преобразовать в integer , а в скаде integer преобразовать в single и разделить на 100 . Не могу разобраться со скриптом преобразования типов (Преобразует целое число Value в строку).Для вывода любой переменной (в том числе и с типом Real) в скаду не требуется использование скриптов. Если у Вас на OPC-сервере переменная настроена правильно и имеет тип Real, то можно:
var
aText: TM_Text;
aVar: TM_Variable;
s: string;
i: integer;
begin
aText := GetTextByName('TextV'+ IntToStr( Field1namevyt.Tag)); // ищем объект "Текст" с именем "TextV(I)"
aVar:= GetVariableByName('Name_vyt'+ IntToStr( Field1namevyt.Tag));
aVar.Value:= Name_vyt.Value ;
i:= UTF8Length (aVar.AsUTF8String);
if i > 30 then
UTF8Delete(aVar.AsUTF8String,30,i-30);
if FileExists('Ventyleit_'+ IntToStr( Field1namevyt.Tag)+'.txt', '') then
begin
TextFileOpen('Ventyleit_'+ IntToStr( Field1namevyt.Tag)+'.txt','' , fomRewrite, fcpUTF8); // открываем файл для чтения
TextFileWriteLn(aVar.AsUTF8String);
TextFileClose; // закрываем файл
end;
end.
var
aTry: utf8string;
begin
aTry := aVar.AsUTF8String;
if i > 30 then
UTF8Delete(aTry, 30, i-30);
end.
procedure UTF8Delete(var AStr: UTF8String; AFrom, ACount: Integer);
1. Типу переменной LongWord какой тип соответствует в Object Pascal? Есть ли что-то типа TM_Variable.AsLongWord?Если объявляете локальную переменную в скрипте, то можно так и объявлять: "MyVar: LongWord;". При взятии значения переменной используйте TM_Variable.AsInt64.
Если в скрипте в цикле несколько сотен раз читается/пишется TM_Variable.Value, имеет ли смысл заводить локальную переменную (lVar:=Sender.Value в начале и Sender.Variable.Value:=lVar в конце скрипта), или обращения непосредственно к свойству .Value достаточно эффективны?Идеальный вариант по производительности: взять значение перед циклом в локальную переменную и работать с ней, например:
var
i: integer;
aValue: Integer;
begin
aValue := myVariable.AsInt;
for i := 0 to 1000000 do
begin
// здесь работаем с aValue вместо myVariable.AsInt
end;
end.
V := CreateOleObject('PDFCreator.clsPDFCreator');
V.Test('ok test.');
Можно ли в скрипте пробежаться по всем переменным проекта, группы или тем, у которых имя начинается с определённой строки?Нет, это невозможно. Если переменные имеют однотипные имена отличающиеся только номером, то можно перебрать переменные в цикле получая их по имени через функцию GetVariableByName, но это плохой вариант по производительности даже несмотря на то, что GetVariableByName выполняет поиск по хеш-таблице.
Нет, это невозможно. Если переменные имеют однотипные имена отличающиеся только номером, то можно перебрать переменные в цикле получая их по имени через функцию GetVariableByName, но это плохой вариант по производительности даже несмотря на то, что GetVariableByName выполняет поиск по хеш-таблице.Понятно, жаль.
И еще Аваст ругается (пишет вирус IDP.Generic) и помещает файлы Server.exe и sscrep.exe в карантин.Simple-Scada использует систему защиты от взлома основанную на виртуализации кода. Если Ваш антивирус ложно определяет Simple-Scada 2, как вирус, то необходимо добавить сервер Simple-Scada 2 (Server.exe) и sscrep.exe в исключения. С результатами антивирусного анализа можно ознакомиться здесь (https://www.virustotal.com/ru/file/f424cf02b20b291f34135888c4b3e9209f94ac97bebbab488661648ef178f161/analysis/1498846119/).
Может эти объекты (TIdHTTP и TlkJSON, или такие же по возможностям на ваш выбор, что вам легче внедрить) тоже сможете добавить будет вообще замечательноПоддержку контроллеров RADSEL добавлять не планируется.
Может в базе данных SQL стоит создать дополнительную таблицу, состоящую из двух или трех столбцов (ID переменной, имя переменной в Simple-Scada, описание переменной в Simple-Scada)? Таким образом SQL-клиент смог бы выдать пользователю список переменных с именами и описанием для дальнейшей работы с архивами.Где вы ответили:
Можем реализовать такую таблицу, нот только после решения более популярных задач.Это кстати помогло бы в решении проблемы, озвученной kedr'ом:
Можно ли в скрипте пробежаться по всем переменным проекта, группы или тем, у которых имя начинается с определённой строки?
Это кстати помогло бы в решении проблемы, озвученной kedr'ом:ID переменных не подходят для перечисления в цикле, т.к. ID не упорядочиваются и могут содержать пропуски любого размера, соответственно поиск переменной по ID будет именно поиском (т.е. относительно "тяжелой" операцией), а не извлечением элемента по прямой ссылке. Для корректного перечисления переменных в цикле может подойти только глобальная переменная-массив со списком переменных.
Имеете ввиду, что придется пробежаться по каждому элементу полученного списка, используя при этом функцию GetVariableByName, и вся эта процедура отнимет много ресурсов ПК?Нагрузка на ПК будет зависеть от его конфигурации. Это будет просто плохой подход, вместо прямого перебора будет поиск переменной по имени / ID.
Неужели GetPing выдает False после первого же утерянного пинга?Функция GetPing не учитывает кол-во попыток или предыдущие попытки. Она возвращает False, если последняя попытка пинга не удалась, т.е. устройство не ответило за отведённое ему время. Возвращает True в ином случае.
Подскажите плз, можно ли обратиться с SQL-запросом к чужой (не своей) базе данных?Для БД MySQL (при наличии прав доступа) удаётся обратиться с запросом к системной (чужой?) БД при условии,
begin
Table1.Title := 'Таблицы БД ' + GetDatabaseName;
Table1.RunSQL('SELECT `TABLE_NAME`, `TABLE_ROWS`, `TABLE_COMMENT` FROM '
+ 'INFORMATION_SCHEMA.TABLES' // полное имя таблицы не заключено в кавычки ` `
+ ' WHERE `TABLE_SCHEMA` = ''' + GetDatabaseName + ''';', tsAll);
end.
Здравствуйте. Отладчика скриптов нет и не планируется в системе? Вопрос для меня критичный, не хочется для проверки скриптов каждый раз перезапускать клиентаЗдравствуйте. Внедрение отладчика для скриптов не планируется в ближайшем будущем. Планируется только добавить быстрый запуск проекта из редактора скады. К тому же и с отладчиком в большинстве случаев придется подключаться к проекту клиентом, например для отладки скрипта по событию OnClick кнопки. Придется сначала подключиться к проекту клиентом, найти эту кнопку на мнемосхеме и кликнуть её. Поэтому для событий, которые вызываются действиями пользователя внедрение отладчика не сильно упростит работу.
не хочется для проверки скриптов каждый раз перезапускать клиентаПерезапускать клиент не обязательно, достаточно перезапустить проект на сервере (https://simple-scada.com/help/manual/server-project.html). Клиент сам подключится к проекту и подхватит изменения в проекте.
Перезапускать клиент не обязательно, достаточно перезапустить проект на сервере (https://simple-scada.com/help/manual/server-project.html). Клиент сам подключится к проекту и подхватит изменения в проекте.А вот тут не работает. Изменения подхватывает только веб-клиент. И только при обновлении страницы. Скада клиент проект не обновляет, даже при перезапуске проекта на сервере.
Я не разработчик, но думаю, что вряд ли. Написать отладчик очень сложно.Нашел эту функцию, прописал в скрипт выполняемый каждую секунду, но не могу найти этот лог. Есть Client-log.txt, там нет никаких записей.
Рекомендую для отладки активно использовать функцию Log_Add.
функции вызываемые при старте проекта и остановкеТакие события уже есть (https://simple-scada.com/help/script/event-types.html): "Полностью запущен" и "Остановка проекта". Также при подключении каждого клиента выполняется событие "Клиент подключен" (https://simple-scada.com/help/script/client-connected.html).
Будут ли реализованы глобальные переменные в скриптахКак Вы правильно отметили - виртуальные переменные в меню "Проект -> Переменные" это и есть глобальные переменные.
Те переменные (внутренние виртуальные) не подходят у них нет значения хранить как Variant Type.Любая глобальная переменная, независимо от типа, который Вы выбираете, на самом деле является Variant переменной. Поэтому свойство Value такой переменной имеет тип Variant. А выбранный при создании тип данных влияет только на формат отображения и на некоторые преобразования.
Также не хватает функции VarType и IsObject. Пока на этом у меня застопоролось перевод проектов на вашу платформу.Можете подробно описать задачу которую нужно решить и для чего конкретно нужно применить эти функции? На данный момент мы не знаем реальных задач в которых нельзя было бы обойтись без этих функций.
Также при использовании скриптов в обработчике нажатия например от кнопки, возникает рядом с курсором мыши изображение колесика загрузки на доли секунды, может его не показывать если исполнение скрипта длится меньше секунды.Скада сама никогда не меняет курсор, поэтому мы не сможем на это повлиять (кроме как принудительно вызывать смену курсора на обычный в "бесконечном" цикле). Скорее всего его меняет ОС. Какие именно действия выполняются в этом скрипте? Может быть работа с файлами? Можете привести пример скрипта? Либо курсор меняется при выполнении любого скрипта?
function Test(param : variant) : Variant;
var
WshNetwork : Variant;
begin
WshNetwork := CreateOleObject("WScript.Network");
Field41.text := WshNetwork.ComputerName;
//ShowMessageAll('Заголовок окна', WshNetwork.ComputerName, clNone);
end;
function Test(param : variant) : Variant;
var
WshNetwork : Variant;
begin
objTemp.Value := CreateOleObject("WScript.Network");
Field41.text := objTemp.Value.ComputerName;
//ShowMessageAll('Заголовок окна', objTemp.Value.ComputerName, clNone);
end;
var
WshNetwork : Variant;
begin
WshNetwork := CreateOleObject("WScript.Network"); // создаем объект
Text1.Text := WshNetwork.ComputerName;
WshNetwork := Unassigned; // освобождаем объект
end.
var
WshNetwork : Variant;
begin
WshNetwork := CreateOleObject("WScript.Network"); // создаем объект
Text2.Text := WshNetwork.EnumNetworkDrives.Count;
WshNetwork := Unassigned; // освобождаем объект
end.
VarType и IsObject нужны чтобы определить создался ли объект.Можете не проверять и сразу работать с объектом, как в примерах выше. Сервер всё равно будет делать такую проверку автоматически и в случае ошибки Вы увидите соответствующее сообщение в журнале сервера.
begin
G_IP.Value:='192.168.2.9';
end.
begin
StartPing(G_IP.AsStr, 5000);
end.
begin
if GetPing(G_IP.AsStr) then
begin
G_ping.Value:=TRUE;
G_sPing.Value:='ОК';
end
else
begin
G_ping.Value:=FALSE;
G_sPing.Value:='-';
end
end.
begin
G_IP.Value:='192.168.2.9';
StartPing(G_IP.AsStr, 5000);
end.
Тогда присвоение "G_IP.Value:='192.168.2.9'" может выполниться не моментально и StartPing пройдёт для пустого IP-адреса. И почему у Вас два разных скрипта для старта?Да, это переменная скады. Я использую их, как глобальные константы и в отдельном скрипте на старте раздаю значения. Запуск пинга тоже на старте, с еще пустой константой, отсюда и ошибка. Буду задавать константы IP в скрипте старта пинга.
if (PLCtemp.value <> psPLCtemp.value) and (HMItemp.value <> psHMItemp.value) then
begin
// что-то делаем, если обе переменные изменились вместе (присваиваем значения с нужным приоритетом);
psPLCtemp.value := PLCtemp.value;
psHMItemp.value := HMItemp.value;
Exit;
end;
if (PLCtemp.value <> psPLCtemp.value) then
begin
HMItemp.value := PLCtemp.value;
psPLCtemp.value := PLCtemp.value;
psHMItemp.value := HMItemp.value;
Exit;
end;
if (HMItemp.value <> psHMItemp.value) then
begin
PLCtemp.value := HMItemp.value;
psPLCtemp.value := PLCtemp.value;
psHMItemp.value := HMItemp.value;
Exit;
end;
var // Упрощенный вид, но даже он не работает.
aCell_ID:TM_TableCell;
begin
aCell_ID := Table1.GetCell(2,2);
if aCell_ID <> nil then
vID_recept.Value := StrToInt(aCell_ID.Text);
end.
Так же на сервере выходит необработанная ошибка.ИМХО, что-то не так с данными в выбранной ячейке,- или текст из ячейки не удается преобразовать в число
Причем на ноуте к ячейкам привязаны переменные.Здравствуйте. Если к ячейке привязана переменная, то нужно работать со значением переменной, а не с текстом ячейки, например:
var
aCell:TM_TableCell;
begin
aCell := Table1.GetCell(1, 1);
if aCell <> nil then
if aCell.Variable <> nil then
vID_recept.Value := aCell.Variable.AsInt;
end.
Подскажите может проблема не только в моей криворукости?Все таки только в криворукости(
mailString: string;
mailString := 'Пост№1 = '+ QuotedStr(f1.AsStr) +', Пост 2 = '+ QuotedStr(f2.AsStr)+ ' ';
SendMail('test', 'Отчет', mailString, 'адрес_получателя@mail.ru');
Если галка стоит, то сообщения по первому изменению будут выводиться. Чтобы они не выводились ее нужно снять.эффект есть, но не большой) хотел избавиться от пиликанья алармов во время тестирования ( часто приходиться перезагружать проект). Очень жду когда реализуется редактор/runtime client в одном флаконе) Чтобы удобнее переходить из режима редактора в рантайм)
var
aIndex:integer;
begin
aIndex := tblTEST.RowIndex;
if aCell.Variable <> nil then
begin
tblTEST.GetCell(1,aIndex).Color := RGB(217,255,255);
tblTEST.GetCell(2,aIndex).Color := RGB(217,255,255);
tblTEST.GetCell(3,aIndex).Color := RGB(217,255,255);
end;
end.
var
aIndex: integer;
begin
aIndex := tblTEST.RowIndex;
if aCell.Variable <> nil then
begin
tblTEST.GetCell(1,aIndex).Color := clNone;
tblTEST.GetCell(2,aIndex).Color := clNone;
tblTEST.GetCell(3,aIndex).Color := clNone;
end;
end.
..причем все сразу?Аналогично для всех ячеек, только придется в цикле пройти по всем ячейкам. Это не очень хороший вариант, т.к. если таблица очень большая, то цикл может быть относительно долгим, плюс к этому каждое изменение цвета по каждой ячейке будет отправлено клиентам и если ячеек очень много, то и изменений придется отправить много.
var
I, J: Integer;
aCell: TM_TableCell;
begin
if (Table1.ColumnsCount > 0) and (Table1.RowCount > 0) then
for I := 0 to Table1.RowCount - 1 do
for J := 0 to Table1.ColumnsCount - 1 do
begin
aCell := Table1.GetCell(J, I);
if aCell <> nil then
if aCell.Color <> clNone then
aCell.Color := clNone;
end;
end.
то и изменений придется отправить много.Может у Вас есть возможность добавить функцию сброса выделенных цветом ячеек таблицы, хотя бы через массив столбцов? Типа- Table1.Columns[1..3].Color := clNone;(Это как образный пример)
Может у Вас есть возможность добавить функцию сброса выделенных цветом ячеек таблицыРассмотрим этот вариант.
begin
if (a1.AsInt + a2.AsInt + a3.AsInt) >= 2 then
begin
if not a1.AsBool then Bt_a1.Visible := false;
if not a2.AsBool then Bt_a2.Visible := false;
if not a3.AsBool then Bt_a3.Visible := false;
end
else
begin
Bt_a1.Visible := true;
Bt_a2.Visible := true;
Bt_a3.Visible := true;
end;
end.
begin
if (Button1.AsInt + Button3.AsInt) > 1 then
Button2.Visible := False
else
Button2.Visible := True ;
end.
Не получается по вашему скрипту .Я перед публикацией ответа этот способ сам проверял.
Не получается по вашему скрипту. Сделал по другому. Скрипты работают , но кажется так не совсем правильно.Victor_P предложил Вам правильный, рабочий вариант. Откройте редактор скриптов, создайте новый скрипт с типом события "Изменились переменные (https://simple-scada.com/help/script/changed-the-variables.html)", добавьте в него свои переменные(а1, а2, а3), скопируйте предложенный код и замените в нем имена кнопок, если у Вас они названы по другому. Пример создания скрипта с типом события "Изменились переменные" можно найти здесь (https://simple-scada.com/help/script/changemulvar.html).
Вопрос к пользователю Simple_Scada. Как Вы делаете код делфи в сообщениях?Нужно выбрать обычный код и затем дописать [code=delphi]. Можете для примера открыть Ваше отредактированное сообщение выше.
Sergey_Em, я придумал 2 варианта:
1. делаем 2 скрипта на изменение переменных, один на PLCtemp, другой на HMItemp
в скрипте на PLCtemp делаем HMItemp := PLCtemp, в скрипте на HMItemp наоборот PLCtemp := HMItemp
при этом будет лишнее присвоение из-за срабатывания скрипта после присвоения и не совсем понятно что делать, если оба события произойдут вместе
2. делаем 1 скрипт на изменение обеих переменных и создаем 2 доп. переменные psPLCtemp и psHMItemp
дальше так:Код: (delphi)if (PLCtemp.value <> psPLCtemp.value) and (HMItemp.value <> psHMItemp.value) then
begin
// что-то делаем, если обе переменные изменились вместе (присваиваем значения с нужным приоритетом);
psPLCtemp.value := PLCtemp.value;
psHMItemp.value := HMItemp.value;
Exit;
end;
if (PLCtemp.value <> psPLCtemp.value) then
begin
HMItemp.value := PLCtemp.value;
psPLCtemp.value := PLCtemp.value;
psHMItemp.value := HMItemp.value;
Exit;
end;
if (HMItemp.value <> psHMItemp.value) then
begin
PLCtemp.value := HMItemp.value;
psPLCtemp.value := PLCtemp.value;
psHMItemp.value := HMItemp.value;
Exit;
end;
как то так, может и не очень красиво, но работать должно
1 вариант я сам уже пробовал, но меняются значения только в одну сторону.
var i, j: integer;
begin
with Sender as TM_Field do j := Value; // запомнить изменившуюся переменную
PLCtemp.Value := j; // изменить переменные
HMItemp.Value := j;
i := i + 1; // счетчик вызовов этого скрипта
Text1.Text := 'Событий = ' + IntToStr(i);
end.
i := i + 1; // счетчик вызовов этого скриптаа можно подробнее?
Локальные переменные в Pascal, Delphi, C, C++, C# и т.п. языках не инициализируются компилятором и это забота программиста. При каждом очередном вызове скрипта переменной выделяется область памяти и её структура может быть любой (зависит от того, что в ней хранилось ранее), соответственно и значение переменных после выделения памяти может быть каким угодно. Компилятор в Simple-Scada работает аналогично, но при первой инициализации все-таки обнуляет переменную, а при последующих выделяет ту же самую область памяти, что и в первый раз. Поэтому в переменной значение сохраняется (не касается динамических типов данных, например строк). При этом мы рекомендовали бы работать с локальными переменными как и в других языках, т.е. сначала инициализировать, а затем использовать, чтобы значение переменной всегда было очевидным.
По поводу локальных переменных ...Т.е. она все-таки static.
if temp100.value = 10 then
begin
PlayUserSound("brew",'1.ogg',false);
PlayUserSound("brew",'2.ogg',false);
PlayUserSound("brew",'3.ogg',false);
end;
как при наступлении определенных условий проиграть несколько звуковых файлов подряд?Самый простой вариант - в программе вроде Wave editor соедините все 3 файла в 1, вставьте тишину между звуками нужной длины и сохраните как 1 файл .ogg
Добрый день!
Возник вопрос как можно реализовать следующую функционал:
Имеется определённое количество двигателей с каждым из которых связано по 2-3 переменных: ток, частота, температура, например.
Как можно сделать так, что бы была одна универсальная таблица, а параметры отображаемые в ней, зависели от конкретно выбранного насоса?
begin
{ берём ячейку на пересечении первого столбца и первой строки и
привязываем к ней переменную MyVariable }
Table1.GetCell(0, 0).Variable := MyVariable;
end.
SELECT date, value FROM table ORDER BY date DESC LIMIT 30
необходимо взять 30 последних значений, одного тэга из БД, со временем записи и вставить в таблицу, соответственно значение тега и времени записи.Здравствуйте. Как было отмечено выше -этот вопрос касается работы с SQL-запросами, а не Simple-Scada. Примеры подобных запросов можно найти в интернете (https://www.google.ru/search?q=mysql+%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C+%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%B5+%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F&oq=mysql+%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C+%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%B5+%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F&aqs=chrome..69i57.11653j1j7&sourceid=chrome&ie=UTF-8). Быстро проверить выполнение и получить результаты запросов можно через MySQLWorkbench (https://simple-scada.com/help/manual/mysql-workbench.html). Когда запрос создан и выполняет свои функции достаточно подставить его в скрипт Simple-Scada. Пример под Вашу задачу:
var
aQuery: string;
begin
{ формируем SQL-запрос в переменную aQuery }
aQuery := 'SELECT * FROM (' +
'SELECT `timestamp`, `value` FROM `trends_data`' +
'WHERE id=1' +
'ORDER BY `timestamp` DESC LIMIT 30' +
') AS Q ORDER BY `timestamp` ASC;';
{ выполняем запрос aQuery и выводим результат в таблицу Table1 }
Table1.RunSQL(aQuery, tsSaveFixRow);
end.
var
I: Integer;
B: Boolean;
begin
if (I = 0) and (B = false) then
begin
end;
if (myVar1.Value = false) and (myVar2.Value = true) then
begin
end;
end.
Вопрос касательно механизма работы окон в Simple-Scada, в частности примера "Подмены переменных у объектов" https://simple-scada.com/help/script/index.html?varsubs.html.Если второй пользователь нажал объект позже, то у обоих будет отображаться окно с переменными для "Valve2". Подмена переменных происходит одновременно на всех клиентах - в примере (https://simple-scada.com/help/script/varsubs.html) имеется соответствующее предупреждение об этом. Поэтому, если клиентов несколько, то использовать подмену переменных следует с осторожностью или не использовать вообще для избежания ситуаций, подобных описанной Вами.
Допустим у нас имеется несколько клиентов. На одном клиенте пользователь нажал на некий объект "Valve1", при этом произошел вызов окна "ValveWindow" с подменой переменных "Valve1_Cmd", "Valve1_Status", "Valve1_Mode". В это же время на другом клиенте пользователь нажал на объект "Valve2" и вызвал то же окно "ValveWindow" с подменной соответствующих переменных. Что будет отображаться в окнах на каждом клиенте: окно с переменными для "Valve2", или для каждого клиента свой экземпляр окна?
Подмена переменных происходит одновременно на всех клиентах - в примере имеется соответствующее предупреждение об этом.Невнимательно прочитал, так вопроса бы не возникло.
По вопросам 2,3: мы планируем добавить поддержку шаблонных объектов, функции импорта и экспорта объектов между проектами. Какой-то определенный срок появления таких возможностей сейчас назвать сложно. Возможно, данные функции будут добавлять поэтапно.Главное, что это планируется.
begin
{ здесь функция Round используется для округления результата деления до целого числа }
vrOPC.Value := Round(vrA.Value / vrB.Value);
end.
Как можно написать скрипт для задачи:У страницы есть событие "OnEnter" которое выполняется каждый раз, когда пользователь переходит на страницу. Для включения бита переменной нужно использовать функцию SetBit (https://simple-scada.com/help/script/setbit.html), например:
При открытии определённой странице включить по переднему фронту БИТ?
begin
VarName.Value := SetBit(VarName.AsInt, 3, True); // записать 1 (true) в 4 бит переменной "VarName"
end.
Пример моего скрипта: vvar1(вирт.перем.) := 20/16384 * Tag1(тег с ОПС). И эту "vvar1" отображаю на экране.Можно обойтись одним универсальным скриптом, но у вас ведь выполняется линейное изменение переменной, т.е. значение тега просто умножается на число 20/16384 = 0,00122. А значит можно обойтись вовсе без скриптов. Допустим Ваш OPC-тег имеет шкалу 0-100 и нужно умножать его на 0,00122. Тогда откройте его для редактирования в Редакторе и на вкладке "Дополнительные" (https://simple-scada.com/help/manual/index.html?variable-new.html#var_dop) включите режим масштабирования и рассчитайте визуальный минимум и визуальный максимум. Для этого просто берём текущую шкалу 0-100 и умножаем её на 0.00122. Получаем, виз. минимум: 0 * 0.00122 = 0. Виз. максимум: 100 * 0.00122 = 0.122. Теперь скада будет автоматически выполнять пересчет, причем это работает и в обратную сторону - при записи в OPC.
Есть к-либо более грамотное решение такой задачки?
Сделал все в точности, как было сказано... Отображаются не совсем верные данные...Напишите что именно не так с результатом? Не хватает знаков после запятой? Может быть результат сильно округлён? Вышлите проект (или пример проекта) на support@simple-scada.com в котором расчеты выполняются неверно, мы исправим, если что-то не так.
...а хотелось бы с последнего заархивированного значения...При выходе сохраняете значение в файл или БД, при запуске восстанавливаете значение.
begin
if Field1.Variable.AsInt = 1 then
vrCounter.Value := vrCounter.Value +1;
Field7.Text := IntToStr(vrCounter.Value);
end.
ведь переменная архивируется. Почему она обнуляется, ведь в скрипте она не переобъявляется?Архивация используется для отображения трендов по переменным и не влияет на значение переменной после перезапуска.
Кстати, было бы не плохо добавить свойство retentive переменнымВ ближайших обновлениях добавим возможность инициализации внутренних переменных любым значением при старте, а также возможность восстановления значения.
Еще вопрос - пытаюсь создать свой отчет в демо-версии, но похоже, что это невозможно. Так?В двух демо-версиях можно протестировать систему отчетов. В версии DEMO-64 можно построить отчет не более 5 раз после каждого перезапуска сервера скады. В версии DEMO-TIME не более 32 раз. Возможно у Вас отчет просто содержит какие-то ошибки и его не удается построить. Если это так, то Вы должны увидеть соответствующие сообщения о неудачном построении в журнале сервера (https://simple-scada.com/help/manual/server-journal.html).
Процедура RunSQL недоступна в версии DEMO-64. Протестировать работу процедуры RunSQL можно на версии DEMO-TIME.Проверил - действительно... Спасибо еще раз. Буду раскручивать директора на покупку проф-версии.
огромное спасибо...возможно ли к данному скрипту прибавить сообщение..которое будет появляться в логе (+звук), при плохом признаке качества переменной... естественно сообщение должно соответствовать тому полю которое окрасилось в красный цвет..Да, можно. Вот так:Кодvar
aVar: TM_Variable;
begin
// сначала проверяем что скрипт вызван объектом
if Sender is TM_Object then
begin
// получаем переменную с которой связан объект
aVar := TM_Object(Sender).Variable;
// если не удалось получить переменную объекта, то прерываем выполнение скрипта
if not Assigned(aVar) then Exit;
if aVar.IsGoodQuality then
TM_Object(Sender).Color := clLime
else
begin
TM_Object(Sender).Color := clRed;
AddMessage(Now, mkWarning, Sender.Name + '. Плохое качество тега!', TRUE, TRUE);
end;
end;
end.
Обратите внимание, что процедура AddMessage появилась только в Simple-Scada 2.2.0.0 (http://simple-scada.com/forum/index.php?topic=178.msg2930#msg2930). Обновите, если у Вас более старая версия.Здравствуйте. В Simple Scada 2 имеется двадцать полей, в которые выводятся значения температур. Как можно периодически (по таймеру) подсвечивать поле с максимальным значением?Здравствуйте. Правильнее будет не по таймеру, а по изменению (OnDataChange). Все поля придется перебирать в скрипте (в будущем сможем предложить лучшее решение, но пока только так). Далее подробное описание реализации.
Допустим наши поля называются Field1, Field2 ... Field20. Переходим в меню скриптов и нажимаем кнопку "Создать скрипт". В раскрывшемся окне вводим название скрипта, а тип события оставляем "Универсальный". И пишем такой код:Кодvar
aMaxField: TM_Field;
procedure GetMax(AField: TM_Field);
begin
AField.Color := clSilver; // сбрасываем цвет поля на стандартный
if AField.AsInt > aMaxField.AsInt then
aMaxField := AField;
end;
begin
aMaxField := Field1;
GetMax(Field1);
GetMax(Field2);
//... и так далее
GetMax(Field20);
aMaxField.Color := clIndianRed;
end.
Готово. Теперь выделяем все 20 полей и на событие OnDataChange ставим этот скрипт.
Далее опишем код скрипта: есть локальная переменная aMaxField, которая сначала устанавливается равной первому полю Field1. Затем для каждого из полей вызывается подпроцедура GetMax в которую передается очередное поле. И если значение в переданном поле больше чем значение в aMaxField, то aMaxField приравнивается переданному полю. В итоге после всех процедур GetMax в переменной aMaxField будет поле с самым большим значением. Его мы и подсвечиваем красным цветом. В примере работа идет с целочисленными переменными. Если у вас в полях вещественные числа, то измените в коде AsInt на AsFloat.
TextFileOpen('smool.bat', '', fomRewrite, fcpUTF8); { открыть файл для записи }
{ записать в файл строку }
Textfilewriteln(textwrite);
{ закрыть файл }
TextFileClose;
{ запуск }
RunApplication('brew', 'smool.bat', '');
TextFileOpen('smool.bat', 'D:\Моя папка\', fomRewrite, fcpUTF8); { открыть файл для записи }
{ записать в файл строку }
Textfilewriteln(textwrite);
{ закрыть файл }
TextFileClose;
{ запуск }
RunApplication('brew', 'D:\Моя папка\smool.bat', '');
нужен скрипт для открытия картинки (JPEG) из проекта, которая находиться на сервере (путь:\\\\hproliant\info_simplescada\Температурный график )Для этого нужно запустить приложение для просмотра изображений(при помощи процедуры RunApplication (https://simple-scada.com/help/script/runapp.html)) и в параметры передать путь к файлу, например:
begin
RunApplication(GetClientName, 'mspaint.exe', '"\\hproliant\info_simplescada\Температурный график.jpeg"');
end.
А можете добавить возможность рисовать точку, окружность, прямоугольник с заданными координатами из скрипта?Добавление такой возможности не планируется.
1. Через RunApplication можно запустить браузер и передать в качестве адреса "192.168.0.100", чтобы сразу открылась нужная страница ?Да, можно, например:
begin
RunApplication(GetClientName, 'C:\Program Files\Internet Explorer\iexplore.exe', 'http://simple-scada.com');
end.
2. Можно ли прочитать данные со страницы браузера, используя скрипты?Стандартными средствами нельзя.
В двух демо-версиях можно протестировать систему отчетов. В версии DEMO-64 можно построить отчет не более 5 раз после каждого перезапуска сервера скады. В версии DEMO-TIME не более 32 раз.Мы приобрели проф-версию, но почему-то через некоторое время отчеты все равно перестают запускаться. Что у нас не так?
В журнале сервера (https://simple-scada.com/help/manual/server-journal.html) выводятся какие-то сообщения связанные с построением отчетов?В журнале никаких сообщений об ошибках нет. На ПК стоит лицензионная версия сервера с ключом, и запущена именно она. Может, надо было удалить все демо-версии перед установкой лицензионной?
Вы уверены, что на Вашем ПК установлена лицензионная версия и Вы запускаете именно её? Посмотрите что отображается в строке версия на странице "Состояние сервера" (https://simple-scada.com/help/manual/server-status.html).
Удалять не нужно, главное чтобы был запущен именно лицензионный сервер, а не Demo-64 или Demo-Time. Также отчеты могут не строиться, если во время построения отчета возникают ошибки. Но в этом случае текст ошибки должен отобразиться в журнале сервера. У Вас отчет не строится ни одного раза? Или несколько раз строится и затем перестаёт? Вы проверяли журнал сервера после неудачных попыток построения отчета?Надо еще понаблюдать. Возможно, в отчетах были ошибки, хотя в журнале никаких сообщений не было. Самое интересное, что в предварительном просмотре отчет формировался, а из клиента не вызывался, помогал только перезапуск сервера.
Какую версию скады Вы используете?Версия 2.3.1.0, получена в октябре 2018 года, я думаю, что это самая-самая.
В журнале нашел сообщения типа "Подключен клиент системы отчетов", "Отключен клиент системы отчетов", "Отчет такой-то поставлен в очередь на отправку", "Отчет такой-то отправлен клиентам". Что это означает?Это стандартные сообщения. Первые два выдаются если вы в редакторе отчетов нажимаете кнопку "обновить данные". Вторые два когда строите реальный отчет через скаду и он отправляется клиентам для просмотра. Если будут сообщения связанные с ошибками, то они будут выделены красным цветом.
Время | Значение
-------------------
00:00 | 1
00:02 | 2
00:04 | 3
00:06 | 4
Время | Значение
-------------------
00:00 | 10
00:03 | 20
00:06 | 30
00:09 | 40
Время | vrA | vrB
--------------------------
00:00 | 1 | 10
00:02 | 2 | ???
00:03 | ??? | 20
00:04 | 3 | ???
00:06 | 4 | 30
00:09 | ??? | 40
Время | vrA | vrB
--------------------------
00:00 | 1 | 10
00:02 | 2 | 10
00:03 | 2 | 20
00:04 | 3 | 20
00:06 | 4 | 30
00:09 | 4 | 40
Время | vrA | vrB
--------------------------
00:00 | 1 | 10
00:02 | 2 | 10
00:04 | 3 | 20
00:06 | 4 | 30
koef_rasch := (21 - t_narvozd) / (t_komfort + 43);
IF (t_narvozd >= -43) THEN
t_zad := t_komfort + 61 * 0.5 * koef_rasch + (129 - t_komfort * 2) * 0.5 * EXPT(koef_rasch, 0.8);
ELSE
t_zad := 95;
END_IF
t_zad_obr := t_zad - 25 * koef_rasch;
begin
koef_rasch.Value := (21 - t_narvozd.Value) / (t_komfort.Value + 43);
if t_narvozd.Value >= -43 then
t_zad.Value := t_komfort.Value + 61 * 0.5 * koef_rasch.Value +
(129 - t_komfort.Value * 2) * 0.5 * Power(koef_rasch.Value, 0.8)
else
t_zad.Value := 95;
t_zad_obr.Value := t_zad.Value - 25 * koef_rasch.Value;
end.
var
koef_rasch, t_zad: Double;
begin
koef_rasch := (21 - t_narvozd.Value) / (t_komfort.Value + 43);
if t_narvozd.Value >= -43 then
t_zad := t_komfort.Value + 61 * 0.5 * koef_rasch +
(129 - t_komfort.Value * 2) * 0.5 * Power(koef_rasch, 0.8)
else
t_zad := 95;
t_zad_obr.Value := t_zad - 25 * koef_rasch;
end.
begin
Var1.Value := GetBit(VarName.AsInt, 0); // получить значение 1-ого бита переменной "VarName"
end.
var
aVar: TM_Variable;
aName: string;
aTagPage: string;
aStatus: string;
begin
if Sender is TM_Object then
with Sender as TM_Object do
if VariableEx.Value = True then
begin
Visible := True;
aName := IntToStr(Sender.Tag);
aTagPage := IntToStr // (здесь необходимо получить значение дополнительного тега подстраницы которая активна);
// aVar:= GetVariableByName ()
end else
Visible := False;
{ if VariableEx.Value = True then // если значение доп переменной объекта равно 1, то
begin
FlashColor := clYellow; // включить мигание объекта жёлтым цветом
AnimSpeed := 5;
end else // иначе
begin
FlashColor := clNone; // отключить мигание объекта
AnimSpeed := 0;
end;
end; }
end.
Не подскажите как из скрипта для кнопки получить значение Tag(дополнительной переменной) подстраницы на которой находится эта кнопкаУ страниц и подстраниц нет таких свойств. Они не могут быть привязаны к переменной, или к дополнительной переменной. У них есть только свойство Тег (целое число). Может быть нужно по номеру тега получить переменную. Например, если у страницы Тег = 200, то получить переменную с именем varable200? Если у другой страницы Тег = 111, то получить переменную с именем varable111 и т.д.?
Добрый день, подскажите как правильно написать скрипт. (он должен быть универсальным) Есть лампочка, к ней привязана основная переменная, скрипт должен проверять что если число переменной выходит за рамки значения от 4 до -4 включая ноль (то есть если будет 5 и больше или -5 и меньше) то должно включать горение красным цветом. Спасибо за вниманиеВот пример:
begin
if Sender is TM_Object then
with Sender as TM_Object do
if (AsInt < -4) or (AsInt > 4) then
Color := clRed
else
Color := clNone;
end.
Здравствуйте!
Не подскажите как из скрипта для кнопки получить значение Tag(дополнительной переменной) подстраницы на которой находится эта кнопка .Скрипт должен быть универсальным без явного прямого обращения
var
aName: string;
aTagPage: string;
begin
. . .
aName := IntToStr(Sender.Tag div 100);
aTagPage := IntToStr(Sender.Tag mod 100);
. . .
end.
begin
// для подстраницы - установить ее тэг, для страницы - восстановить тэг подстраницы, открытой последней на этой странице
iTagPage.Value := Sender.Tag;
end.
begin
Sender.Tag := iTagPage.AsInt; // запомнить тэг подстраницы, открытой последней на этой странице
Здравствуйте.Здравствуйте. Попробовал повторить скрипт, но он почему то не работает. Что я сделал не правильно? Посмотрите пожалуйста.
См. функции для работы с битами (https://simple-scada.com/help/script/work-bits.html). Разбивать на биты лучше всего в скрипте по изменению переменной (https://simple-scada.com/help/script/changed-the-variables.html).Код: (delphi)begin
Var1.Value := GetBit(VarName.AsInt, 0); // получить значение 1-ого бита переменной "VarName"
end.
begin
IntVar_1.Value := GetBit(MI120_Alarms.AsInt, 0);
IntVar_2.Value := GetBit(MI120_Alarms.AsInt, 1);
IntVar_3.Value := GetBit(MI120_Alarms.AsInt, 2);
IntVar_4.Value := GetBit(MI120_Alarms.AsInt, 3);
IntVar_5.Value := GetBit(MI120_Alarms.AsInt, 4);
end.
// Заполнение суточной таблицы получасовыми отрезками
if chbValue_2.Value = 1 then
begin
tbl_Ed.Visible := True;
tbl_Ed.Caption := tblCapt;
et := StartT;
st := IncMinute(StartT,-30); // Начало периода
for i:=1 to 48 do
begin
st := IncMinute(st,30);
et := IncMinute(et,30);
ArchiveMin(EnergyAin_1,EnergyAin,st,et);
ArchiveMax(EnergyAin_1,EnergyAout,st,et);
tbl_Ed.Columns[0].Cells[i].Text := DateTimeToStr(st)+ ' - ' +TimeToStr(et);
tbl_Ed.Columns[1].Cells[i].Text := FloatToStr(EnergyAout.Value-EnergyAin.Value);
end;
end;
...
{ здесь мы запускаем вычисление архивного значения. Когда вычисления будут завершены,
результаты запишутся в переменные EnergyAin и EnergyAout }
ArchiveMin(EnergyAin_1, EnergyAin, st, et);
ArchiveMax(EnergyAin_1, EnergyAout, st, et);
{ и сразу же, не дожидаясь завершения вычислений (ведь они могут занять сколько угодно
времени), переходим к выполнению следующих строк скрипта }
{ здесь в переменных EnergyAin и EnergyAout всё ещё будут нули, т.к. вычисления архивных функций
ещё не завершены }
tbl_Ed.Columns[0].Cells[i].Text := DateTimeToStr(st)+ ' - ' +TimeToStr(et);
tbl_Ed.Columns[1].Cells[i].Text := FloatToStr(EnergyAout.Value-EnergyAin.Value);
...
Поэтому правильнее было бы связать ячейки таблицы с переменными EnergyAin и EnergyAout, тогда скада сама отобразит их значения после завершения вычисленийСпасибо за разъяснение, переделал с перемеными в ячейках, всё работает как надо
как сделать проверку значения А==В и запись в тэг В значения тэга А если условие А=!В?
{ если значение тега А не равно значению тега B }
if (A.Value <> B.Value) then
{ то записываем значение тега А в тег B }
B.Value := A.Value;
{ если значение тега А равно значению тега B }
if (A.Value = B.Value) then
begin
// здесь можно расположить свой код
end;
отсутствует объект переключатель, поэтому с помощью двух кнопок хочу сделать аналог переключателя, но в скриптах не нашел как задать состояние кнопки, прочитать можно, а задать нет(У кнопки есть выбор типа "С фиксацией", этого недостаточно чтобы сделать переключатель?
Добрый день, прошу подсказки:
как сделать фильтрацию антидребезга контактов с настраиваемым периодом с помощью скрипта.
Сомнительно что это на скаде получиться реализовать, скада обновляет значение тега с заданным интервалом (1 сек по-умолчанию). Можно конечно попробовать сделать скрипт изменились переменные и по ней запускать таймер, но я таймера не применял никогда, не совсем понятно из хелпа как они работают.Ну и я о том же, нужно видеть что повторяется включение, запустить таймер (с возможностью менять его значение) и если по прошествии заданного времени условие сохраняется - повторить включение.
делается для операторов бабулек и высока вероятность принять включено за включить.
отсутствует объект переключатель, поэтому с помощью двух кнопок хочу сделать аналог переключателя, но в скриптах не нашел как задать состояние кнопки, прочитать можно, а задать нет(чем Вас кнопка с фиксацией не устраивает? и в ней же как выше посоветовали выбираете цвет индикации состояния.
если нет то буду из кнопок городитьПростите великодушно ;)
делается для операторов бабулек и высока вероятность принять включено за включить.
...При этом мы хорошо понимаем, что описанные Вами функции будут очень полезны даже при решении небольших задач, поэтому пункты 1, 2, 4 (касаемо глобальных классов) и 7 будут реализованы в будущих обновлениях...Можно узнать, когда они (глобальные процедуры) будут реализованы? Надоело один и тот же кусок кода размножать по нескольким скриптам...
begin
// Sender - это объект, который вызвал скрипт(например поле, изображение и т.д.)
if Button123 is TM_Object then // проверяем, что Sender это объект
with Button123 as TM_Object do // приводим Sender к типу "TM_Object"
case AsInt of // если значение переменной связанной с объектом равно:
1..25: Color := clYellow; // от 1 до 5 - изменить цвет на красный
26..50: Color := clChartreuse; // 6 или 7 - изменить цвет на зеленый
51..255 : Color := clRed; // 8 - изменить цвет на желтый
end;
end.
begin
{ записываем значение 15 в переменную ArduinoSerial0_D2}
ArduinoSerial0_D2.Value := 15;
end.
begin
iButtons.Value := Sender.Tag and iButtons.AsInt; // не более одной нажатой кнопки
// действия по кнопкам
case iButtons.AsInt of
. . .
end;
end.
begin
iButtons.Value := Sender.Tag; // только одна нажатая кнопка
// действия по кнопкам
case iButtons.AsInt of
. . .
end;
end.
emoxristov!
Возможно Вам подойдет "радиокнопка", в примере два варианта алгоритма:
- не более одной нажатой кнопки (см. этот);
- только одна нажатая кнопка.
Кнопки с фиксацией и управляются битами переменной iButtons, тег кнопки равен битовой маске.
Переменная кнопок модифицируется скриптами по событию "Пользователь кликнул объект мышью"Кодbegin
iButtons.Value := Sender.Tag and iButtons.AsInt; // не более одной нажатой кнопки
// действия по кнопкам
case iButtons.AsInt of
. . .
end;
end.Кодbegin
iButtons.Value := Sender.Tag; // только одна нажатая кнопка
// действия по кнопкам
case iButtons.AsInt of
. . .
end;
end.
А где поставил переменная ArduinoSerial0_D2 ?
var i:integer;
begin
iButtons.Value := Sender.Tag and iButtons.AsInt; // не более одной нажатой кнопки
// действия по кнопкам
case iButtons.AsInt of
2: i := 15; // значения взяты из примера Button_without_scripts
4: i := 50;
8: i := 75;
16: i := 200;
else
i := 0;
end;
ArduinoSerial0_D2.Value := i;
end.
begin
ReportView('my_client', 'Текущие данные');
end.
begin
ReportView(GetClientName, 'Текущие данные');
end.
подскажите пожалуйста , как можно разобрать строку и потом собрать её снова , но только без не нужных символов , т.е у меня есть строка - это тэг с плк в формате дата и время DT в скаду он передаётся как строка вида он такого DT#2018-09-03-12:01:03.569 а мне нужно получитьДумаю, что как-то так получится:
2018-09-03-12:01:03 - заранее благодарен.
Добрый день!
Возможно ли на уровне скриптов формировать GET/PUT http запросы????
Вариант с RunApplication не очень элегантный...
спасибо.
Возможно ли на уровне скриптов формировать GET/PUT http запросы?Нет, но в будущем появятся такие функции в скриптах.
Функция RunApplication вызывает переполнение памяти при многократном вызове!Скада для запуска приложений использует прямой вызов ShellExecuteExW из WinAPI (https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecuteexw), никаких доп. действий не выполняется. Если у Вас Windows 10, то возможно Вы столкнулись с ошибкой Windows, которая подробно описана здесь (https://stackoverflow.com/questions/53590512/how-to-avoid-memory-leaks-using-shellexecuteex) и здесь (https://social.msdn.microsoft.com/Forums/azure/en-US/33bade27-ad82-49a7-b9fa-f8eb55803470/shellexecute-leaks-handles?forum=windowsgeneraldevelopmentissues). Она была устранена в последних обновлениях Windows. По крайней мере у нас она не наблюдается в версии 1803 (сборка 17134.472).
Почему происходит утечка памяти?
begin
RunApplication(GetClientName, GetProjectPath + 'Files\info_simplescada\Температурный график', '');
end.
begin
{ запустить Paint на клиенте, который вызвал скрипт и открыть изображение }
RunApplication(GetClientName, 'mspaint.exe', GetProjectPath + '"Files\info_simplescada\Температурный график.jpeg"');
end.
begin
{ открыть папку в проводнике Windows }
RunApplication(GetClientName, 'explorer.exe', GetProjectPath + 'Files\info_simplescada\Температурный график');
end.
begin
{ открыть папку в проводнике Windows }
RunApplication(GetClientName, 'explorer.exe', '\\192.168.1.10\info_simplescada\Температурный график');
end.
К сожалению в этом вопросе мы вряд ли сможем что-то изменить, т.к. для запуска приложений используется только код Windows. Видимо в сборке 17134.523 также присутствует проблема с ShellExecute (в этом случае можно рекомендовать только сменить версию или сборку ОС), либо утечка связана с чем-то другим. Если есть желание, отправьте нам на support@simple-scada.com текущую версию проекта. Мы можем проверить её на различных ПК, а также на наличие другие проблем (вдруг утечка памяти вызвана чем-то ещё?).
begin
if Started.Value = 1 then
Counter.Value := Counter.Value + 1
else
Counter.Value := -1;
case Counter.Value of
1: ArduinoSerial0_D31.Value := 1;
2: ArduinoSerial0_D31.Value := 1;
8: ArduinoSerial0_D49.Value := 1;
9: ArduinoSerial0_D49.Value := 1;
15: ArduinoSerial0_D45.Value := 1;
18: Started.Value :=0;
end;
end.
Как можно отключить (завершить работу) Web-клиентов?Отключить web-клиентов можно через сервер Simple-Scada -> вкладка "Клиенты"(см. скриншот во вложении).
Используя процедуру CloseApplicationClient/CloseApplicationAll, скрипт срабатывает только для обычных клиентов и для web-клиентов, запущенных через созданную иконку на Android-устройстве.Проверим и по возможности добавим отключение web-клиентов через процедуры CloseApplicationClient/CloseApplicationAll.
Simple Scada как я могу изменить 1,2,8,9,15,18 секунды, чтобы стать миллисекундами 100,200,300,400,500,600.Вероятнее всего, Вы привели пример секундного скрипта - он будет выполняться 1 раз в секунду, т.е. переменная Counter будет принимать значения 1,2,3,4,5 и т.д. и условия по миллисекундам не выполнятся. Если Вам требуется по каму-либо условию записать значения в переменные контроллера с задержкой 100мс., то правильнее всего это делать на контроллере.
Проверим и по возможности добавим отключение web-клиентов через процедуры CloseApplicationClient/CloseApplicationAll.Ок, будем ждать. Задача стоит именно в автоматическом отключении web-клиентов спустя некоторое время, при максимальном количестве подключенных клиентов.
var
Temp_float: Single;
Temperature: ShortInt;
begin
Temp_float := (Temperature / 100);
end.
var
Temp_float: Single;
Temperature: ShortInt;
begin
Temp_float := ShortIntToFloat (Temperature);
end.
Есть внешняя переменная Temperature(ShortInt) с значением 2395, а надо преобразовать в внутр. переменную Temp_float(Single), чтобы значение отображалось как 23,95.Это можно сделать без использования внутренней переменной и скриптов. Откройте редактор переменных, дважды кликните по переменной Temperature, перейдите на вкладку свойств "Дополнительные (https://simple-scada.com/help/manual/index.html?variable-new.html#var_dop)" и установите свойство "Сдвиг запятой" равным "-2". Также, не забудьте назначить переменной соответствующую шкалу, например если переменная Temperature может принимать значения от 0 до 10000, то у нее должна быть шкала 0-10000.
var
Temp_float: Single;
begin
Temp_float := (Temperature.Value / 100);
end.
var
Temp_float: Single;
Temperature: ShortInt;
begin
Temp_float := Temperature;
end.
begin
vrH.Value := ATime div 3600;
vrM.Value := (ATime - vrH.AsInt * 3600) div 60;
vrS.Value := ATime - vrH.AsInt * 3600 - vrM.AsInt * 60;
end.
Есть переменная в контроллере, тип DWORD. В ней записаны секунды, общим количеством не более чем 86400 (полное количество секунд в сутках). Как можно отобразить на скаде в полях отдельно часы, минуты и секунды данной переменной с возможностью редактирования и без использования промежуточных внутренних переменных?
begin
dwTimeOPC.Value := Frac(dtTime.Value); // ограничить только временем суток
end.
begin
dtTime.value := dwTimeOPC.Value;
end.
begin
if vrSCREEN.AsInt = 0 then
Page1.GoToPageClient(GetClientName);
if vrSCREEN.AsInt = 1 then
Page2.GoToPageClient(GetClientName);
if vrSCREEN.AsInt = 2 then
Page3.GoToPageClient(GetClientName);
end.
begin
CASE vrSCREEN.AsInt OF
0: Page1.GoToPageClient(GetClientName);
1: Page2.GoToPageClient(GetClientName);
2: Page3.GoToPageClient(GetClientName);
END;
end.
нужен скрипт, который подсчитывает количество изменений состояний переменной DI_01 за интервал 10 секунд.Здравствуйте!
begin
cntDI_01.Value := cntDI_01.Value + 1; // счетчик изменений DI_01
end.
// переменная статическая и сохраняется между обращением к скрипту, однако в рабочем проекте желательно использовать внутреннею переменную
var i: integer; // i - отсчет интервала
begin
i := (i + 1) mod 10; // счетчик 0 - 9, отсчет временного интервала 10 сек
if i <> 0 then exit;
// обработка данных за интервал 10 сек
. . .
cntDI_01.Value := 0; // сброс счетчика изменений DI_01 для следующего интервала
end.
begin
vrTime.Value := vrTime.Value + 1; // счетчик 0 - 9, отсчет временного интервала 10 сек
if vrTime.Value < 10 - 1 then exit;
// обработка данных за интервал 10 сек
. . .
vrTime.Value := 0; // сброс таймера
cntDI_01.Value := 0; // сброс счетчика изменений DI_01 для следующего интервала
end.
const
INTERVAL_1 = 7;
INTERVAL_2 = 9;
begin
if DO_06.AsInt = 0 then
begin
if vrTimer.AsInt < INTERVAL_1 then
vrTimer.Value := vrTimer.Value + 1;
if vrTimer.AsInt = INTERVAL_1 then
begin
DO_06.Value := 1;
vrTimer.Value := 0;
end;
end else
begin
if vrTimer.AsInt < INTERVAL_2 then
vrTimer.Value := vrTimer.Value + 1;
if vrTimer.AsInt = INTERVAL_2 then
begin
DO_06.Value := 0;
vrTimer.Value := 0;
end;
end;
end.
const
INTERVAL = 16;
arDO_06: array [0..(INTERVAL - 1)] of integer =
(0, 0, 0, 0, 0, 0, 0, // образ выхода DO_06, в частности и не бинарный
1, 1, 1, 1, 1, 1, 1, 1, 1); // или для нескольких выходов
begin
{ if vrTimer.AsInt < 0 then <обработка ошибки индекса массива> }
if vrTimer.AsInt >= INTERVAL then vrTimer.Value := 0;
DO_06.Value := arDO_06[vrTimer.AsInt];
vrTimer.Value := vrTimer.Value + 1;
end.
если за 15 секунд переменная T2.Value не изменила значение, то переменной OT.Value :=0.
begin
TimeCnt.Value := 0; // начало отсчета по изменению переменной - перезапуск одновибратора
end.
const INTERVAL = 15;
begin
if TimeCnt.Value >= INTERVAL then
OT.Value := 0 // таймер сработал
else
begin
OT.Value := 1; // если удалить эту команду, то одновибратор будет работать в режиме однократного запуска по OT=1
TimeCnt.Value := TimeCnt.Value + 1;
end;
end.
begin
preTimeCnt.Value := TimeCnt.Value; // момент отсчета по изменению переменной - перезапуск одновибратора
end.
const INTERVAL = 15;
begin
if (TimeCnt.Value - preTimeCnt.Value) >= INTERVAL then
OT.Value := 0 // таймер сработал
else
begin
OT.Value := 1; // если удалить эту команду, то одновибратор будет работать в режиме однократного запуска по OT=1
TimeCnt.Value := TimeCnt.Value + 1;
end;
end.
begin
TimeCnt.Value := TimeCnt.Value + 1;
end.
const INTERVAL = 15;
begin
if Variable.Name = 'T2' then preTimeCnt.Value := TimeCnt.Value;
if Variable.Name = 'TimeCnt' then
if (TimeCnt.Value - preTimeCnt.Value) > INTERVAL then
OT.Value := 0
else
OT.Value := 1;
end.
const INTERVAL = 15;
var i: integer;
begin
i := T2.Value;
if i <> preT2.Value then TimeCnt.Value := 0;
preT2.Value := i;
if TimeCnt.Value >= INTERVAL then
OT.Value := 0
else
begin
OT.Value := 1;
TimeCnt.Value := TimeCnt.Value + 1;
end;
end.
begin
ChangeVarTime.Value := NOW(); // начало отсчета по изменению переменной
end.
const INTERVAL = 15;
begin
if SecondsBetween(NOW(), ChangeVarTime.Value) >= INTERVAL then // сравнить с текущим временем события "Прошла секунда"
OT.Value := 0
else
OT.Value := 1;
end.
begin
if not (Sender is TM_Button) then Exit;
with Sender as TM_Button do
if Assigned(VariableEx) then
if GetBit(VariableEx.AsInt,7) then
begin
VariableEx.Value := SetBit(VariableEx.Value,7,False);
VariableEx.Value := SetBit(VariableEx.Value,6,False);
end else
begin
VariableEx.Value := SetBit(VariableEx.Value,6,False);
end;
end.
end.
begin
if not (Sender is TM_Button) then Exit;
with Sender as TM_Button do
if Assigned(VariableEx) then
if GetBit(VariableEx.AsInt,7) then
begin
VariableEx.Value := SetBit(VariableEx.Value,7,False);
VariableEx.Value := SetBit(VariableEx.Value,6,False);
end else
VariableEx.Value := SetBit(VariableEx.Value,6,False);
end.
И опять же, повторюсь. Он компилируется в редакторе скриптов без ошибок.И это верно, пока Вы компилируете скрипт отдельно - ошибок не будет, он закончится на первом "end.". Но при сохранении проекта код скрипта будет помещён в общую программу вместе со всеми остальными скриптами и ошибка с двумя "end." выявится, что и происходит.
{Заполняем таблицу tblPremix}
procedure Update_tblPremix;
var
aQuery:string;
begin
aQuery := 'SELECT '+
'DP_PRODUCT_ID '+
',AR_NAME '+
',AR_ARTICLECODE '+
// ',CASE DP_PREMIX_STATUS1 WHEN 1 THEN ''X'' ELSE '''' END AS PREMIX_STATUS1 '+
// ',CASE DP_PREMIX_STATUS2 WHEN 1 THEN ''X'' ELSE '''' END AS PREMIX_STATUS2 '+
// ',CASE DP_PREMIX_STATUS3 WHEN 1 THEN ''X'' ELSE '''' END AS PREMIX_STATUS3 '+
// ',CASE DP_PREMIX_STATUS4 WHEN 1 THEN ''X'' ELSE '''' END AS PREMIX_STATUS4 '+
'FROM CFG_DAYPROGRAM '+
'INNER JOIN CFG_ARTICLE ON DP_PRODUCT_ID = AR_ARTICLE_ID '+
'WHERE (DP_BATCHESDONE < DP_BATCHESSET AND DP_LOTNUMBER > 0 AND DP_PRODUCE = 1 AND DP_MODIFYTYPE <> ''D'') '+
'OR DP_LOTNUMBER IN (SELECT DP_LOTNUMBER '+
'FROM CFG_DAYPROGRAM '+
'INNER JOIN CFG_BATCHES ON DP_LOTNUMBER = BD_LOTNUMBER '+
'INNER JOIN CFG_UNITS ON UN_BATCHNUMBER = BD_BATCHNUMBER '+
'WHERE BD_BATCHNUMBER > 1 '+
'AND UN_MODIFYTYPE <> ''D'') '+
'ORDER BY DP_PRODUCTIONNUMBER;';
tblPremix.RunSQL(aQuery, tsSaveFixRow);
end;
begin
if vUpdate_tblPremix.Value = true then // если тру - то выполняем процедуру
begin
Update_tblPremix;
vUpdate_tblPremix.Value := false;
end;
end.
Тогда сразу же хочется кнопку "Компилировать все"Добавим. Сейчас для перекомпиляции достаточно сохранить проект (Ctrl + S).
Есть скрипт с запросом к БДА это ошибка, исправим в ближайшее время. В текущей версии её можно устранить: как и описал pan2000, добавьте пробел между запятой и case.
при компиляции выдает ошибку [Update_tblPremix]"begin" not found
Если закоментировать блок с CASE в запросе - то компиляция проходит без ошибок.
var
aQuery: string;
begin
aQuery := ',Begin'; // следующие строки сохраняются с ошибкой:
// aQuery := ', Begin'; // не все пробелы одинаково полезны
// aQuery := ',' + 'Begin'; // различие между значениями суммы констант и одиночной константой
end.
а в попытке нового компилятора рассматривать строковую константуИменно в этом и состоит ошибка. Но в приведённом deldemo коде пробел исправит ситуацию, т.к. в строке после CASE есть и END, который будет учтен как завершение CASE.
begin
B1.Enabled := False;
B2.Enabled := False;
end.
begin
with Sender as TM_Object do
if AsInt AND Sender.Tag = Sender.Tag then Sender.Color := clRed
else Sender.Color := RGB(191,191,191);
if Variable.IsGoodQuality = False then Sender.Color := clGray;
end.
Ошибка в скрипте "ChangeColor2Red" в строке 8. Access violation at address 0479C684. Read of address 0A569FA0Компилируется скрипт без ошибок. В чем может быть дело?
begin
with Sender as TM_Object do
begin
if AsInt AND Sender.Tag = Sender.Tag then
Sender.Color := clRed
else
Sender.Color := RGB(191,191,191);
if Variable.IsGoodQuality = False then Sender.Color := clGray;
end;
end.
begin
{ если значение переменной (в виде целого числа),
которая связана с изображением больше нуля, то }
if Image1.AsInt > 0 then
begin
Image1.AnimSpeed := 16; // запустить анимацию
Button14.Value := 0;
end else // иначе
Image1.AnimSpeed := 0; // остановить анимацию
end.
скажем в имени файла должна быть дата, время и цифраЛучше всего использовать специальную функцию DateTimeToFileName (https://simple-scada.com/help/script/datetimetofilename.html) для имён файлов.
Если пользователь редактирует значение любого поля и нажимает "enter", а затем наводит мышь на любую таблицу и кликает, то событие OnCellClick с первого раза не срабатывает.Исправим в ближайшем обновлении.
begin
{ записываем значение 1 в переменную ArduinoSerial0_D2}
ArduinoSerial0_D2.Value := 1;
end.
Могу ли я создать дополнительное окно для ввода для каждой кнопки при mV активировать.Да, окно можно создать через пункт меню "Окна (https://simple-scada.com/help/manual/windows.html)". Для вызова окна из скрипта нужно использовать процедуры ShowAll (https://simple-scada.com/help/script/wshowall.html), ShowClient (https://simple-scada.com/help/script/wshowclient.html).
Simple Scada:Все зависит от представления переменной, если у Вас шкала этой переменной описана от 0 до 20 мВ, то в данном случае будет 1 мВ
ArduinoSerial0_D2.Value := 1; Вы имеете в виду, что 1 равен одному вольту, так ?
Как сделать милливольт, Я хочу активировать кнопку на милливольт?
begin
ReportView(GetClientName, 'Электроэнергия - Гагарина, 8');
end.
S := TextFileReadLn;
B1.Value := StrToInt(S);
S := TextFileReadLn;
B1_STRING_24.Value := S;
...
подскажите как это понимать, почему строка 5840 не может конвертироваться в интежер
...
begin
Text1.Text := IntToStr(StrToInt(' 5840')); // 5840
Text2.Text := IntToStr(StrToInt('5840 ')); // Ошибка в скрипте "Script_0" в строке 3.
end. // '5840 ' is not a valid integer value
var i: integer;
aStr: string;
begin
Text1.Text := '';
aStr := S.AsStr;
for i := 1 to Length(aStr) do
Text1.Text := Text1.Text + IntToHex(integer(aStr[i]), 4) + ' ';
end.
РАЗОБРАЛСЯ , необходимо было удалять невидимые символы , до команды каретки , использовал нотепад++...
подскажите как это понимать, почему строка 5840 не может конвертироваться в интежер
...
Ошибка StrToInt для аргумента завершающегося не цифрой:Код: (delphi)begin
Text1.Text := IntToStr(StrToInt(' 5840')); // 5840
Text2.Text := IntToStr(StrToInt('5840 ')); // Ошибка в скрипте "Script_0" в строке 3.
end. // '5840 ' is not a valid integer value
GetVariableByName(aName + '_Ypravl').Value := SetBit(GetVariableByName(aName + '_Ypravl').Value, 4, False);
aQuery := 'UPDATE ' + Month + ' SET ' + //
'`Text_1`=' + QuotedStr(GetVariableByName('edtDay_1_' + IntToStr(i)).Value) + ', ' +
'`Text_2`=' + QuotedStr(GetVariableByName('edtDay_2_' + IntToStr(i)).Value) +
' WHERE `Day`=' + QuotedStr(IntToStr(i));
var
aVar: TM_Variable;
begin
aVar := GetVariableByName(aName + '_Ypravl'); // только один поиск переменной по имени
aVar.Value := SetBit(aVar.Value, 4, False);
end.
BEGIN
if Sender is TM_Image then // проверяем, что Sender это изображение
with Sender as TM_Image do // приводим Sender к типу "TM_Image"
if AsBool then
Visible := True //
else // иначе
Visible := False;
END.
begin
if Sender is TM_Image then // проверяем, что Sender это изображение
with Sender as TM_Image do // приводим Sender к типу "TM_Image"
Visible := AsBool;
end.
и второй вопрос создавал новый проект , соответственно в офлайне , когда перенес на полную версию , сервер начал ругаться на половину переменных с OPC DA сервера, передобавил их уже на полной версии , и все ок , что за глюк не понял , все переменные вносились в проект , почти одновременно, но ответ на первый вопрос важнее , не охота плодить прямые скриптыОт переноса проекта на полную версию или Demo-Time ничего не меняется, эти версии не отличаются ничем, кроме ограничения по времени работы. Какие именно сообщения выдал сервер касаемо DA-переменных? Можем предположить что это были сообщения о том, что переменная в указанным адресом не существует на OPC-сервере. Если это так, значит на объекте куда Вы перенесли проект на OPC-сервере действительно нет переменных с теми адресами которые Вы указали при разработке проекта. А когда Вы их заново импортировали с OPC-сервера, то адреса считались с OPC-сервера и стали правильными, что логично. Опишем то же самое по шагам:
по 1 вопросу , дело было не в скрипте ,а ва отображении элемента , и метода просмотра ,в общем в вэб некорректно отображаются элементы , а именно лампочкаЧто именно не так с отображением лампочки? В обычном клиенте она скрыта, а в веб видна, или что-то другое?
2) в том то и дело ,что в опс сервере я видел эти переменные со значениями с плкКонечно переменные должны быть видны в OPC-сервере. Если они не будут там видны, то в скаду их можно даже не переносить. Прочтите наше сообщение выше, мы описали подробный пример того, как у переменных на двух OPC-серверах можно сделать разные адреса. В OPC-сервере они в обоих случаях будут отображаться, но читать их с OPC-сервера придётся по разным адресам. Нужно же делать так, чтобы проекты на OPC-серверах не отличались, чтобы OPC-клиенты (включая скаду) могли читать теги с OPC-серверов по одним и тем же адресам. Также ещё раз подчеркиваем, что мы не знаем какую ошибку выдавал сервер, поэтому мы только предполагаем что могло произойти. Чтобы сказать что-то подробнее нужно знать текст ошибки которую выдал сервер скады.
Здравствуйте. Подскажите пожалуйста по вопросу. Есть скада, модуль ввода и модуль вывода. Как можно через скаду связать два модуля. То есть, при включении кнопки в модули ввода должна включиться выход на модуле вывода
Есть скада, модуль ввода и модуль вывода. Как можно через скаду связать два модуля. То есть, при включении кнопки в модули ввода должна включиться выход на модуле выводаКак правильно написал deldemo, Вам необходимо импортировать (https://simple-scada.com/help/manual/variable-import-opc.html) в скаду переменные модуля ввода и модуля вывода с Вашего OPC-сервера. Затем можно создать скрипт по событию "Изменились переменные (https://simple-scada.com/help/script/changed-the-variables.html)", добавить в него переменную модуля ввода(как описано в этом примере (https://simple-scada.com/help/script/changemulvar.html)) и написать скрипт:
begin
OutputMod.Value := InputMod.Value;
end.
Есть возможность привязать тег на сервере к комбинацию клавиш на клавиатуре? Как это можно организовать?К сожалению, такой возможности нет.
Можно подробнее?Поясните пожалуйста, что именно требуется объяснить подробнее?
добрый день ,подскажите , а почему многие скрипты не работают в вэб , например вызов окна, как то можно в будущем это изменитьС ограничениями, имеющимися при работе через web-клиент можно ознакомиться по ссылке (https://simple-scada.com/help/manual/comp-constraints.html). Привязка окон в web-клиенте, а также событие OnClick работает только у компонентов "Кнопка", "Флажок", "Список" "Фигура", "Текст", "Изображение", "Трубопровод", "Линия", "Резервуар", "Заслонка". Посмотреть пример вызова окон в web через скрипты можно на нашем web-сервере (https://web.simple-scada.com:8755/Web-demo) -> страница "Скрипты" -> пример №8. Если не получится разобраться, пришлите нам для проверки папку с проектом из директории "\Simple-Scada 2\Projects\Имя_проекта" на support@simple-scada.com и укажите какой скрипт не работает.
Подробнее о скрипте. Как два тега с опс сервера привязать.А инструкцию читать не пробовал? Говорят, что помогает иногда.
Подробнее о скрипте. Как два тега с опс сервера привязать.Добавить теги с OPC-сервера можно одним из следующих способов:
var
i: integer;
sw: string;
begin
i := 99;
while i <= 0 do
begin
sw := IntToStr(i);
if FileExists('vesy_1_'+sw.'txt', 'C:Base/') and (read.Value) then
RenameFile('C:Base/vesy_1_'+sw.'txt', 'C:Base/vesy_1.txt')
end;
i := i - 1;
end;
end.
begin
sw := IntToStr(i);
if FileExists('vesy_1_'+sw.'txt', 'C:Base/') and (read.Value) then
RenameFile('C:Base/vesy_1_'+sw.'txt', 'C:Base/vesy_1.txt')
end;
i := i - 1;
end;
Странно почему проходит компиляцию в редакторе - у вас в этом цикле один "end;" лишний, скорее всего первыйда уверен, а по поводу енда, я же писал использовал разные конструкции , и энд убирал и добавлял беги и тд всегда один результатКод: (delphi)и вы уверены, что вот здесь: "while i <= 0 do" правильное условие? Может нужно изменить знак на ">"?begin
sw := IntToStr(i);
if FileExists('vesy_1_'+sw.'txt', 'C:Base/') and (read.Value) then
RenameFile('C:Base/vesy_1_'+sw.'txt', 'C:Base/vesy_1.txt')
end;
i := i - 1;
end;
Добрый день , возможно ли сделать разбор строки , несколько значений через разделитель, ...
function GetField(aStr: string; n: integer): string;
var i, j: integer;
aStr1: string;
begin
j := 1; // номер собираемого поля
aStr1 := ''; // собственно собираемое поле
for i := 1 to Length(aStr) do begin
if aStr[i] = ';' then begin // проверить на разделитель
if j = n then begin
GetField := aStr1; // поле найдено
exit;
end;
aStr1 := ''; j := j + 1; // на следующие поле
end
else aStr1 := aStr1 + aStr[i]; // продолжить собирать поле
end;
GetField := aStr1; // поле с номером n не найдено в строке
end;
. . и второе главное , возможно из одного скрипта вызвать другой скрипт, если да то как , какой синтаксис.Единственный вызвыаемый из другого скрипт это "Изменились переменные". Можно было использовать для общего кода.
Добрый день , ещё вопрос есть скрипт в редакторе проходит компиляцию, а при сохранении нет ...Исправленный код:
var
i: integer;
sw: string;
begin
i := 99;
... и вы уверены, что вот здесь: "while i <= 0 do" правильное условие? Может нужно изменить знак на ">"?
while i > 0 do // цикл выполнится 99 раз, i >= 0 - выполнится 100 раз
begin
sw := IntToStr(i);
if FileExists('vesy_1_' + sw + 'txt', 'C:Base/') and (read.Value) then // ошибка выражения +sw.'txt'
RenameFile('C:Base/vesy_1_'+sw+'txt', 'C:Base/vesy_1.txt');
i := i - 1;
end;
end.
Добрый день , возможно ли сделать разбор строки , несколько значений через разделитель, и второе главное , возможно из одного скрипта вызвать другой скрипт, если да то как , какой синтаксис.Напрямую вызвать скрипт на выполнение из другого скрипта нельзя. Можно делать это косвенными методами (через событие "изменились переменные", "выполнен SQL-запрос"). Но вообще это обычно и не требуется. Если создать скрипт "глобальный модуль", то функции и переменные из этого модуля можно использовать в дальнейшем практически в любом другом скрипте. (насчет переменных - желательно не злоупотреблять).
И еще вопрос , может кому то покажется глупым , но подскажите правила расстановки end end; и ; и без ; ...... компилятор пропускает много вариантов их расстановки и этим путает, в других языках , все как то нагляднейСимвол ";" нужно ставить всегда в конце каждого блока кода. Примеры:
// точка с запятой в конце блока if..then:
begin
if (A = 0) then
A := 1;
end.
// точка с запятой в конце блока if..then..else:
begin
if (A = 0) then
A := 1
else
A := 0;
end.
// точка с запятой в конце блока begin..end:
begin
if (A = 0) then
begin
myProcedure_1;
myProcedure_2;
end;
end.
// точка с запятой в конце блока begin..end..else:
begin
if (A = 0) then
begin
myProcedure_1;
myProcedure_2;
end else
myProcedure_3;
end.
// точка с запятой и вложенные begin..end:
begin
begin
begin
end else
begin
end;
end;
end.
procedure WriteValueToTable_1;
var
aQuery: string;
begin
aQuery := 'INSERT INTO `vesy_1` ' +
'(`timestamp`, `silos_1`, `silos_2`, `silos_3`, `silos_4`, `ves`, `naklad`) VALUES ' +
'(NOW(), ' +Vesy_1_silos_1.AsStr+ ', ' +Vesy_1_silos_2.AsStr+ ', '
+ Vesy_1_silos_3.AsStr+ ', ' + Vesy_1_silos_4.AsStr+ ', ' + Vesy_1_ves.AsStr+ ', ' + vesy_1_naklad.AsStr+ ');';
RunSQL(aQuery, nil, 0);
end;
timestamp()
int
int
int
int
int
VARCHAR(20)
Так QuotedStr(vesy_1_naklad.AsStr) нужно.спасибо , заработало
SELECT o.`VarName`,r.`Value` FROM `route` r, `scadaobjects` o
aVar:=GetVariableByName(DataSet.Fields[0].AsUTF8String);
if (Assigned(aVar)) and (aVar.Value=StrToInt(DataSet.Fields[1].AsUTF8String))
then ...
Level1 := 'L1=no data';
Level2 := 'L2=no data';
SendSMS(Field_PhoneNumber.AsStr, DateTimeToStr(Now) + ', s.Vozzhaevka, MK 492. Uroven mazuta: ' + Level1 + ', ' + Level2, false);
И второй вопрос: создаю внутренний тег типа String, включаю свойство "Автоматическое восстановление". В программе записываю в него номер телефона, и после перезапуска программы он не сохраняется, приходится заново его вбивать.Можете выслать проект для проверки на support@simple-scada.com с указанием проблемной переменной?
Table1.Columns[3].Cells[1].Variable.Value := 10;
var
aNum : String;
tblParam : TM_Table;
begin
aNum:=IntToStr(Sender.Tag;); // получили номер объекта
tblParam := GetTableByName('tblParam'+aNum);
if tblParam <> nil then
begin
tblParam.Tag:=IntToStr(Sender.Tag;); // передали его ниже
tblParam.OnDataChangeEvent // обновили данные на объект
end;
end;
tblParam := GetTableByName('tblParam'+aNum);
if tblParam <> nil then
with tblParam as TM_Table do
begin
Columns[2].Cells[1].Text:=SecondsToStr(GetVariableByName('StepRunTimeSet_T'+aNum+'_W').AsInt64);
...
end;
begin
with Cell do
case Row of
1:Variable.Value :=Text; // попытка присвоить отображаемое значение
2:Variable.Value :=1800; // попытка писать число
...
end;
end;
Table3.Columns[2].Cells[17].Variable:=GetVariableByName('StepMonTimeSet_T01_W');
StepMonTimeSet_T02_W.Value:=3600;
Как быть в такой ситуации?очень просто... надо быть внимательней. Выводится текст, а меняется переменная... которой нет
И второй вопрос: создаю внутренний тег типа String, включаю свойство "Автоматическое восстановление". В программе записываю в него номер телефона, и после перезапуска программы он не сохраняется, приходится заново его вбивать.Переменная успешно восстанавливает своё значение, но сразу стирается скриптом "Button_ClearPhoneNumber_OnDataChange", ведь при запуске проекта переменные инициализируются и происходит выполнение OnDataChange скриптов. Используйте событие OnClick вместо OnDataChange чтобы скрипт выполнялся только по клику на объект.
Бьюсь с отправкой смс - если отправляю сообщение кирилицей, то всё нормально. Если отправляю на латинице, то приходит абракадабра (вложение).Сделали исправление при отправке на латинице, обновление будет в ближайшее время.
Переменная успешно восстанавливает своё значение, но сразу стирается скриптом "Button_ClearPhoneNumber_OnDataChange", ведь при запуске проекта переменные инициализируются и происходит выполнение OnDataChange скриптов. Используйте событие OnClick вместо OnDataChange чтобы скрипт выполнялся только по клику на объект.Изначально я так и сделал (по событию OnClick), но мне нужно было, чтобы при этом работало подтверждение на действие. А при событии OnClick, подтверждение не обрабатывается.
Изначально я так и сделал (по событию OnClick), но мне нужно было, чтобы при этом работало подтверждение на действие. А при событии OnClick, подтверждение не обрабатывается.Для этого у Вас должна быть кнопка "с фиксацией", на событие OnClick нужно назначить стирание номера телефона, событие OnDataChange нужно удалить. Да и привязку кнопки к переменной можно убрать.
Сделал сейчас очистку по событию OnClick, а OnDataChange удалил - ничего не поменялось, номер в поле при загрузке пустой.У нас после изменений в Вашем проекте всё работает как нужно, включая автовосстановление и кнопку с подтверждением. Вы перезапустили проект после внесения изменений? Если да, то пришлите текущую версию проекта, вероятно Вы что-о упустили.
По Вашему сообщению можно предположить:
Для этого у Вас должна быть кнопка "с фиксацией", на событие OnClick нужно назначить стирание номера телефона, событие OnDataChange нужно удалить. Да и привязку кнопки к переменной можно убрать.Спасибо. Изменил кнопку на "С фиксацией" и всё заработало как надо.
. . .Привязка переменной к Полю или Ячейке выводит на экран её значение в формате переменной, свойство ячейки Text не используется при редактировании ячейки.
Columns[2].Cells[1].Text:=SecondsToStr(GetVariableByName('StepRunTimeSet_T'+aNum+'_W').AsInt64);
надо привязку всё-таки сделать...
Columns[2].Cells[1].Variable:=(GetVariableByName('StepRunTimeSet_T'+aNum+'_W');
1. Объект ТЕКСТ не редактируется (пока?), переменные связаны только с событиями.
А как отображаются объекты (текстовое поле, ячейка и т.д.) у которых привязан тег сервера/переменная и есть свойство Text?
Сейчас при вводе видно число секунд (ввод пользователя), потом их преобразование в формат времени.
Обновление 2.3.6.0Бьюсь с отправкой смс - если отправляю сообщение кирилицей, то всё нормально. Если отправляю на латинице, то приходит абракадабра (вложение).Сделали исправление при отправке на латинице, обновление будет в ближайшее время.
SendSMS('79281112233', DateTimeToStr(Now) +
', s.Vozzhaevka, MK 492. Uroven mazuta: ' +
Level1 + ', ' + Level2, FALSE);
Upd: есть предположение, что это из-за текстового режима отправки. Мы отправили Вам на почту тестовое обновление сервера скады которое всегда работает в цифровом режиме. Решает ли это обновление проблему?С этим сервером работает!
EncodeTime(ЧЧ, ММ, СС); В справке описаны в разделе "Время и дата/Формирование значения"Спасибо за ответ
begin
if HourOf(Now) = 8 then // если сейчас 8 часов утра
begin
myVar1.Value := 0; // то обнуляем переменные
myVar2.Value := 0;
end;
end.
Здравствуйте. Подскажите пожалуйста, возможно ли заполнять ComboBox через SQL запрос &Да
Здравствуйте. Подскажите пожалуйста, возможно ли заполнять ComboBox через SQL запрос &Здравствуйте. Для этого нужно отправить SQL-запрос на выполнение через RunSQL (пометить запрос уникальным тегом, например 20) и в отдельном скрипте с типом события "Выполнен SQL-запрос" убедиться что это запрос с тегом = 20. Далее пройти по полученному результату запроса и добавить каждую строку в раскрывающийся список через ComboBox.AddItem. Простой пример выполнения запроса и обработки результата описан в этой статье (https://simple-scada.com/help/script/getvardb.html).
begin
if DataSet.Tag = 20 then // если набор данных помечен тегом 20
begin
ComboBox1.Clear; // очищаем список
if DataSet.IsEmpty then Exit; // прерываем выполнение, если набор данных пуст
while not DataSet.EOF do // проходим в цикле по всем строкам полученного набора данных
begin
ComboBox1.AddItem(DataSet.Fields[0].AsStr); // добавляем строку из первой колонки набора данных в список ComboBox1
DataSet.Next; // переходим к следующей строке набора данных
end;
end;
end.
Здравствуйте. Для этого нужно отправить SQL-запрос на выполнение через RunSQL (пометить запрос уникальным тегом, например 20) и в отдельном скрипте с типом события "Выполнен SQL-запрос" убедиться что это запрос с тегом = 20. Далее пройти по полученному результату запроса и добавить каждую строку в раскрывающийся список через ComboBox.AddItem. Простой пример выполнения запроса и обработки результата описан в этой статье (https://simple-scada.com/help/script/getvardb.html).
if Sender is TM_ComboBox then
with Sender as TM_ComboBox do
aQuery:='SELECT '''+Name+''', `ItemName` FROM `ItemList`;';
if (aTag = 20) then // наш запрос
if (not (DataSet.IsEmpty)) then // не пустая выборка
begin
aCombo:=GetComboBoxByName(DataSet.Fields[0].AsUTF8String); // получили имя пославшего запрос объекта
aCombo.Clear; // очистили список
aCombo.AddItem(''); // добавили (если надо) первую пустую строку
while not (DataSet.EOF) do
begin
aCombo.AddItem(DataSet.Fields[1].AsUTF8String); // заполняем список
DataSet.Next
end
end
else Exit; // если в выборке нед данных - вышли
В запрос можно включить имя ComboBox`а по OnClickЕсли нужно связать запрос с объектом, то использовать имя - плохое решение. Везде где это возможно, лучше избегать использования имён, т.к. в конечном счёте придётся искать ссылку на объект по имени, например через GetComboBoxByName и это затратная операция. Правильнее будет передавать объект напрямую в RunSQL:
RunSQL(aQuery, ComboBox1, 20);
var
aComboBox: TM_ComboBox;
begin
aComboBox := DataSet.Sender as TM_ComboBox;
end.
Есть неприятный момент - список разворачивается и тут же сворачивается.Да, вызов ComboBox.AddItem будет приводить к обновлению списка и если в этот момент список был развёрнут, то он свернётся.
begin
if Sender is TM_Image then // сначала убедимся, что скрипт вызван объектом "Изображение"
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
if GetBit(AsInt, Tag) = TRUE then
Color := clGreen
else
Color := clRed;
end.
1) есть скрипт по чтению из своей таблицы , одинаковый в 3-х местах (проект запущен , выполнение раз в 1 час и на кнопке ) интересность в том ,что таймштамп передается первый час после запуска сервера в формате год.мес.день час.мин.сек , а после примерно часа работы сервера (но было на 3-4 обновление) и далее год_мес_день час_мин_сек, почему так происходит , почему разделитель меняется?Куда передаётся? В скаду? В компонент таблица? Или в другой компонент? Или с неправильным форматом передаётся из скады в таблицу базы данных?
2)Вопрос на будущее , есть MICROSOFT SQL SERVER, зная пароль , имя пользователя и место откуда мне нужны данные , смогу я из скады выкачать данные с базы M SQLСЕРВЕРА? или не выйдет , если да то примерный скрипт для ознакомления, если можно.Сейчас можно только задать в параметрах БД проекта (меню Проект - Настройки - База данных) СУБД SQL Server. Но скада создаст в ней отдельную БД с нужной структурой и будет работать с этой БД, а обратиться к посторонним данных не получится. В будущем мы планируем дать возможность создавать отдельные подключения к множеству БД, чтобы можно было читать данные из них.
Приведите код скрипта которым выполняется заполнение таблицы.
procedure s22; var aQuery: string;
begin
aQuery := 'SELECT `timestamp`,`vesy`,`sort` value FROM `silosa_download` ' +
' WHERE ID=22 ORDER BY `timestamp` DESC limit 1;';
RunSQL(aQuery, nil, 22);
end;
if DataSet.Tag = 22 then
begin
Table4.Columns[1].Cells[1].text := DataSet[0].AsUTF8String;
Table4.Columns[2].Cells[1].text := DataSet[1].AsUTF8String;
Table4.Columns[3].Cells[1].text := DataSet[2].AsUTF8String;
end;
подскажите какие настройки должны быть для работы с OPC DA удаленным ПК, как настраивать DCONНастройки для разных OPC-серверов могут отличаться и обычно описаны в справке к OPC-серверу. Также в разных ОС настройки могут отличаться. Основные пункты настройки описаны в руководстве, в этом разделе (https://simple-scada.com/help/manual/dcom-set.html). Эти настройки проверены с ОС Windows 7 + OPC-сервер KepServerEx.
cbSubstation := GetObjectByName('cb_Substation' + IntToStr(Sender.Tag)) As TM_ComboBox;
cbObject := GetObjectByName('cb_Object' + IntToStr(Sender.Tag)) As TM_ComboBox;
cbElements := GetObjectByName('cb_Elements' + IntToStr(Sender.Tag)) As TM_ComboBox;
Можно ли его как-то в универсальную процедуру засунуть?Приведённый код уже универсальный, ведь в нём нет обращений напрямую к какому-либо объекту, вместо этого обращение через Sender. Описание универсальных скриптов можно найти здесь (https://simple-scada.com/help/script/script-types.html). Поэтому Вы можете создать в меню скриптов новый скрипт с типом "Универсальный", вставить в него этот код и далее назначить полученный скрипт любому объекту, например на событие OnClick или другое. А код будет получать объекты cbSubstation, cbObject, cbElements по тегу того объекта к которому привязан скрипт. Только если объекты cbSubstation, cbObject, cbElements являются раскрывающимися списками, то гораздо производительнее был бы поиск именно среди списков, через GetComboBoxByName (https://simple-scada.com/help/script/getcomboboxbyname.html).
Если мы в окне клиента меняем значение внутренней переменной, другие клиенты получат то же значение? Внутренние переменные хранятся на сервере?На сервере хранится всё. Все изменения также выполняются на сервере, а клиенты отображают текущее состояние проекта. Поэтому если что-то изменить в проекте, то все клиенты отобразят это изменение.
Если записываем в поле, не связанное с переменной, какое-то значение, на других клиентских машинах будет то же самое значение?
TM_Field(Sender).Value := some_var;
// либо
Sender.Value := some_var;
В универсальных скриптах как лучше обращаться к свойствам Sender - как к родителю или к потомку через преобразование.По производительности оба способа равны, поэтому можно обращаться как угодно.
2. Из имени объекта вытаскивается строка, которая обрабатывается в процедуре. Эту обработку можно делать как внутри процедуры, так и внешне, до её вызова. Что лучше отправлять в качестве параметров процедуры - объект или строку?Объект лучше.
begin
if HandMode = 1 then
Window6.ShowAll; // показать окно
end.
begin
if test1 = 1 then
testtime1 := GetClientData;
end;
if test1 = 0 then
testtime2 := GetclientData;
end;
end.
Выдает ошибку "Incompatible types"Правильно, потому что HandMode - это переменная(тип TM_Variable), а 1 это целое число(тип Integer). Если требуется сравнить значение численной переменной с числом, то нужно использовать свойство AsInt (https://simple-scada.com/help/script/varasint.html), т.е. взять значение переменной переведенное в тип Integer:
begin
if HandMode.AsInt = 1 then
Window6.ShowAll; // показать окно
end.
begin
if test1.AsInt = 1 then
testtime1.Value := Now
else
testtime2.Value := Now;
end.
Begin
if NameReportMake.AsInt = 1 then
HeadReport := "Kultura 1" ;
if NameReportMake.AsInt = 2 then
HeadReport := "Kultura 2" ;
end.
begin
if NameReportMake.AsInt = 1 then
HeadReport.Value := 'Kultura 1';
if NameReportMake.AsInt = 2 then
HeadReport.Value := 'Kultura 2';
end.
case a.value of
1: begin
a:= 0;
b := 1; end
else begin
a:= 1;
b:= 0; end
end;
Подскажите как написать скрипт по проверке состояния флажковКаждый флажок нужно привязать к отдельной переменной. По нажатию на флажок значение переменной будет меняться 0 <-> 1 (или FALSE <-> TRUE). Соответственно по значению переменной можно понять включен флажок или нет. Если значение переменной True, значит включен. Пример проверки:
begin
// если флажки A, B и D включены
if (cbA.AsBool) and (cbB.AsBool) and (cbD.AsBool) then
begin
// здесь выполняем какие-то действия
end;
end.
подскажите как правильно расставить енды и ;Вот правильный вариант:
case a of
1: // если значение "а" равно единице
begin
a := 0;
b := 1;
end;
else // для любых других значений переменной "а"
a := 1;
b := 0;
end;
if a = 1 then
begin
a := 0;
b := 1;
end else
begin
a := 1;
b := 0;
end;
Спасибо за ответ.Подскажите как написать скрипт по проверке состояния флажковКаждый флажок нужно привязать к отдельной переменной. По нажатию на флажок значение переменной будет меняться 0 <-> 1 (или FALSE <-> TRUE). Соответственно по значению переменной можно понять включен флажок или нет. Если значение переменной True, значит включен. Пример проверки:Код: (delphi)begin
// если флажки A, B и D включены
if (cbA.AsBool) and (cbB.AsBool) and (cbD.AsBool) then
begin
// здесь выполняем какие-то действия
end;
end.
Это я понимаю, просто не до конца могу понять алгоритм проверки всех флажков, просто при наличии 4 флажков, количество комбинаций с различным состоянием флажков получается, если правильно посчитал, 15 и на каждую комбинацию выполнять какое то действие ? А если флажков 5, то комбинаций становиться еще больше. И все это проверять при помощи if ?Если разных ситуаций 15, то описывать их придётся отдельно, ведь они разные и уникальная комбинация не сформируется сама по себе. Можно под флажки выделить одну целочисленную переменную и каждому флажку назначить какой-то свой бит, который нужно менять (т.е. чтобы каждый флажок работал со своим битом этой переменной). Тогда любая комбинация флажков будет формировать уникальное целое число в переменной и можно будет использовать конструкцию case..of для проверки, например:
begin
case myVar.AsInt of
0: // все флажки выключены 0x0000
1: // включен первый флажок 0x0001
2: // включен второй флажок 0x0010
3: // включен первый и второй флажок 0x0011
// и так далее
end;
end.
вчера например несколько часов ловил глюк какого не было , использовал описанные в скрипте временные переменные, так вот пока перед самым использованием я их не обнулил , лезли непонятные числа.Здесь нет никаких "глюков". Если Вы объявили локальную переменную в скрипте, то в момент выполнения скрипта ей будет выделена память и значение её будет совершенно случайным (зависит от структуры оперативной памяти в момент запуска скрипта) до тех пор, пока Вы не инициализируете переменную первым значением. Поэтому нужно обязательно инициализировать локальные переменные перед использованием. Подробнее о инициализации переменных можно прочесть на любом сайте по запросу "локальные переменные delphi" (или object pascal), например здесь (https://delphicomponent.ru/109-globalnye-i-lokalnye-peremennye-v-delphi.html), а также в этом разделе руководства по скриптам (https://simple-scada.com/help/script/compilerchange.html).
так вот данные не рассчитываются , равны всегда 0, но если перенести в один скрипт то все отлично и данные рассчитываются как надо...Значит скрипты написаны неправильно, либо Вы в расчетах используете локальные переменные, которые в каждом скрипте свои и никак друг от друга не зависят. Можем гарантировать что программа скриптов всегда выполняется скадой одинаково (ведь это программа) в соответствии с её алгоритмом, независимо от того на каком ПК её запускают и от количества запусков (в ином случае ни один проект скады не мог бы работать стабильно). Если есть желание вышлите проект на support@simple-scada.com с указанием скриптов для проверки, мы назовём более точную причину по которой данные не считаются.
Можно ли как-то менять частоту опроса переменных в скрипте?Нет, частота опроса переменной задаётся на этапе разработки проекта через свойство "Частота опроса" (https://simple-scada.com/help/manual/variable-new.html). Для каждой переменной можно задать свою частоту опроса.
Т.е. таймер это какая-то переменная типа DateTime в проекте? Нужно в какой-то момент получить её значение которое она принимала в прошлом? Если так, то достаточно включить у переменной архивацию по-изменению (https://simple-scada.com/help/manual/index.html?variable-new.html#var_archive) и затем можно будет получить значение переменной из архива за указанное время при помощи процедуры ArchieveValueByTime (https://simple-scada.com/help/script/archivevaluebytime.html).
Беда в том, что переменная таймера не архивируется нормально, и принимает значение, не соответствующее миллисекундам, например.Переменная таймера архивируется нормально, просто формат у неё - TDateTime.
В примере ниже - график переменной таймера из демо проекта, добавлена только архивация по изменению
// допустим переменная для миллисекунд называется MSec
MSec.Value:= MilliSecondsBetween(Variable.Value, 0);
var
i, j, maxi, maxj: integer;
cell: TM_TableCell;
begin
maxi:= Table1.ColumnsCount - 1;
maxj:= Table1.Columns[0].CellsCount - 1;
for i:= 0 to maxi do
begin
for j:= 0 to maxj do
begin
cell:= Table1.GetCell(i, j);
cell.Color:= RandomColor;
end;
end;
end.
Да, можно, оказывается. :)Большое спасибо! Table1.GetCell(i, j) -ключевой момент для меня был. Все получилось.
case AsInt of
0 : begin
Table1.GetCell(1, 0).Text := 'Stop';
Table1.GetCell(1, 0).Color := clRed;
Как правильно написать код, чтоб при нуле разом и цвет поменялся и текст?Для этого нужно использовать конструкцию begin...end:
case AsInt of
0 :
begin
Table1.GetCell(1, 0).Text := 'Stop';
Table1.GetCell(1, 0).Color := clRed;
end;
end;
Всем хорошего дня , подскажите как наиболее коротко и оптимально проверить строку, что она не сможет преобразоваться в интежер, т.е. в ней есть посторонние символы и буквыЗдравствуйте!
begin
try
Text1.Text := IntToStr(StrToInt(aSTR.AsStr)); // попытались
except
Text1.Text := 'Увы, это не переводимая игра слов C(К/ф""Бриллиантовая рука"")'; // не удалось
end.
var
I: Integer;
begin
I := StrToIntDef(myString, -999); // в случае неудачи вернуть значение -999
if I = -999 then
Log_Add('Ошибка!');
end.
Здравствуйте.
Ещё часто подходит функция StrToIntDef (https://simple-scada.com/help/script/strtointdef.html), которая в случае неудачи возвращает заданное вами значение. Например:
Чем try, как общий случай, то плох?Почему плох? Мы же не случайно написали: "Ещё часто подходит функция StrToIntDef". Про "try..except" не стали писать ещё раз, т.к. Вы описали этот вариант в пред. сообщении. Но минус у "try..except" есть, он состоит в том, что каждый раз когда блок исключения будет выполняться, в журнал сервера будет выводиться аварийное сообщение, что может помешать просмотру других сообщений, если таких случаев будет много. Для StrToInt это будет сообщение "Ошибка в скрипте "имя_скрипта" в строке N. 'text' is not a valid integer value".
Вопрос по отправке телеграм-сообщений: можно ли вставлять переменную в текст сообщения?Конечно, например:
begin
SendTelegram('Значение переменной vrMyVar равно ' + vrMyVar.AsStr);
end.
begin
MyVarCorrect.Value := MyVar.Value + 4;
end.
Здравствуйте. У кнопки есть свойство "Бит" (https://simple-scada.com/help/manual/button.html) которое отвечает за то какой бит переменной будет меняться при нажатии на кнопку. Просто введите номер бита который нужно менять в это свойство (биты нумеруются с нуля). И не забудьте привязать саму кнопку к переменной в которой нужно менять бит.
var
aValue: Integer;
begin
aValue := lampa.AsInt;
if knopka_2.Value then
begin
aValue := SetBit(aValue, 2, true);
end else
begin
aValue := SetBit(aValue, 2, False);
end;
lampa.Value := aValue;
end.
каким образом менять макс и мин у шкалы тренда?Тренд берёт шкалу из переменной. Откройте окно редактирования переменной (https://simple-scada.com/help/manual/variable-new.html) и назначьте ей ту шкалу в которой "плавает" значение переменной. Если Вы хотите расположить тренд в каком-то произвольном месте, то включите у тренда свойство "задать положение" и через свойства "от" и "до" настройте положение тренда. Описание всех этих свойств доступно в руководстве по ссылке (https://simple-scada.com/help/manual/index.html?editor-trends.html#trend_prop).
т.е. каким образом сделать автоматическое маштабирование тренда в зависимости от переменной?
Если ли возможность где-то почитать про параллельные процессы в Скаде?Параллельно выполняются только тяжелые операции или операции, которые могут занять время, такие как вычисление архивных значений (https://simple-scada.com/help/script/work-with-archive.html), отправка e-mail или telegram-сообщений, построение отчетов. Работают следующим образом: Вы вызываете построение отчета в скрипте, но скада не строит его в тот же момент, вместо этого отчет начинает строиться в отдельном потоке, а код скриптов продолжает выполняться дальше. Вся эта информация никак не касается очереди скриптов и её переполнения и её можно не знать.
Сейчас очередь скриптов часто переполняетсяЗдесь всё очень просто. Все скрипты в скаде добавляются в очередь скриптов в том порядке в котором они были вызваны и выполняются последовательно друг за другом. Никакой параллельности у самих скриптов нет (два скрипта никогда не будут выполняться одновременно), т.к. это привело бы к хаотичности и нестабильной работе. Чтобы очередь скриптов переполнилась, нужно сделать в скриптах грубые ошибки, например зациклить скрипты на самих себя, чтобы очередь росла бесконечно. Или добавить в какой-то скрипт бесконечный цикл, чтобы он никогда не выполнился и соответственно не дал выполниться другим скриптам. Пример зацикленных друг на друга скриптов:
// скрипт по изменению переменной vrA
procedure vrA_OnDataChange(Sender: TM_Control);
begin
vrB.Value := Random(100); // меняем значение переменной vrB
end.
// скрипт по изменению переменной vrB
procedure vrB_OnDataChange(Sender: TM_Control);
begin
vrA.Value := Random(100); // меняем значение переменной vrA
end.
Также в скаде есть события "Запуск проекта" и "Полностью запущен".Скрипты с типом события "Запуск проекта" выполняются один раз - при запуске проекта на сервере.
procedure SetSPToTag(spTag: TM_Variable; spValue: double; spType: byte);
begin
DebugSP('[SetSPtoTag] spValue = ' + FloatToStr(spValue));
case spType of
1: begin
spTag.HighWarning := spValue;
DebugSP('[SetSPtoTag] ' + spTag.Name + ' SP1 set: ' + FloatToStr(spTag.HighWarning));
end;
2: begin
spTag.HighAlarm := spValue;
DebugSP('[SetSPtoTag] ' + spTag.Name + ' SP2 set: ' + FloatToStr(spTag.HighAlarm));
end;
end;
end;
procedure SetTagsSPs(objIndex: byte);
var
spTag, mConstTag: TM_Variable;
spNum, spType: byte;
objectId, mConstNumStr: string;
begin
DebugSP('[SetTagsSPs] in: objIndex = ' + IntToStr(objIndex));
objectId := GetObjectIdByIndex(objIndex);
for spNum := 1 to MCONST_SP_COUNT do
begin
spTag := GetVariableByName(objectId + '_' + mConstSPTag[spNum]);
if Assigned(spTag) then
begin
spType := 2;
repeat
mConstTag := GetVariableByName(objectId + '_MCONST_' + intToStr(mConstSPNum[spType, spNum]));
if Assigned(mConstTag) then
begin
DebugSP('[SetTagsSPs] ' + mConstTag.Name + ' = ' + mConstTag.AsStr);
SetSPToTag(spTag, mConstTag.AsFloat, spType);
end;
Dec(spType);
until spType = 0;
end;
end;
end;
begin
{ открываем текстовый файл MyTextFile.txt из папки "\Simple-Scada\Projects\Папка_проекта\User files\"
для добавления записи в конец файла }
if TextFileOpen('MyTextFile.txt', '', fomAppend, fcpUTF8) then
begin
TextFileWriteLn(MyVar.AsStr); // записываем значение переменной "MyVar" в конец файла
TextFileClose; // закрываем файл
end;
end.
Подскажите , а можно как то скриптами контролировать , есть ли связь с опс сервером?Можно воспользоваться перечисленными ниже способами и например выводить предупреждение при потери связи с удаленным ПК или ПЛК/устройствами, которые работают через этот OPC.
Здравствуйте.Ясно. Спасибо за информацию.
Через скаду нельзя прочитать эти атрибуты. Скада во время работы подписывается на нужные теги и получает каждое изменение тега с атрибутами "значение" + "метка времени" + "качество тега". Во время импорта тегов через редактор скада читает "имя" + "тип данных" + "описание".
возвращается не более 3535 - это что-то странное. Такого лимита точно нет. Вы уверены что строк именно 35? Запрос точно выбирает более 35 строк? Используете web-клиент, или обычный клиент? Версия скады выше чем 2.2.6.0?
35 - это что-то странное. Такого лимита точно нет. Вы уверены что строк именно 35? Запрос точно выбирает более 35 строк? Используете web-клиент, или обычный клиент? Версия скады выше чем 2.2.6.0?
Подскажите пожалуйста , наиболее простой, автоматический способ распечатать содержимое окна , и желательно только его содержимое , в окне находиться таблица , вот её мне и надо распечатать .
begin
if GetBit(control.AsInt, 0) then begin
Y7.Value := int(X7.Value); // таймер интервала X7 - уставка, Y7 - текущее значение
DTstart.Value := now; // метка времени начала
TextFileOpen('PrintTable.csv', '', fomRewrite, fcpANSI);
TextFileWriteLn(''); // файл с пустой строкой
TextFileClose;
end;
end.
var i: integer;
aReport: TM_Report;
begin
if GetBit(control.AsInt, 0) then
if CompareDateTime(Y7.Value, X7.Value) = -1 then
Y7.Value := IncSecond(Y7.AsDateTime, 1) // счет таймера
else
if Table1.Tag = 0 then begin // Table1.Tag = 0 - запись файла
TextFileOpen('PrintTable.csv', '', fomRewrite, fcpANSI); // запись данных в CSV-файл
TextFileWriteLn(';;'); // заголовок (имена столбцов)
with Table1 as TM_Table do
for i := 1 to 6 do
TextFileWriteLn(UTF8ToString(GetCell(0,i).Text) + ';' + GetCell(1, i).Variable.AsStr + ';' + GetCell(2, i).Variable.AsStr);
TextFileWriteLn(' Интервал;' + TimeToStr(DTstart.AsDateTime) + ';' + TimeToStr(Now)); // время начала и конца процесса
TextFileClose;
Table1.Tag := 1;
end
else begin // Table1.Tag <> 0 - ожидание завершения записи файла и построение отчета
if TextFileOpen('PrintTable.csv', '', fomReset, fcpANSI) then
if TextFileReadLn = ';;' then begin // первая строка содержит заголовок, файл записан
aReport := ReportBuild('Print_1');
aReport.View(ClientName.AsStr); // осталось дождаться отчета для просмотра или печати (.Print)
Table1.Tag := 0; // сбросить флаг построения отчета
control.Value := 0; // завершено - отпустить кнопку и
end;
TextFileClose;
end;
end.
В отчете 'silos_1' выявлена ошибка компиляции:'Извините за задержку с ответом, почему-то не пришло уведомление об ответе в теме.
Класс "Reports.silos_1.silosDataSource" уже содержит определение для "PK_"
....как выполняется скрипт если нет вызывающего скрипт события...Рискну предположить, что никак не выполняется.
...Могу ли я просто написать скрипт как программу которая будет что то рассчитывать и присваивать результат внешней переменной?...А что мешает?
не понятно как выполняется скрипт если нет вызывающего скрипт событияСкрипты выполняются при возникновении события, к которому они привязаны, это могут быть события объектов (https://simple-scada.com/help/script/object-events.html) или события, доступные при создании нового скрипта (https://simple-scada.com/help/script/event-types.html). Если скрипт не связан ни с каким событием, то он не будет выполняться.
написать скрипт как программу которая будет что то рассчитывать и присваивать результат внешней переменнойЕсли требуется производить расчеты на основе каких-либо переменных, то можно использовать скрипт по событию "Изменились переменные" (https://simple-scada.com/help/script/changemulvar.html), добавив в него необходимые переменные - такой скрипт будет выполняться при изменении любой переменной из списка. Если требуется выполнять скрипт периодически через какой-то интервал времени, то можно использовать скрипт с типом события "Прошла секунда (https://simple-scada.com/help/script/second-passed.html)", добавив в него условие интервала, как в примере по ссылке (https://simple-scada.com/help/script/second-passed.html). Для максимального быстродействия проекта следует правильно подбирать событие, по которому должен выполняться скрипт, например если требуется производить сложение нескольких переменных, то лучше использовать скрипт по событию "Изменились переменные"(а не "Прошла секунда"), тогда скрипт будет выполняться только при изменении переменных, а не каждую секунду.
Добрый день,Тоже ломал голову, как не используя таймеры озвучивать динамические сообщения.
Как при наступлении определенных условий проиграть несколько звуковых файлов подряд?Код: (delphi)if temp100.value = 10 then
begin
PlayUserSound("brew",'1.ogg',false);
PlayUserSound("brew",'2.ogg',false);
PlayUserSound("brew",'3.ogg',false);
end;
при таком написании воспроизводится только файл 3.ogg
var
aHTTP: TM_HTTP;
aPOST: TM_HTTPPost;
begin
aPost := TM_HTTPPost.Create; // создаем данные для отправки
aPost.Add('username=admin'); // !!!компилятор не ругается, а в логе сервера ОШИБКА Access violation at address!!!
aPost.Add('password=my_password'); // добавляем второй параметр
aHTTP := RequestHTTP; // создаем экземпляр запроса
aHTTP.Post('http://mysite.com/index.html', aPost); // вызываем POST
end.
PostData := 'log=123&pass=456';
S := 'POST /a.php HTTP/1.0'#13#10+ // тип запроса (POST) и страница, к которой обращаемся
'Host: 127.0.0.1'#13#10+ // имя сервера
'Connection: Close'#13#10+ // закрывать подключение после того, как сервер вернёт данные
'Content-Type: application/x-www-form-urlencoded'#13#10+ // в каком формате передаются данные
'Content-Length: '+IntToStr(Length(PostData))+#13#10+ // размер данных
#13#10+PostData; // сами данные
var
aHTTP: TM_HTTP;
aQuery: string;
begin
// формируем текст HTTP запроса
aQuery := 'http://127.0.0.1/a.php?log=123&pass=456';
aHTTP := RequestHTTP; // создаём экземпляр запроса
aHTTP.Get(aQuery, 0); // вызываем GET с запросом aQuery
end.
Также не понятна нужная структура запроса серверу. "Обвязку" запроса нужно самому писать или aHTTP.Post делает это сам?Стандартные заголовки запроса, как Host, Connection, Content-Type, Content-Length формируются автоматически и обычно их не нужно прописывать. Но при желании можно задать свои заголовки, например:
Вот такие подобные конструкции нужны?
aHTTP := RequestHTTP;
aHTTP.SetHeader('Host', '127.0.0.1');
aHTTP.SetHeader('Connection', 'Close');
aHTTP.SetHeader('ContentType', 'application/x-www-form-urlencoded');
DataSet.Fields[0].AsInt
UPD: исправление с конструктором класса TM_HTTPPost добавили в обновление 2.3.6.12;
Если добавляю любые символы, в aPost.Add('login":user@cloud.by'); -не важно что внутри Add, даже хотя бы один символ , то получаю ответ в Response: HTTP/1.1 400 Bad requestКонечно, ведь если отправлять какие-то произвольные значения серверу, то сервер их не поймёт и выдаст сообщение о том, что запрос неправильный, что и происходит в Вашем случае (400 Bad request). Запрос нужно посылать строго в том формате, который требует сервер, никаких лишних символов в нём быть не должно.
Если делаю зарос без строчки aPost.Add(' '); то получаю ответ в Response: HTTP/1.1 401 UnauthorizedТоже верно, ведь теперь Вы не посылали никаких непонятных серверу данных и ошибки "Bad request" не произошло. Но при этом Вы и не авторизовались, о чём и говорит сервер сообщением "401 Unauthorized".
aPost.Add('login":user@cloud.by');В этом запросе пропущена двойная кавычка в самом начале, очевидно что он не будет правильно распознан сервером. Возможно правильный формат такой: aPost.Add('"login":user@cloud.by');
Если делать просто GET запрос типа 'http://127.0.0.1/a.php?log=user@cloud.by&pass=123456', то ответ от сервера такой:Это уже больше похоже на рабочую версию запроса, т.к. сервер дал понятный ответ "Неверный email или пароль". Вы точно уверены в том что указали верный e-mail и пароль? Также обратите внимание, что в этом GET запросе Вы передаёте логин как параметр с именем "log", но в других местах Вы пишете "login", а также "pas" и "password". Ваш сервер допускает оба варианта "log" / "login", "pas" / "password"? Или всё таки какой-то один вариант. Если сервер требует имя параметра "login", то указывайте именно так, т.е.:
{"name":"Exception","message":"Неверный email или пароль.","code":0,"status":401,"type":"customException","error_status":1}
Вообще, как-нибудь возможно "подсмотреть" что отправляется в запросе aHTTP.Post и в каком виде?Можно попробовать снимать трафик через Wireshark или подобное ПО, но зачем, если ответы сервера дают нужную информацию, как описано выше.
{Это JSON-формат запроса, его обычно передают через TStringStream, который мы не выносили в скрипты, но можем добавить в будущем (обычно для JSON также указывают ContentType как json). Сейчас можно попробовать и напрямую:
"login":"user@cloud.by",
"password":"123456"
}
var
aHTTP: TM_HTTP;
aPOST: TM_HTTPPost;
begin
aPost := TM_HTTPPost.Create; // создаем данные для отправки
aPost.Add('{"login":"user@cloud.by","password":"123456"}'); // добавляем данные
aHTTP := RequestHTTP; // создаем экземпляр запроса
aHTTP.SetHeader('ContentType', 'application/json');
aHTTP.Post('http://127.0.0.1/a.php', aPost); // вызываем POST
end.
По API мне после заголовка нужно отправлять...Если у Вас есть описание API, то дайте нам ссылку на него (можно выслать на support@simple-scada.com), тогда было бы понятно какие запросы требует сервер и вышеописанные вопросы решатся сами собой.
begin
if Sender is TM_Button then
with Sender as TM_Button do
if GetBit(VariableEx.Value , 0) = false and GetBit(VariableEx.Value , 1) = false and GetBit(VariableEx.Value , 2) = false then
setState(1); // это фантазии
else
setState(0); // это фантазии
end.
Нужно отобразить 4ю кнопку "Неактивное"....Здравствуйте!
Здравствуйте!
Это кнопка с восьмью состояниями. Бонусом - индикация запрещенных комбинаций. При отсутствии оных - можно обойтись кнопкой с пятью состояниями. См. пример во вложении.
Но этот пример не совсем решает мою проблему. Во вложении примерный вид элемента управления как я его себе представляю.Здравствуйте!
Оч удобно и красиво получилось сделать с помощью button с enabled = false. По факту это не кнопка уже, красивая такая метка.Для таких целей правильнее использовать компонент "Текст (https://simple-scada.com/help/manual/text.html)", его внешний вид можно настроить также как у кнопки на Вашем скриншоте(см. вложение).
На первых страницах темы был пример реализации таймера наработки оборудования https://simple-scada.com/forum/index.php?topic=145.30Для такой задачи сейчас можно использовать процедуры для работы с таймерами (https://simple-scada.com/help/script/timers.html). Пример можно найти в демо-проекте -> страница "Скрипты" -> "Простые скрипты(1)" -> Пример №3. Также, время наработки можно получить по архивным данным переменой при помощи процедуры ArchiveTimeOn (https://simple-scada.com/help/script/archivetimeon.html). Если требуется создать отчет времени наработки/простоя, то пример такого отчета можно найти по ссылке (https://simple-scada.com/help/report/rep-time-work-idle.html).
// скрипт по изменению переменной vrA
procedure vrA_OnDataChange(Sender: TM_Control);
begin
vrB.Value := Random(100); // меняем значение переменной vrB
end.
// скрипт по изменению переменной vrB
procedure vrB_OnDataChange(Sender: TM_Control);
begin
vrA.Value := Random(100); // меняем значение переменной vrA
end.
begin
SendTelegram('Значение переменной = ' + MyVar.AsStr);
end.
Вопрос, при записи в текстовый файл, необходимо записать информацию на кириллице (по русски) перепробовал уже кучу кодировок в функции открытия файла ,а в блокноте все равно в файле не читается информация по русски , вопрос какую кодировку использовать для вывода файл , что бы блокнотом читалось нормально.Используй юникод.
// Раскраска ячеек таблицы в зависимости от значения температуры термоподвески
procedure TableColorCell();
var
iRow, iCol : integer;
aCell: TM_TableCell;
aVal : Single;
begin
if Table_term.RowCount > 0 then
for iRow := 1 to (Table_term.RowCount-1) do // Цикл со второй строки таблицы до конца
for iCol := 2 to (Table_term.ColumnsCount-1) do // Цикл с третьего столбца таблицы до конца
begin
aCell := Table_term.GetCell(iCol, iRow); // Получим нужную ячейку
if aCell <> nil then
begin
aVal := aCell.Variable.AsSingle; // получим значение в ячейке
if aVal >= Tmax_warning then // Если значение больше предупредительной уставки, то
if aVal >= Tmax_alarm then // Если значение больше аварийной уставки, то
aCell.Color := clIndianRed // Раскрашиваем фон ячейки в красный цвет
else
aCell.Color := RGB(255,255,128) // Раскрашиваем фон ячейки в желтый цвет
else
aCell.Color := clWhite; // Раскрашиваем фон ячейки в белый цвет, если значение в норме
end;
end;
end;
TextFileWriteLn(UTF8ToString(DateTimeToStr(Now)) +' Создан новый файл ');
ещё подскажите , можно ли читать текстовые файлы по сери , в расшаренных папках и как это сделать , пример пожалуйста.Можно, также как и в обычных папках, только в пути нужно указывать сетевой адрес. Но нужно учитывать, что при сбоях в сети обращения к файлам по сети могут выполняться значительно дольше, что может отразиться на скорости работы скады.
...
aCell := Table_term.GetCell(iCol, iRow); // Получим нужную ячейку
if aCell <> nil then
if aCell.Variable <> nil then
begin
...
lipvasko, вот правильный вариант:Выражение:Код: (delphi)TextFileWriteLn(UTF8ToString(DateTimeToStr(Now)) +' Создан новый файл ');
UTF8ToString(DateTimeToStr(ADateTime: TDateTime): String)
String(DateTimeToStr(Now)) + ' Создан новый файл ';
Можно, также как и в обычных папках, только в пути нужно указывать сетевой адрес. Но нужно учитывать, что при сбоях в сети обращения к файлам по сети могут выполняться значительно дольше, что может отразиться на скорости работы скады.для скады чтение по сети не отличается от чтения по локальному пути. Разница только в том, что вместо локального пути указывается сетевой.
Saurin, отправлять через разных ботов нельзя (через несколько токенов). Обычно создают одного бота (и один токен) и через него рассылают множеству получателей в чаты. Или в группу, на которую подписаны получатели. Можно используя Telegram API реализовать и свою отправку через GET-запросы, но это будет сложно.
begin
{ отправить Telegram-сообщение в одну группу }
SendTelegram('Авария котла!', '258579674');
{ отправить Telegram-сообщение в две группы }
SendTelegram('Авария котла!', '258579674, 568479675');
end.
Здравствуйте, мы отвечали на этот вопрос в этой теме выше, цитата:ЦитироватьМожно, также как и в обычных папках, только в пути нужно указывать сетевой адрес. Но нужно учитывать, что при сбоях в сети обращения к файлам по сети могут выполняться значительно дольше, что может отразиться на скорости работы скады.для скады чтение по сети не отличается от чтения по локальному пути. Разница только в том, что вместо локального пути указывается сетевой.
Как разобрать такую строку, что бы получить "Мука карусель В2" из строки видаЕсли заранее известно, что искомый текст всегда начинается (например) с 28 символа, то можно сначала через UTF8Copy удалить лишние символы в начале строки, скопировав символы с 28 символа до конца строки. Далее можно в цикле проходить по символам и копировать их в отдельную строку до тех пор, пока не дойдём до места с двумя пробелами подряд. В результате останется искомая строка. Пример цикла по символам строки:
var
I: Integer;
aStr: string;
begin
aStr := 'Моя строка';
for I := 1 to Length(aStr) do
begin
if aStr[I] = ' ' then
begin
// текущий перебираемый символ является пробелом
end;
end;
end.
пробовал использовать команду копи и ютф8копи, результат в первом случае "1", во втором вместо букв "?" и в конце "1"Это значит, что в ходе преобразований строк Вы смешали строки разных типов (например UTF8String + String). Это разные типы поэтому строка при таком сложении испортится. При сложении строк их всегда нужно приводить к какому-то одному типу.
Может ли объект кнопка выполнять разные функции, в зависимости от того, какой пользователь сейчас авторизован?
begin
if auLogin = 'user1' then Button1.Tag := 1 else Button1.Tag := 0; // идентификатор пользователя 1 или неизвестного пользователя
if auLogin = 'user2' then Button1.Tag := 2; // идентификатор пользователя 2 - при необходимости использования
// в нескольких скриптах можно объявить внутренней переменной
iButton.Value := 0; // установить в исходное состояние кнопку, разделяемую двумя пользователями (и все прочие объекты и переменные)
end.
begin
Text1.Visible := not ((Button1.Tag = 2) and (iButton.AsInt <> 0)); // экранировка группы объектов
if (Button1.Tag = 1) and (iButton.AsInt <> 0) then Window1.ShowAll;
if iButton.AsInt = 0 then Window1.CloseAll;
end.
procedure MyProc(out AResult1, AResult2: Integer);
begin
AResult1 := 1;
AResult2 := 2;
end;
function mySum(A, B: Integer): Integer;
begin
Result := A + B;
end;
var
aNum1, aNum2, aSum: Integer;
begin
aNum1 := 10;
aNum2 := 15;
aSum := mySum(aNum1, aNum2); //как вызвать правильно несколько результатов? можно ли их вызывать по отдельности?
end.
procedure MyProc(out AResult1, AResult2: Integer);
begin
AResult1 := 1;
AResult2 := 2;
end;
var
aRes1, aRes2: Integer;
begin
MyProc(aRes1, aRes2);
// здесь переменные aRes1 и aRes2 будут равны 1 и 2. Т.к. их изменила процедура MyProc.
end;
procedure MyProc(AInp1, AInp2: Integer; out AResult1, AResult2: Integer);
begin
if (AInp1 = 1) then AResult1 := 1 else AResult1 := 0;
if (AInp2 = 1) then AResult2 := 1 else AResult2 := 0;
end;
var
aRes1, aRes2: Integer;
begin
MyProc(a1.value, a2.value, aRes1, aRes2);
end;
if not DataSet.IsEmpty then
begin
DataSet.First;
Repeat
//Ваш код
DataSet.Next;
Until DataSet.EOF;
end;
end;
Доброго дня, подскажите, можно как то найти файл определенного расширения, не зная его имени , по дате создания, т.е. нужен последний созданный файл в папке с определенным расширением.Нужно использовать функцию ScanDirectory. Она подробно (с подходящим примером) описана в руководстве (https://simple-scada.com/help/script/scandirectory.html). По отметке времени ".Timestamp" можно определить файл созданный/отредактированный последним и работать с ним. Расширение файла можно проверить через свойство ".Ext".
Подскажите, формирую таблицу вот таким скриптом Table1.RunSQL(aQuery, tsAll); А как узнать, что таблица сформирована и с ней можно работать?К сожалению узнать о выполнении SQL-запроса отправленного от таблицы - нельзя. Постараемся добавить таблице отдельное событие на этот случай.
вот я и думаю, что может скрипт обработки выполняется раньше, чем успевает заполниться таблица.Да, так и происходит.
Возможно открыть цвет заголовка таблицы для записи?Да, откроем в одном из будущих обновлений.
закрыть окно "крестиком"... и отслеживать его нажатие или отжатие тоже нельзя.Отслеживать можно. Крестик закрывает окно, соответственно в окне вызывается событие OnClose. В нём можно сбрасывать цвет кнопки.
begin
if Sender is TM_Image then // проверяем, что Sender это объект
with Sender as TM_Image do // приводим Sender к типу "TM_Image"
case AsInt of
0: Frame := 1; // изменить кадр на стоп
1: Frame := 2; // изменить кадр на работа
2: Frame := 3; // изменить кадр на ОП
3: Frame := 4; // изменить кадр на ОП
4: Frame := 5; // изменить кадр на ПП
end;
end.
begin
if Sender is TM_Shape then
with Sender as TM_Shape do
case VariableEx.AsInt of
0: Color := clGray;
1: Color := clRed;
end;
end.
Например, есть поле FieldВ скрипте происходит работа с объектом как с "Фигурой (https://simple-scada.com/help/manual/figure.html)"(TM_Shape) и если данный скрипт назначен на событие Поля, то он не будет выполняться. Если указанный скрипт нужно применить к объекту Поле, то нужно изменить его так:
begin
if Sender is TM_Field then
with Sender as TM_Field do
case VariableEx.AsInt of
0: Color := clGray;
1: Color := clRed;
end;
end.
begin
Text1.Text := 'Строка 1' + CharToStr(#10) + 'Строка 2';
end.
begin
if Sender is TM_Text then // проверяем, что Sender это текст
with Sender as TM_Text do // приводим Sender к типу "TM_Text"
if Mode.VALUE=TRUE then
begin
case AsInt of
0: begin Text := 'РАБОТА В АВТОМАТИЧЕСКОМ РЕЖИМЕ ОСТАНОВЛЕНА'; Color := clWhite; end;
1: begin Text := 'РАБОТА В АВТОМАТИЧЕСКОМ РЕЖИМЕ'; Color := clBlue; end;
2: begin Text := 'АВТОМАТИЧЕСКИЙ РЕЖИМ. АВАРИЯ!'; Color := clRed; end;
end;
end else
begin
Text := 'РЕЖИМ НАЛАДКИ'; Color := clWhite;
end;
end.
Большое спасибо за оперативность. Теперь всё работает как надо!Подскажите, формирую таблицу вот таким скриптом Table1.RunSQL(aQuery, tsAll); А как узнать, что таблица сформирована и с ней можно работать?К сожалению узнать о выполнении SQL-запроса отправленного от таблицы - нельзя. Постараемся добавить таблице отдельное событие на этот случай.
UPD: в версиях 2.4.0.3 и выше у таблицы есть событие OnDoneSQL для отслеживания выполнения SQL-запросов
Делаю всё так, как в руководстве, Бд проверяю, переменные архивирую, но в отчётах ничего нет.Можем гарантировать, что если создать отчет строго в соответствии с примерами (https://simple-scada.com/help/report/sample-reports.html), то он точно будет работать без ошибок.
При формировании отчёта по этому примеру: https://simple-scada.com/help/report/per-rep.html предварительный просмотр выдает ошибку по интервалу - он не подтягивается из БД. И вообще ничего не подтягивается. Хотя БД работает, подключена, проверяется, данные архивируютсяПеременную-интервал не требуется архивировать в БД. Значение интервала можно задать в редакторе отчетов(описано в середине примера периодического отчета (https://simple-scada.com/help/report/per-rep.html)) или если требуется изменять интервал из скады, то можно связать переменную-интервал отчета с нужной переменной проекта. Для начала можно создать простейший отчет на примере периодического отчета (https://simple-scada.com/help/report/per-rep.html) и затем, когда будет понятен принцип работы с системой отчетов, вносить в него необходимые изменения. На скриншоте видно, что в отчете имеются ошибки в выражениях, которые нужно исправить. Убедитесь, что у переменной-интервал задано корректное значение(см. скриншот во вложении), возможно вместе с числом по ошибке были введены какие-либо лишние символы. Если не разберетесь, пришлите нам на support@simple-scada.com текущую версию проекта для проверки.
Пробовала функцию COUNT, но компилятор выдаёт ошибки.Если компилятор выдает ошибки, значит они точно есть и их нужно исправить. Ошибка на скриншоте может выдаваться, например если в отчете отсутствует источник с именем "Источник2", но при этом он используется в выражении. Без проверки отчета сложно сказать в чем именно ошибка.
{CountIf(Данные.Уровень == 64)}
Здравствуйте.
Опишите подробнее, какую задачу Вы хотите решить и для чего хотите использовать шаблонные подстановки в скрипте?
begin
if AI%Номер канала%_inuse.LimitType = ltAlarmsAndWarnings then AI%Номер канала%_inuse.LimitType := ltNone
else AI%Номер канала%_inuse.LimitType := ltAlarmsAndWarnings;
end.
begin
if Sender is TM_Button then // проверяем, что Sender это кнопка
with Sender as TM_Button do // приводим Sender к типу "TM_Button"
if VariableEx.LimitType = ltAlarmsAndWarnings then VariableEx.LimitType := ltNone
else VariableEx.LimitType := ltAlarmsAndWarnings;
end.
{DateDiff(
(DateTime)Данные.Время,
StrToNullableDateTime((string)Previous(Данные, "Время"))
)}
{
PreviousIsNull(Источник2, "Время") ?
TimeSpan.FromSeconds(0) :
DateDiff(
(DateTime)Источник2.Время,
(DateTime)Previous(Источник2, "Время")
)
}
{MinTime(
PreviousIsNull(Источник2, "Время") ?
TimeSpan.FromSeconds(0) :
DateDiff(
(DateTime)Источник2.Время,
(DateTime)Previous(Источник2, "Время")
)
)}
{MaxTime(
PreviousIsNull(Источник2, "Время") ?
TimeSpan.FromSeconds(0) :
DateDiff(
(DateTime)Источник2.Время,
(DateTime)Previous(Источник2, "Время")
)
)}
var
v_cycle: shortint;
sm: smallint;
aVar: TM_Variable;
vName: UTF8string;
begin
if First_start.Value = 0 {and (имя.скрипта.IsFirstChange = false)} then //защита от записи
begin
vName := UTF8Encode(Variable.Name);
UTF8Delete(vName,8,1);
aVar := GetVariableByName(UTF8ToString(vName));
if Assigned(aVar) then
begin
sm := Variable.OriginalAsInt;
sm := sm * 10;
if GetBit(sm,15) = true then
begin
v_cycle := 0;
while v_cycle < 15 do
begin
sm := InverseBit(sm,v_cycle);
v_cycle := v_cycle + 1;
end;
sm := sm + 1;
end;
aVar.Value := sm ;
end;
end;
end.
Добрый день!Здравствуйте.
Прошу помощи.
Требуется скрипт.
присвоение переменной DeltaT.value разницы T1.Value через каждые 10 секунд.
const
INTERVAL = 10; // Интервал таймера 10 сек
begin
vrTimer.Value := vrTimer.Value + 1; // накапливаем секунды в vrTimer
// если прошло больше, чем INTERVAL секунд с последнего срабатывания таймера
if vrTimer.Value >= INTERVAL then
begin
// код размещенный здесь будет выполняться каждые 10 секунд
DeltaT.Value:= T2.Value - T1.Value; // присваиваем переменной DeltaT.Value результат вычисления T2.Value - T1.Value
vrTimer.Value := 0; // обнуляем счетчик
end;
end.
Здравствуйте.присвоение переменной DeltaT.value разницы T1.Value через каждые 10 секунд.Не совсем понял разницы T1.Value с чем.
const
INTERVAL = 10; // Интервал таймера 10 сек
var temporaryT1: <тип T1>;
begin
vrTimer.Value := vrTimer.Value + 1; // накапливаем секунды в vrTimer
// если прошло больше, чем INTERVAL секунд с последнего срабатывания таймера
if vrTimer.Value >= INTERVAL then
begin
// код размещенный здесь будет выполняться каждые 10 секунд
temporaryT1 := T1.Value; // для разностных схем следует использовать только одно значение в качестве конечного и начального на смежных интервалах
DeltaT.Value := temporaryT1 - previousT1.Value; // присваиваем переменной DeltaT.Value результат изменения T1.Value за интервал
previousT1.Value := temporaryT1; // сохраняем как предыдущее значение для вычисления следующей разности
vrTimer.Value := 0; // обнуляем счетчик
end;
end.
. . .
в OPC изменения значения видны, но в скаде не работает, что не так?
Задача передать 32 значения в скаду одной переменной, и на каждое создать определенные скрипты для индикации состояний.
. . .
begin
if Sender is TM_Image then // сначала убедимся, что скрипт вызван объектом изображение
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
if GetBit(AsInt, Tag) then begin
FlashColor := clGreen; // включить мигание объекта зеленым цветом
Color := clWhite; // второй цвет мигания
end else begin
FlashColor := clNone; // выключить мигание объекта
Color := clBlack; // цвет объекта в этом (false) состоянии
end;
end.
begin
if Sender is TM_Image then // сначала убедимся, что скрипт вызван объектом изображение
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
if AsBool then begin
FlashColor := clGreen; // включить мигание объекта зеленым цветом
Color := clWhite; // второй цвет мигания
end
else begin
FlashColor := clNone; // выключить мигание
Color := clBlack; // цвет объекта в этом (false) состоянии
end;
end.
var i: integer;
aVar: TM_Variable;
begin
for i := 0 to 31 do begin // в реальном проекте можно ограничить цикл или индивидуальные присвоения переменным без анализа наличия
aVar := GetVariableByName('myvar' + IntToStr(i));
if aVar <> nil then aVar.Value := GetBit(Variable.AsInt, i);
end;
end.
связь переменных in32scada и набора myvar<i> осуществляет скрипт "Изменилась переменная in32scada"Так заработало!
begin
alarmstopnor48.Value := GetBit(Variable.AsInt, 0); // 1
avtomat_nor48.Value := GetBit(Variable.AsInt, 1); // 2
end.
const
TIMEOUT = 70; // пауза в секундах
begin
if (TimerGetState(Variable) = 1) then
if SecondsBetween(Variable.AsDateTime, 0) >= TIMEOUT then
begin
TimerReset(Variable);
MyVar.Value := 1;
end;
end.
Здравствуйте.Ошибка появляется строго на строке с вызовом сообщения, , повесил на кнопку этот скрипт Message11.Show(1); и снова ошибка в сервере, 11 сообщение и его состояние 1 , существуют в проекте
Значит в скрипте точно есть ошибка и нужно ее исправить. См. строку 5 скрипта и подумайте, в чем может быть ошибка. Если не разберетесь, пришлите нам папку с проектом для проверки на support@simple-scada.com.
Ошибка появляется строго на строке с вызовом сообщения, , повесил на кнопку этот скрипт Message11.Show(1); и снова ошибка в сервере, 11 сообщение и его состояние 1 , существуют в проектеИсправили. Обновление отправили Вам на e-mail.
var
timeOfPreviousValue: TDateTime; //хранит время
speedOfChanging: Double; //скорость изменения переменной (производная по времени)
s: String; //сообщение о неисправности
begin
if Sender is TM_Shape then
with Sender as TM_Shape do
speedOfChanging := 0.0;
timeOfPreviousValue := IncSecond(Now, -interval.value); //interval - глобальная переменная времени задержки
ArchiveValueByTime(TM_Shape(Sender).VariableEx, previousValue, timeOfPreviousValue); //запрос из архива значения, полученного на interval времени раньше
Text2.Text := IntToStr(TM_Shape(Sender).VariableEx.AsInt64); //для отладки
Text3.Text := IntToStr(previousValue.AsInt64); //для отладки
if previousValue.Value <> 0 then //проверка на тот случай, когда в архиве недостаточно данных
begin
if (TM_Shape(Sender).VariableEx.AsInt64 - previousValue.AsInt64) > 0 then //проверка на ноль
speedOfChanging := (TM_Shape(Sender).VariableEx.AsInt64 - previousValue.AsInt64)/interval.Value; //вычисление скорости изменения второй переменной
Text4.Text := FloatToStr(speedOfChanging); //для отладки
if speedOfChanging > 0.5 then //условие, при котором наступает третье состояние
begin
s := "Неисправность УКМ" + UTF8ToString(Sender.Hint);
AddMessage(Now, mkAlarm, s, true, true);
end;
end;
end.
изменение состояния первой может в какой-то момент синхронизироваться с частотой опроса и в этом случае скада будет считать, что устройство в первом или втором состоянииКак Вы указали: " 0 и 1 сменяют друг друга с периодом в 1 секунду". Значит Вам нужно установить частоту опроса данных переменных = 500мс., тогда описанная ситуация никогда не возникнет и не нужно будет использовать вторую переменную.
var
a: integer;
S: String;
begin
{ условия для поля }
if Sender is TM_Field then // проверяем, что Sender это поле
with Sender as TM_Field do // приводим Sender к типу "TM_Field"
if AsInt > 100 then // если значение температуры больше 100 то
begin // температура отрицательная и вычисляем ее значение
a:=0; // сбрасываем старое значение временной переменной
a:=AsInt-65536; // вычисляем отрицательную разницу температуры
S:= IntToStr(a); // преобразуем значение с минусом в текстовую строку
Text:= S; // выдаем строку в свойство текста элемента поля
end;
end.
begin
{ условия для поля }
if Sender is TM_Field then // проверяем, что Sender это поле
with Sender as TM_Field do // приводим Sender к типу "TM_Field"
if VariableEx.AsInt > 100 then // если значение температуры больше 100 то
Text:= IntToStr(VariableEx.AsInt - 65536)
else
Text:= IntToStr(VariableEx.AsInt);
end.
begin
{ условия для поля }
if Sender is TM_Field then // проверяем, что Sender это поле
with Sender as TM_Field do // приводим Sender к типу "TM_Field"
if VariableEx.AsInt > 100 then // если значение температуры больше 100 то
Value := VariableEx.AsInt - 65536
else
Value:= VariableEx.AsInt;
end.
Клавиша Btn1 срабатывает со второго раза, клавиша перехода на главное окно вообще не работает.При желании, Вы можете прислать нам для проверки проект из директории "...\Simple-Scada 2\Projects\Имя_проекта" и мы скажем почему именно это происходит.
if not aaa.AsBool then
if aaa.AsBool = false then
Я попробовал создать внутреннюю переменную String и предустановить в качестве значения код смайла, но обращение к этой переменной при формировании строки возможно только посредством обращения к свойству .Value. Но в таком случае вместо смайла в строку вставляется сам код.Не нужно создавать отдельную переменную через меню "Переменные". Объявите константу в коде скрипта, или в глобальном модуле и используйте её.
const
smile: string = #$274C;
begin
//...
end.
var
sl800ProductsNames, sl800ProductsCodes: TM_StringList;
implementation
initialization
sl800ProductsNames := TM_StringList.Create;
sl800ProductsCodes := TM_StringList.Create;
finalization
FreeAndNil(sl800ProductsNames);
FreeAndNil(sl800ProductsCodes);
interface
var
sl800ProductsNames, sl800ProductsCodes: TM_StringList;
function repeatSign(const sign: string; const count: integer): string;
implementation
function repeatSign(const sign: string; const count: integer): string;
var i: integer;
temp: string;
begin
temp := '';
for i := 1 to count do temp := temp + sign;
Result := temp;
end;
initialization
sl800ProductsNames := TM_StringList.Create;
sl800ProductsCodes := TM_StringList.Create;
finalization
FreeAndNil(sl800ProductsNames);
FreeAndNil(sl800ProductsCodes);
end.
Ошибка в том, что функция repeatSign оказалась в разделе finalization. Её нужно перенести в раздел implementation:
Достаточно ли обосновано применение списков строк? Возможно проще использовать пару объектов ComboBox.
Тогда один из комбо-боксов мне придется прятать, потому что пользователь будет взаимодействовать только с одним из них. А если комбо-бокс нужен только для того, чтобы хранить список, то разве создание самого списка без визуальной оболочки не было бы логичнее?Да, конечно.
while not TextFileEOF do
AddMessage(Now, mkMessage, TextFileReadLn, True, True);
var line: String;
...
while not TextFileEOF do begin
line := TextFileReadLn;
AddMessage(Now, mkMessage, line, True, True);
end;
Коллеги, подскажите.Могу конечно ошибаться, но может функция не принимает значение переменной
Могу конечно ошибаться, но может функция не принимает значение переменнойПроверил ваше предположение. Скорее всего причина не в этом, потому что этот код работает:
var line: String;
...
line := 'test';
AddMessage(Now, mkMessage, line, True, True);
...
// warning sending
procedure sendWarning;
begin
sendDirect('А сообщения об аварии я буду отмечать таким значком - ' + ICON_WARNING);
end;
initialization
sl800ProductsNames := TM_StringList.Create;
sl800ProductsCodes := TM_StringList.Create;
finalization
FreeAndNil(sl800ProductsNames);
FreeAndNil(sl800ProductsCodes);
end.
Кстати, в ходе теста обнаружили грубую ошибку в скрипте "Global_script". Ключевое слово "end." стоит перед секциями "initialization" и "finalization", поэтому они никогда не выполняются. В результате скрипт "load_OnClick" будет прерываться на 68 строке. "end." обязательно должен быть в конце модуля:Именно это и было причиной проблемы. Большое спасибо за указание на эту ошибку, даже не знаю, сколько я бы искал еще.
var
aSubPage: TM_SubPage;
begin
{ ищем подстраницу с именем SubPage + значение свойства Тэг }
aSubPage := GetSubPageByName('SubPage' + IntToStr((Sender as TM_Text).Tag));
if aSubPage <> nil then // если подстраница существует
aSubPage.GoToSubPageClient(GetClientName); // то, перейти на нее
end.
1. Изменение свойств объекта TM_Window.H и TM_Window.W работают только при смене страницы/подстраницы. Так и задумывалось?Честно говоря, мы не предполагали что кто-то будет менять размеры окон во время работы проекта и разрабатывали окна как статические объекты постоянного размера. А свойства W, H автоматически наследовались компилятором из базового класса TM_Control. В будущем возможно сделаем их доступными только для чтения.
2. Окно автоматически закрывается при смене страницы/подстраницы. Каким образом его можно оставить?Это невозможно.
3. Компоненты в окне, которые размещены за пределами окна все равно отрисовываются.Да, поэтому в редакторе объекты не удастся переместить за пределы окна перетягиванием, или через инспектор объектов. Предполагается, что все объекты будут находиться в окне.
begin
if Sender is TM_Image then
with Sender as TM_Image do
if Frame = 1 then
Frame := 2
else
Frame := 1;
end
Все работает.Прикольно. Заменил текстуру на огонек - заработало. Вернул свою текстуру - работает. Что было - не понятно.
К огоньку (Пламя2) прицепил приведенный код и все нормально переключается.
var
aNumStr: integer; // номер строки при чтении файла
{ выгружаем коды во временную таблицу в БД }
procedure InsertDmCod;
var
aQuery: string;
begin
aQuery := 'INSERT INTO DmExplorer.dbo.TMP_DeFileRead (DFR_DmCod, DFR_TimeStamp) '+
'VALUES (' +QuotedStr(vrUploadStr.AsStr)+', '+SQLServerDateTime(Now, dttMillisecond)+ ')';
RunSQL(aQuery, nil, 3);
end;
{ выгружаем серийный номер выгрузки в таблицу БД }
procedure InsertSN;
var
aQuery: string;
begin
aQuery := 'INSERT INTO DmExplorer.dbo.CFG_DeArhiveTask (DAT_SN, DAT_TimeStamp) '+
'VALUES (' +QuotedStr(vrUploadStr.AsStr)+', '+SQLServerDateTime(Now, dttMillisecond)+ ')';
RunSQL(aQuery, nil, 5);
end;
{ выгружаем задание на печать в БД }
procedure InsertTaskPrint;
var
aQuery: string;
begin
aQuery := 'UPDATE DmExplorer.dbo.CFG_DeArhiveTask '+
'SET DAT_TaskPrint = ' +vrUploadStr.AsStr+
'WHERE DAT_TaskID = SELECT TOP(1) DAT_ID FROM DmExplorer.dbo.CFG_DeArhiveTask ORDER BY DAT_ID DESC';
RunSQL(aQuery, nil, 6);
end;
{ выгружаем задание на печать в БД }
procedure InsertTotalCod;
var
aQuery: string;
begin
aQuery := 'UPDATE DmExplorer.dbo.TMP_DeTaskInfo '+
'SET DTI_TotalCod = ' +vrUploadStr.AsStr+
'WHERE DTI_TaskID = SELECT TOP(1) DAT_ID FROM DmExplorer.dbo.CFG_DeArhiveTask ORDER BY DAT_ID DESC';
RunSQL(aQuery, nil, 7);
end;
{ вспомогательная процедура для обработки отдельной строки }
procedure ProcStr(const AStr: string);
var
I: Integer;
aBuf: string;
aQuery: string;
{ эта подпроцедура вызывается каждый раз когда из строки было извлечено
значение отделённое ";" }
procedure OnDone;
begin
if aBuf = '' then Exit; // игнорируем пустые значения
vrUploadStr.Value := aBuf; // записываем данные из буфера
if aNumStr = vrNumStrSN.AsInt then // записываем в БД сирийный номер выгрузки
InsertSN;
if aNumStr = vrNumStrPrintTask.AsInt then // записываем в БД задание на печать
InsertTaskPrint;
if aNumStr = vrNumStrTotalCod.AsInt then // записываем в БД общее количество кодов в файле
InsertTotalCod;
if aNumStr > vrFirstStrDM.AsInt then // если номер строки больше чем уставка -> записываем DM коды в TMP_DeFileRead
InsertDmCod; // записываем в БД}
aBuf := ''; // затем обнуляем буферную строку
end;
begin
aBuf := '';
for I := 1 to Length(AStr) do // проходим по каждому символу строки в цикле
if aStr[I] <> 'CR+LF' then // если текущий символ не "CR+LF", то
begin
if aStr[I] <> ' ' then // игнорируем пробелы
aBuf := aBuf + AStr[I] // добавляем символ в буферную строку
end else // если дошли до ";", то
OnDone; // работаем с полученным значением
OnDone; // вызываем завершающую процедуру напоследок
end;
{ Очищаем таблицу }
procedure CleanTableBD;
var
aQuery: string;
begin
aQuery := 'truncate table DmExplorer.dbo.TMP_DeFileRead'; //Очищаем таблицу
{ Отправляем запрос на выполнение с тегом = 1 }
RunSQL(aQuery, nil, 4);
end;
{PRG************************************************************************************************************************}
begin
imgLoad1.Visible := true;
// открываем текстовый файл для чтения
if TextFileOpen('MyCSV.csv', 'D:\Home\Simple-Scada 2 (time-demo)\Projects\DmExplorer\User files\', fomReset, fcpDefault) then
begin
CleanTableBD; // очищаем временную таблицу в БД перед загрузкой DM кодов из файла
aNumStr := 0; // сбрасываем счетчик пропуска служебных строк
while not TextFileEOF do // цикл с проходом по каждой строке текстового файла
begin
aNumStr := aNumStr + 1; // счетчик строк
ProcStr(TextFileReadLn);
end;
TextFileClose; // закрываем файл
imgLoad1.Visible := false;
end;
end.
Как избавится от запроса в каждом цикле?Вариант с накоплением запросов в списке с их последующим выполнением после заполнения списка:
interface
var
myList: TM_StringList; // объявляем список с именем myList
implementation
initialization // во время запуска проекта
myList := TM_StringList.Create; // создаём список
finalization // во время выключения проекта
FreeAndNil(myList); // !!! обязательно удаляем список
end.
procedure InsertDmCod;
begin
myList.Add('INSERT INTO DmExplorer.dbo.TMP_DeFileRead (DFR_DmCod, DFR_TimeStamp) '+
'VALUES (' +QuotedStr(vrUploadStr.AsStr)+', '+SQLServerDateTime(Now, dttMillisecond)+ ')');
end;
{PRG************************************************************************************************************************}
begin
imgLoad1.Visible := true;
. . .
MyList.Clear; // очистить список
while not TextFileEOF do // цикл с проходом по каждой строке текстового файла
begin
. . .
end;
TextFileClose; // закрываем файл
MyLisyIndex.Value := 0; // индекс текущего элемента списка
if MyList.Count > 0 then RunSQL(MyList[0], nil, 4) // запуск цикла по непустому списку
else imgLoad1.Visible := false;
end;
end.
begin
if DataSet.Tag = 4 then begin
MyLisyIndex.Value := MyLisyIndex.AsInt + 1; // индекс текущего элемента списка = параметр цикла
if MyList.Count > MyLisyIndex.AsInt then RunSQL(MyList[MyLisyIndex.AsInt], nil, 4)
else imgLoad1.Visible := false;
end;
end.
if Image6. AsBool = True then // если значение переменной изображения = 1, то
Image6. Visible := True // показать изображение
else // иначе
Image6. Visible := False; // скрыть изображение
end.
begin
if Image7. AsBool = True then // если значение переменной изображения = 1, то
Image7. Visible := True // показать изображение
else // иначе
Image7. Visible := False; // скрыть изображение
end.
begin
if Sender is TM_Image then // если скрипт вызван каким-то изображением
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
if AsBool = True then
AnimSpeed := 10
else
AnimSpeed := 0;
end.
begin
if Sender is TM_Image then // если скрипт вызван каким-то изображением
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
if AsBool = True then
AnimSpeed := 10
else
begin
AnimSpeed := 0;
Frame := 1; // показать первый кадр изображения
end;
end.
begin
if Sender is TM_Object then // проверяем, что Sender это объект
with Sender as TM_Object do // приводим Sender к типу "TM_Object"
if AsBool = True then // если значение переменной объекта = 1
Visible := True // показать объект
else
begin // иначе
Visible := False; // скрыть объект
end;
begin
if Sender is TM_Image then // если скрипт вызван каким-то изображением
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
if AsBool = True then
AnimSpeed := 10
else
begin
AnimSpeed := 0;
end;
end;
end.
begin
if Sender is TM_Image then // если скрипт вызван каким-то изображением
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
if AsBool = True then
begin
Visible := True;
AnimSpeed := 10;
end
else
begin
Visible := False;
AnimSpeed := 0;
end;
end.
Если я переименую "UserSound.ogg" (который идет с демо-проектом) в '2.ogg', то всё работает. Если же я использую свой собственный файл с таким названием (или любым другим, то при запуске скрипта ничего не происходит, в ответ тишина)Вы уверены, что звуковой файл, который Вы используете действительно сохранён в формате ogg? Также убедитесь, что проигрываемый звук не слишком тихий. Можно повторить проблему, затем выключить клиент скады и проверить лог-файл "Client-log.txt" из папки "Logs\", нет ли в нём сообщений о неудачной загрузке файла звука. Можете выслать файл на support@simple-scada, мы проверим его у себя.
Может он работает с анимироваными обектами в которых есть nколичиство кадровВсё верно, для мигание цветом (https://simple-scada.com/help/script/flashing.html), будет такой скрипт:
begin
if Sender is TM_Image then // если скрипт вызван каким-то изображением
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
if AsBool then
begin
Visible := True;
FlashColor := clRed;
end
else
begin
Visible := False;
FlashColor := clNone;
end;
end.
begin
if Sender is TM_Object then // проверяем, что Sender это объект
with Sender as TM_Object do // приводим Sender к типу "TM_Object"
if AsBool then // если значение переменной объекта True
Visible := True // показать объект
else // иначе
Visible := False; // скрыть объект
end.
begin
if FileOpen('SaveFile.ssf', '') then
vrTime.Value := FileReadDateTime
else
vrTime.Value := 0;
if FileRecreate('SaveFile.ssf', '') then
FileWriteDateTime(vrTime.AsDateTime);
with (Sender as TM_Object) do
case AsInt of
1: TimerStart (vrTime , vrTime.AsDateTime);
3: TimerStart (vrTime , vrTime.AsDateTime);
7: TimerStart (vrTime , vrTime.AsDateTime);
else
TimerPause (vrTime );
end;
end.
begin
if FileOpen('SaveFile.ssf', '') then
vrTime_1.Value := FileReadDateTime
else
vrTime_1.Value := 0;
if FileRecreate('SaveFile.ssf', '') then
FileWriteDateTime(vrTime_1.AsDateTime);
with (Sender as TM_Object) do
case AsInt of
2: TimerStart (vrTime_1 , vrTime_1.AsDateTime);
3: TimerStart (vrTime_1 , vrTime_1.AsDateTime);
7: TimerStart (vrTime_1 , vrTime_1.AsDateTime);
else
TimerPause (vrTime_1 );
end;
end.
begin
if FileOpen('SaveFile.ssf', '') then
vrTime_2.Value := FileReadDateTime
else
vrTime_2.Value := 0;
if FileRecreate('SaveFile.ssf', '') then
FileWriteDateTime(vrTime_2.AsDateTime);
with (Sender as TM_Object) do
case AsInt of
4: TimerStart (vrTime_2 , vrTime_2.AsDateTime);
7: TimerStart (vrTime_2 , vrTime_2.AsDateTime);
else
TimerPause (vrTime_2 );
end;
end.
var temp: TM_Variable;
begin
with Sender as TM_Text do begin
temp := VariableEx;
// проверка управления по принадлежности к группе, в Tag принадлежность к группе отмечена "единицами" в битах <код группы>,
// пример: для групп 4 и 7 (вентилятор 3) Tag = (2^4) + (2^7) = 16 + 128 = 144
if ((1 shl AsInt) and Tag) = 0 then
TimerPause(temp) // установить таймер на паузу
else
if TimerGetState(temp) <> 1 then TimerStart(temp, temp.AsDateTime); // не беспокоить работающий таймер
end;
end.
if TimerGetState(vrTime) <> 1 then TimerStart(vrTime, vrTime.AsDateTime);
Во вложении пример с шаблоном для вентилятора и просто группой объектов.
Осталась вторая проблема - это откат времени после работы 2 или 3 одновременно вентилятора и переход в обычный режим. нашол один интересный факт если сделать тайм аут приходяшего сигнала Допустим приходит 2 потом 7 и между ними сделать 0 то все работает корректно (Возможно стоит поставить задержку на приходящий сигнал в 1 секунду да и всё или можно реализовать как то в скаде межу переключениями ?По опыту работы с таймером можно отметить три его особенности:
Вообще операторы жалуются на сброс таймеров после рестарта пк или проектаЭто не сброс таймеров, а банальное переписывание нулевого значения из файла (было создано при первом запуске проекта).
Я так понимаю это мне нужно закинуть в корень папки Siple Scada 2 что бы открыть проэкт или как то по другому ?Simple-Scada видит проекты, размещенные в папке Projects, которая размещена в папке пользовательских данных (выбирается во время установки Simple-Scada). Туда и нужно поместить разархивированную папку проекта.
...есть вариант забирать время из контролера и передавать на скаду... Правда не знаю как реализовать вытягивание битов с 3 воздуходувок через 1 тег...Правильно получать значения времени наработки непосредственно из контроллера. Но у модуля SR3NET01 только по четыре шестнадцатиразрядных регистра для записи и для чтения. Т.е. всего-навсего по 64 бита. Можно увеличить количество путем страничного доступа к данным. Но это уже вопрос к автору программы и вычислительными ресурсами контроллера.
const
CLR_LED_ON: Int64 = RGB(67, 67, 67); // вычисляем цвет
CLR_LED_FAULT: Int64 = clRed; // используем готовый
...
function RGB(const r, g, b: byte): Int64;
begin
... // вычисляем цвет
end;
Значение первой константы будет вычисляться при каждом обращении к ней из скрипта, или только один раз, или как?Один раз.
interface
const
CLR_LED_ON1: Cardinal = RGB(67, 67, 67);
CLR_LED_ON2: Cardinal = $434343;
implementation
end.
Нужно, чтобы если качество исходной переменной "бэд", то и у внутренних выставлялось "бэд".Так сделать не получится. Качество переменной недоступно для изменения. Можно разложить переменную на биты в OPC-сервере и считывать в скаду отдельные битовые переменные, тогда качество этих переменных всегда будет соответствовать качеству на OPC-сервере.
как писать в тренд с учетом коэффициента трансформации, т.е. значение переменной умножить на число и его уже писать в тренд?Для этого нужно создать новую внутреннюю переменную, в скрипте произвести необходимые вычисления с исходной переменной и записать результат во внутреннюю переменную. У внешней переменной архивацию выключить, а у внутренней включить и использовать ее в трендах. Универсальный скрипт для подобной задачи можно применить если переменные, в которые нужно записывать рассчитываемые значения будут иметь однотипные имена, например исходная переменная имеет имя "Var1", а пересчитанная переменная имеет имя "Var1_Calc" и т.д. Тогда в скрипте можно использовать поиск переменной по имени через GetVariableByName (https://simple-scada.com/help/script/getvariablebyname.html). В этом случае, можно создать скрипт с типом события "Изменились переменные (https://simple-scada.com/help/script/changemulvar.html)", добавить в список скрипта нужные переменные, которые требуется умножать на коэффициент и написать такой универсальный скрипт:
var
aVar: TM_Variable;
begin
{ ищем переменную, в которую нужно записать преобразованное значение }
aVar := GetVariableByName(Variable.Name + '_Calc');
{ если переменная найдена, то записать в нее значение переменной умноженное на коэффициент }
if aVar <> nil then
aVar.Value := Variable.Value * 0.89;
end.
подскажите, возможно ли из скрипта получить доступ к массиву timeTrends[] временного тренда, размещенного в шаблонном окне?Для управления видимостью трендов, расположенного в шаблонном окне объекта "Временные тренды", можно привязать подстановку управляющей переменной к основной или дополнительной переменной объекта.
var i: integer;
begin
with Sender as TM_TimeTrendViewer do
for i := 0 to TimeTrendsCount -1 do TimeTrends[i].Visible := GetBit(AsInt,i); // или NOT GetBit...
end.
var
VarNm: Variant; //объявляем внутренние переменные
dwStatus: Int64; //объявляем внутренние переменные
dwCmd: Int64; //объявляем внутренние переменные
begin
if not (Sender is TM_Object) then // если скрипт вызван не объектом, то прерываем выполнение
Exit;
with (Sender as TM_Object) do
VarNm := TM_Object(Sender).Variable.Name; // получаем имя переменной, которая привязана к этому объекту
dwStatus:= GetVariableByName('GV_' + VarNm + '_dwStatus').Value; //ищем переменную с именем 'GV_' + VarNm + '_dwStatus' и получаем ее значение (переменная из OPC сервера типа longword)
dwCmd:= GetVariableByName('GV_' + VarNm + '_dwCmd').Value; //ищем переменную с именем 'GV_' + VarNm + '_dwCmd' и получаем ее значение (переменная из OPC сервера типа longword)
if GetBit(dwStatus, 5) = true then //проверяем, если 5 бит переменной dwStatus равен единице, то
dwCmd:=SetBit(dwCmd, 1, True); // устанавливаем 1 бит переменной dwCmd в единицу
end.
var
VarNm: Variant;
dwStatus, dwCmd: TM_Variable;
begin
if not (Sender is TM_Object) then // если скрипт вызван не объектом, то прерываем выполнение
Exit;
VarNm := TM_Object(Sender).Variable.Name; // получаем имя переменной, которая привязана к этому объекту
dwStatus:= GetVariableByName('GV_' + VarNm + '_dwStatus'); //ищем переменную с именем 'GV_' + VarNm + '_dwStatus'
dwCmd:= GetVariableByName('GV_' + VarNm + '_dwCmd'); //ищем переменную с именем 'GV_' + VarNm + '_dwCmd'
if (dwStatus <> nil) and (dwCmd <> nil) and (GetBit(dwStatus.Value, 5)) then //если переменные существуют, и 5 бит переменной dwStatus равен единице, то
dwCmd.Value := SetBit(dwCmd.AsInt64, 1, True); // устанавливаем 1 бит переменной dwCmd в единицу
end.
CloseApplicationClient(getClientName);
MinimizeApplicationClient(getClientName);
На двух клиентах разный интервал мигания, проект один. На дальнем ТВ правильно.Если Вы используете процедуру смены частоты мигания "SetFlashInterval", то причина нам известна и теперь устранена.
применяются ко всем открытым клиентам. Каждая команда размещена в своем отдельном скрипте, который привязан к событию онклик кнопки.Вероятно у Вас клиенты имеют одинаковые имена. Задайте на разных клиентах разные имена через утилиту настроек (https://simple-scada.com/help/manual/settings-simple-client.html?anchor=servset). Второй вариант - Вы вызываете getClientName в скрипте который выполняется сервером, что неправильно (см. описание функции в руководстве (https://simple-scada.com/help/script/getclientname.html)). Также непонятно, зачем Вы вызываете "CloseApplicationClient" два раза. Достаточно одного раза.
... но разметить барграф нужно по сути только один раз ...1. Если у барграфа не используется дополнительная переменная, то достаточно назначить скрипт разметки на событие по изменению доп. переменной, в качестве которой используется одна новая переменная. При ее инициализации (запуск проекта) для каждого барграфа будет однократно выполнен скрипт разметки.
... единственный способ применить этот скрипт к барграфу, который я нашел - это событие onDataChange ...
var i: integer;
begin
Bargraph1.OnDblClickEvent;
Bargraph2.OnDblClickEvent;
. . .
Bargraph100500.OnDblClickEvent;
// или
for i := 1 to 100500 do GetObjectByName('Bargraph' + IntToStr(i)).OnDblClickEvent; // это только пример
end.
procedure refreshSettings(const productsNames: TM_StringList); // вызывается refreshSettings(sl800ProductsNames)
begin
...
productsNames.Clear; // очищается sl800ProductsNames
...
end;
procedure refreshSettings(const lineName: string); // вызывается refreshSettings('sl800')
var
productsNames: TM_StringList;
begin
...
productsNames := TM_StringList(getObjectByName(lineName + 'ProductsNames')); // из имени получается ссылка на sl800ProductsNames
productsNames.Clear; // EXCEPTION
...
end;
Проблема в том, что после рефакторинга я ловлю исключение
Unhandled exception in module "global" at line 570. Access violation at address 01776AFB in module 'Server.exe'. Read of address 00000004
Что я делаю не так? Благодарю за помощь.
Но если вы добавите возможность искать и такие объекты - будет вообще превосходно!Такой возможности точно не будет, т.к. она слишком расточительна в плане производительности и на наш взгляд бессмысленна. Также она потребует добавления строкового представления имени в оперативной памяти реальному адресу объекта. Из 100% объектов Вы скорее всего будете использовать очень малый процент, а большинство других пользователей вообще не используют поиск по имени. В целом, если Вы пришли к тому, что приходится использовать поиск объектов по имени в скриптах, то с большой вероятностью структуры данных в Вашем коде плохо организованы или использован неправильный подход для решения задачи. Например, мы при создании скады никогда не использовали поиска объектов/переменных по имени, хотя однотипных структур очень много. У других программистов тоже никогда не видели кода в котором для обращения к объекту выполнялся бы его поиск по имени.
В целом, если Вы пришли к тому, что приходится использовать поиск объектов по имени в скриптах, то с большой вероятностью структуры данных в Вашем коде плохо организованы или использован неправильный подход для решения задачи.
begin
if Sender is TM_Image then // сначала убедимся, что скрипт вызван объектом "Изображение"
with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
begin
{меняем цвет изображения по основной переменной}
if GetBit(AsInt, 3) = TRUE then Color := clGreen else Color := clRed;
{меняем скорость анимации изображения по доп. переменной "VariableEx"}
if GetBit(VariableEx.AsInt, 1) = TRUE then AnimSpeed := 10 else AnimSpeed := 0;
end;
end.
var
obj: TM_Image;
i: Integer;
begin
for i:=0 to 63 do
begin
obj := GetImageByName('m'+IntToStr(i));
if obj <> nil then
if GetBit(int1.Value,i) then obj.Color:= clLime else obj.Color:= clNone;
if GetBit(int2.Value,i) then obj.Animspeed := 10 else obj.Animspeed := 0;
end;
end.
Возможен ли скрипт: при изменении переменной с "1" в "0", записать в 5 переменных 5 значений и чтобы это делалось автоматически. Либо чтобы при нажатии одной кнопки записывалось 5 переменных.
begin
if variable.AsInt = 0 then
begin
var1.value := ...;
var2.value := ...;
var3.value := ...;
var4.value := ...;
var5.value := ...;
// действия, когда сессия неактивна
{ если вам нужно действие, когда сессия активна, удалите строки 10 и 14
end
else begin
// действия, когда сессия активна
}
end;
end.
if (B24_220.Value = false) and (IsFirstChange() = false) then
AddMessageToGroup(Now, mkAlarm, 4, 'Котельная 24 - нет сети 220В!', true, true);
if (var0.Value = false) and (IsFirstChange() = false) then
AddMessageToGroup(Now, mkAlarm, 4, 'Котельная 24 - нет сети 220В!', true, true);
При сравнении значения переменной лучше использовать явное приведение значения переменной (https://simple-scada.com/help/script/nonvert-values.html) к нужному типу. Например, при сравнении с True/False нужно брать значение переменной переведенное в тип Boolean(свойство AsBool (https://simple-scada.com/help/script/varasbool.html))Кодif (B24_220.Value = false)
Подскажите пожалуйста, в скриптах с типом события "изменились переменные" можно обратиться к этой самой переменной не по имени?Для получения доступа к переменной, изменение которой привело к выполнению скрипта "изменились переменные" можно использовать параметр Variable.
if (Variable.AsBool = false) and (IsFirstChange = false) then
AddMessageToGroup(Now, mkAlarm, 4, 'Котельная 24 - нет сети 220В!', true, true);
Здравствуйте.Но при перезапуске проекта они тут же все будут выданы. Тут ключевое "IsFirstChange() = false", не понимаю, почему этого нет в стандартном функционале сообщений, как и задержек на срабатывание.
Сообщения можно добавлять/редактировать без использования скриптов, через меню сообщений (https://simple-scada.com/help/manual/edit-message.html). Для создания множества однотипных сообщений можно использовать шаблонные сообщения (https://simple-scada.com/help/manual/templatemessage.html).
При сравнении значения переменной лучше использовать явное приведение значения переменной (https://simple-scada.com/help/script/nonvert-values.html) к нужному типу. Например, при сравнении с True/False нужно брать значение переменной переведенное в тип Boolean(свойство AsBool (https://simple-scada.com/help/script/varasbool.html))Учту, спасибо.
Для получения доступа к переменной, изменение которой привело к выполнению скрипта "изменились переменные" можно использовать параметр Variable.Спасибо!Код: (delphi)if (Variable.AsBool = false) and (IsFirstChange = false) then
AddMessageToGroup(Now, mkAlarm, 4, 'Котельная 24 - нет сети 220В!', true, true);
Но при перезапуске проекта они тут же все будут выданы. Тут ключевое "IsFirstChange() = false", не понимаю, почему этого нет в стандартном функционале сообщений
Допустим если в шаблоне к некому полю, тексту, изображению скады не привязана переменная , скрыть этот объект, возможно ли это?Я решал эту задачу следующим способом: для объектов в шаблоне назначал подсказку %hint% либо значение Тега объекта %num% либо Подпись %name% в случае с чекбоксом.
Тогда при клонировании установок, не нужно детально разбираться с каждой индивидуально:
есть переменная => есть поле, текст, изображение., нет привязки=> скрыть поле, текст, изображение.
begin
with Sender as TM_CheckBox do
begin
if Caption = '-' then Visible := False //Если вместо названия датчика прочерк, скрыть чекбокс
else Visible := True; //Название есть - отобразить чекбокс
end;
end.
var
aObj: TM_Object;
begin
aObj := GetTemplateObject('Image1');
aObj.Visible := aObj.Variable <> nil;
aObj := GetTemplateObject('Valve1');
aObj.Visible := aObj.Variable <> nil;
end.
Здравствуйте.Большое спасибо. Заработало. ;)
В обновлении 2.5.8 шаблонам и окнам добавлено событие OnInit и функция GetTemplateObject, которые позволяют легко решить эту задачу.
Суть задачи в следующем. Требуется отправлять телеграм уведомления по трем типам событий:Для второй задачи можно использовать следующий способ: переменные, по которым настроены границы и переменные "Работа" должны иметь однотипные имена. Например, переменная температуры холодильника №1 - Temp1, переменная работы холодильника Temp1Work, холодильник №2 - Temp2 и Temp2Work и т.д. Тогда можно в скрипте использовать поиск переменной "Работа" по имени и далее использовать ее в скрипте, например:
var
aVar: TM_Variable;
begin
// пропускаем первое изменение
if not IsFirstChange then Exit;
// ищем переменную "Работа", соответствующую сработавшей переменной
aVar := GetVariableByName(Variable.Name + 'Work');
// если переменная существует, то..
if aVar <> nil then
// если переменная "Работа" = True
if aVar.AsBool then
// отправляем сообщение в зависимости от того, в какой зоне находится значение переменной
case Variable.GetValueZone of
-2: SendTelegram('Нарушена НА граница переменной ' + Variable.Name + '. Значение: ' + IntToStr(Variable.AsInt));
-1: SendTelegram('Нарушена НП граница переменной ' + Variable.Name + '. Значение: ' + IntToStr(Variable.AsInt));
0: SendTelegram('Значение вернулось в норму ' + Variable.Name + '. Значение: ' + IntToStr(Variable.AsInt));
1: SendTelegram('Нарушена ВП граница переменной ' + Variable.Name + '. Значение: ' + IntToStr(Variable.AsInt));
2: SendTelegram('Нарушена ВА граница переменной ' + Variable.Name + '. Значение: ' + IntToStr(Variable.AsInt));
end;
end.
Можно ли каким-то образом устроить "сегрегацию" пользователей Телеграм, или создать несколько групп.Проще всего будет создать несколько групп в Telegram (https://www.google.com/search?q=%D0%BA%D0%B0%D0%BA+%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C+%D0%B3%D1%80%D1%83%D0%BF%D0%BF%D1%83+%D0%B2+%D1%82%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC&sxsrf=ALeKk03bs1hElje4lPYzcgJCgBDE7YXGtg%3A1628196232449&ei=iE0MYY7tGreFwPAPvNOUqAo&oq=%D0%BA%D0%B0%D0%BA+%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C+%D0%B3%D1%80%D1%83%D0%BF%D0%BF%D1%83+%D0%B2+%D1%82%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC&gs_lcp=Cgdnd3Mtd2l6EAMyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgYIABAHEB4yBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDoHCAAQRxCwAzoHCAAQsAMQQzoECAAQDToICAAQBxAKEB46CAgAEAgQBxAeSgQIQRgAUPOiCViorAlgoLMJaAJwAngAgAHcAYgBiwiSAQUyLjQuMpgBAKABAcgBCsABAQ&sclient=gws-wiz&ved=0ahUKEwiO0bzQ35ryAhW3AhAIHbwpBaUQ4dUDCA4&uact=5), распределить пользователей по этим группам и отправлять сообщения в нужные группы.
[%назв. установки%] [%номер пом.%].......[%index%_температура_1] [%index%_температура_2] [%index%_температура_3] [%index%_температура_4]....
[%назв. установки%] [%номер пом.%].......[%index%_температура_1] [%index%_температура_2] [%index%_температура_3] [%index%_температура_4]
[%назв. установки%] [%номер пом.%].......[%index%_температура_1] [%index%_температура_2] [%index%_температура_3] [%index%_температура_4]
[%назв. установки%] [%номер пом.%].......[%index%_температура_1] [%index%_температура_2] [%index%_температура_3] [%index%_температура_4]
begin
if Sender is TM_Field then
with Sender as TM_Field do
case Filter_Column_Ahu.value of
0 : Variable := GetVariableByName(AsStr + '_температура_1');
1 : Variable := GetVariableByName(AsStr + '_влажность_212');
2 : Variable := GetVariableByName(AsStr + '_температура_3');
3 : Variable := GetVariableByName(AsStr + '_любое_другое_строковое_значение');
end;
end.
С целью экономии рабочего пространства пытаюсь "объединить" столбцы температуры.
Т.е. создать некий всплывающий список, который бы выбирал, что именно показывать в этом столбце: 1,2,3 или 4 температуры.
begin
if Sender is TM_Field then
with Sender as TM_Field do
case Filter_Column_Ahu.value of
0 : Variable := GetVariableByName(Hint + '_Температура1');
1 : Variable := GetVariableByName(Hint + '_Влажность1');
2 : Variable := GetVariableByName(Hint + '_любое другое тестовое значение');
end;
end.
begin
if vrTimer.Value = 0 then vrTimer.Value := 20;
end.
begin
if vrTimer.Value > 0then
begin
vrTimer.Value := vrTimer.AsInt -1;
if vrTimer.Value = 0 then
begin
// КОД РАЗМЕЩЕННЫЙ ЗДЕСЬ БУДЕТ ВЫПОЛНЯТЬСЯ ПО ТАЙМЕРУ
end;
end;
end.
Если кнопки нажаты не последовательно, то регистрация нажатия должна происходить с пометкой о нажатии не в правильной последовательности в виде сообщения.Как один из вариантов решения задачи - Создать внутреннюю переменную типа Word, скрипт "Изменились переменные" - Btn1, Btn2, Btn3
//Скрипт по изменению переменных Btn1, Btn2, Btn3. BtnWord внутренняя переменная типа Word
begin
if Btn1.AsBool = True then
BtnWord.Value := SetBit(BtnWord.Value, 0, True); //Если нажата кнопка 1 устанавливаем во внутренней переменной бит 0
if Btn2.AsBool = True then
BtnWord.Value := SetBit(BtnWord.Value, 1, True); //Если нажата кнопка 2 устанавливаем во внутренней переменной бит 1
if Btn3.AsBool = True then
BtnWord.Value := SetBit(BtnWord.Value, 2, True); //Если нажата кнопка 3 устанавливаем во внутренней переменной бит 2
if BtnWord.Value=0 then Exit
else if (BtnWord.Value<>1) and (BtnWord.Value<>3) and (BtnWord.Value<>7) then //Нарушена последовательность нажатия
AddMessage(Now, mkWarning, 'Нарушен маршрут осмотра помещения ...', True, False)
else if BtnWord.Value=7 then BtnWord.Value := 0; //Маршрут пройден, обнулить слово
end.
const Tmax = 120 * 60; // константа используется в двух скриптах - её можно перенести в глобальный модуль
begin
if vrTimer.AsInt < (Tmax - 2) then exit; // интервал контроля ещё не истек (рано кнопки нажимать). Для исключения контроля удалить оператор
if not Variable.AsBool then exit; // переход в false - игнорировать
if Variable = btn1 then begin K1.Value := 29; vrBtnState.Value := SetBit(vrBtnState.AsInt, 0, true); end; // запомнить нажатые кнопки
if Variable = btn2 then begin K2.Value := 29; vrBtnState.Value := SetBit(vrBtnState.AsInt, 1, true); end;
if Variable = btn3 then begin K3.Value := 29; vrBtnState.Value := SetBit(vrBtnState.AsInt, 2, true); end;
// СОСТОЯНИЕ КНОПОК - другой оператор должен быть закомментирован или удален
// этот оператор контролирует порядок нажатия кнопок 1-2-3 или 3-2-1
case vrBtnState.AsInt of
2, 5: vrBtnState.Value := SetBit(vrBtnState.AsInt, 3, true); // установить флаг ошибки порядка нажатия кнопок
7: vrTimer.Value := 0; // сброс таймера
15: begin
vrTimer.Value := 0; // сброс таймера
AddMessage(Now, mkWarning, 'Он шел на Одессу, а вышел к Херсону...(матрос-партизан Железняк)', true, false);
end;
end;
// этот оператор контролирует факт нажатия всех кнопок
// if vrBtnState.AsInt = 7 then vrTimer.Value := 0; // сброс таймера
end.
const Tmax = 120 * 60; // константа используется в двух скриптах - её можно перенести в глобальный модуль
begin
vrTimer.inc(1, 0, Tmax);
case vrTimer.AsInt of
1: vrBtnState.Value := 0; // сброс регистра нажатых кнопок
2, Tmax - 1: begin
if not Getbit(vrBtnState.AsInt, 0) then K1.Value := 31; // включать индикатор не нажатой кнопки
if not Getbit(vrBtnState.AsInt, 1) then K2.Value := 31;
if not Getbit(vrBtnState.AsInt, 2) then K3.Value := 31;
end;
Tmax: begin
K1.Value := 29; // выключить индикаторы кнопок
K2.Value := 29;
K3.Value := 29;
vrTimer.inc( -2);
end;
end;
end.
uses
WinSvc;
function ServiceGetStatus(sMachine, sService: PChar): DWORD;
{******************************************}
{*** Parameters: ***}
{*** sService: specifies the name of the service to open
{*** sMachine: specifies the name of the target computer
{*** ***}
{*** Return Values: ***}
{*** -1 = Error opening service ***}
{*** 1 = SERVICE_STOPPED ***}
{*** 2 = SERVICE_START_PENDING ***}
{*** 3 = SERVICE_STOP_PENDING ***}
{*** 4 = SERVICE_RUNNING ***}
{*** 5 = SERVICE_CONTINUE_PENDING ***}
{*** 6 = SERVICE_PAUSE_PENDING ***}
{*** 7 = SERVICE_PAUSED ***}
{******************************************}
var
SCManHandle, SvcHandle: SC_Handle;
SS: TServiceStatus;
dwStat: DWORD;
begin
dwStat := 0;
// Open service manager handle.
SCManHandle := OpenSCManager(sMachine, nil, SC_MANAGER_CONNECT);
if (SCManHandle > 0) then
begin
SvcHandle := OpenService(SCManHandle, sService, SERVICE_QUERY_STATUS);
// if Service installed
if (SvcHandle > 0) then
begin
// SS structure holds the service status (TServiceStatus);
if (QueryServiceStatus(SvcHandle, SS)) then
dwStat := ss.dwCurrentState;
CloseServiceHandle(SvcHandle);
end;
CloseServiceHandle(SCManHandle);
end;
Result := dwStat;
end;
function ServiceRunning(sMachine, sService: PChar): Boolean;
begin
Result := SERVICE_RUNNING = ServiceGetStatus(sMachine, sService);
end;
if ServiceRunning(nil, 'Имя службы') then
begin
{Действия если служба запущена}
end else
begin
{Действия если служба не запущена}
end;
Если напрямую в скриптах скады это сделать невозможно, может посоветуете, как по-другому можно вывести статус службы в интерфейс?В скаде сейчас нет какого-то простого способа проверить статус службы. Если служба во время работы обновляет какие-то файлы, то можно через скаду проверять время последнего изменения и реагировать если время давно не менялось.
Есть необходимость видеть в интерфейсе оператора, запущена ли служба опроса модемов.Получить состояние служб можно с помощью командного процессора.
Пример из вложения после ввода имени службы получает её состояние.Спасибо! Была проблема, из-за русского имени пользователя в винде не создавался текстовый файл. Но с переездом в "D:\" все заработало.
Вопрос: а зачем сканировать директорию на наличие файла, если мы точно знаем как он называется и можем открыть его напрямую?Файл результата открывается как перезаписываемый - т.е. новый замещает старый. Узнать что файл обновился можно по изменению даты записи файла, она должна быть больше чем дата начала выполнения скрипта.
Файл результата открывается как перезаписываемый - т.е. новый замещает старый. Узнать что файл обновился можно по изменению даты записи файла, она должна быть больше чем дата начала выполнения скрипта.
var
aPath: String;
i: integer;
tempString: String;
const
servName:string = 'iRZ_Collector_Server';
begin
if SecondOf(Now) = 0 then begin //проверяем статус службы и создаем файл с информацией 1 раз в минуту
aPath := GetProjectPath + 'User Files\';
StartTime.Value := NOW; // начало ожидания
// чтение состояния службы через VB скрипт для подавления вывода окна командной строки
TextFileOpen('Service.vbs', aPath, fomRewrite, fcpUTF8);
TextFileWriteLn('Set WshShell = CreateObject("WScript.Shell")');
TextFileWriteLn('WshShell.Run "cmd /A /C sc query ' + servName + '> ""' // выполнить команду чтения состояния службы
+ 'D:\Service.txt""", 0'); // и записать результат в файл
TextFileWriteLn('Set WshShell = Nothing');
TextFileClose;
RunApplication(SS_SERVER_NAME, aPath + 'Service.vbs', ''); // выполнить VBS-файл
end;
/////////////////////////////////////////////////
if SecondOf(Now) = 2 then begin //считываем файл для анализа через 2 секунды после создания
if (FileExists('Service.txt', 'D:\')) and (SecondsBetween(FileAge('D:\Service.txt'), StartTime.AsDateTime) < 10) then begin // условия корректности файла
TextFileOpen('Service.txt', 'D:\', fomReset, fcpCyrillic_windows866); //открываем файл
tempString := UTF8Encode(TextFileReadLn); //переходим к требуемой строчке в файле
tempString := UTF8Encode(TextFileReadLn);
tempString := UTF8Encode(TextFileReadLn);
tempString := UTF8Encode(TextFileReadLn);
if tempString = ' Состояние : 4 RUNNING ' then
begin txtService.Text := 'Служба опроса модемов работает';
txtService.BorderColor := clLime;
end
else if tempString = ' STATE : 1 STOPPED ' then
begin txtService.Text := 'Служба опроса модемов остановлена';
txtService.BorderColor := clRed;
end
else
begin txtService.Text := 'Ошибка получения статуса службы опроса модемов'; //в нужной строчке не те данные, которых мы ожидали
txtService.BorderColor := clYellow;
end;
TextFileClose; // закрыть файл
end
else begin txtService.Text := 'Ошибка получения статуса службы опроса модемов'; //файл не существует либо слишком старый
txtService.BorderColor := clYellow;
end;
end; // if SecondOf(Now) = 2
end.
Мы же и так можем узнать дату файла, зная имя.Мой пример предполагает однократный запрос состояния службы с минимальным временем отклика, анализ наличия обновленного файла начинается с 0 секунды относительно запроса.
(if SecondOf(Now) = 2) and (StartTime.AsFloat > 1) then begin //считываем файл для анализа через 2 секунды после создания и только после запуска скрипта
Поскольку процесс получения состояния службы периодический и не связан с действиями оператора (как в примере), то анализ файла можно смело перенести до 55 такта (секунды).Так и сделаю пожалуй.
Вывод названия параметра на разных языках ("Состояние" <-> "STATE") это, предположительно, отголосок локализации ОС. Я бы проверял строку на наличие подстрок ": 4 RUNNING" и ": 1 STOPPED".Проект работает на конкретном компьютере, универсальность не требуется, поэтому усложнять лишний раз не хочется.
И ещё одна особенность автомата при запуске проекта, с вероятностью 1/30 в течение первой минуты будет выдано сообщение "Ошибка получения статуса службы опроса модемов".Совсем не критично, перезапуски - редкое явление. Но раз есть возможность улучшить - почему бы и нет.
Для устранения этого можно использовать условие (начальное значение переменной - 0)
begin
if pingdata.Result then
ping_plc.color := clgreen
else begin
ping_plc.color := clred;
AddMessage (Now, mkAlarm, 'Отсутствие связи с контроллером', true, false);
end;
end
В такой реализации аварийное сообщение о потере связи генерируется постоянно (в течении всего времени отсутствия связи).В качестве флажка однократного действия можно использовать свойство ping_plc.color
Подскажите как реализовать однократное появление сообщения при потере связи и подобное сообщение когда связь восстановилась?
begin
if pingdata.Result then begin
if ping_plc.color <> clgreen then <действия при восстановлении связи>;
ping_plc.color := clgreen;
end
else begin
if ping_plc.color <> clred then AddMessage (Now, mkAlarm, 'Отсутствие связи с контроллером', true, false);
ping_plc.color := clred;
end;
end.
var
I: integer;
Sum: Int64;
aVar: TM_Variable;
begin
Sum := 0;
for I := 0 to 23 do
begin
aVar := GetVariableByName('temp' + IntToStr(I));
if aVar <> nil then
Sum := Sum + aVar.AsInt;
end;
tempSum.Value := Sum;
end.
Все работает, но проблема в том, что если нажал на изображение, после чего перешел на другую страницу и вернулся обратно,Установить свойство animspeed в 0 можно при уходе со страницы. Сообщить экземпляру шаблона об этом можно посредством переменной, изменяющейся (доп.переменная) или вызывая событие по изменению основной при уходе со страницы.
произойдет 1 цикл анимации, что и логично т.к. при первом нажатии я изменил animspeed = 40;
Это не проблема. :) Можно не использовать переменные изображения, но добавить в шаблон объект связанный с переменной сброса animspeed. Правда для версий не младше 2.5.8.0 См. вложение.Ого, вот оно как. Понял логику. Спасибо большое.
Почему-то скрипт "onClick" выполняется только после того как я отпустил кнопку мыши, что собственно говоря аналогично методу: "OnMouseUp".OnClick вызывается если пользователь нажал и отпустил ЛКМ. OnMouseUp вызывается при отпускании ЛКМ. Поэтому, эти два события будут срабатывать одновременно.
1. ... нажать в короткий промежуток времени кнопку второй раз ...Блокировка повторного нажатия в течение некоторого временного интервала.
2. ... Допустим мне нужно, чтобы на одну кнопку были два этих события...Событие по одиночному нажатию выполнится через некоторый временной интервал, при условии что в этом интервале не выполнялось событие по двойному нажатию. Действия по одиночному событию начинаются с некоторой задержкой относительно одиночного нажатия.
...OnClick - вызывается при нажатии, а затем отпускании над одним и тем же элементом (после нажатия, можно убрать указатель с элемента, отпустить кнопку и скрипт не сработает)...И это можно использовать для выбора варианта действия по схеме: "пользователь нажал/дважды_нажал" - "пользователь "сдвинул" объект".
между двумя асинхронными событиями и применим только в случае отсутствия фатальных последствий при ошибке определения кратности нажатия. Т.е. так можно: "Одиночное нажатие - окно просмотра состояния объекта, двойное - окно изменения параметров", а так нельзя - "Одиночное нажатие - включить двигатель, двойное - выключить".
... разобрался, но слегка заплутал (: и не совсем понял ваш комментарий:Два асинхронных события - имеется ввиду, что временной интервал между ними имеет некоторое распределение и его среднее значение зависит от используемого ПК, что и создает проблему с величиной интервала контроля. Причем среднее очень мало зависит от загруженности сервера, в частности от количества обрабатываемых событий, объема вычислений, графики и объема архивируемых данных. А вот задержка от физического нажатия клавиши мыши до вызова скрипта по однократному нажатию, на "загнанном" сервере, может визуально составлять несколько секунд.
А можете теперь все то-же расписать, но с условием, что ExtVar может измениться "с той стороны"? Например есть еще один сервер работающий с тем-же регистром и нам надо переключить кнопки в актуальное положение? При том что щелкать по ним могут фактически одновременно. :-\Для этого достаточно написать обратный универсальный скрипт на событие OnDataChangeEx для кнопок:КодТогда виртуальная переменная будет всегда иметь актуальное значение и работа будет корректной.begin
if not (Sender is TM_Object) then Exit;
with Sender as TM_Object do
if (Variable <> nil) and (VariableEx <> nil) then
Value := VariableEx.Value;
end.
{ условия для уровня }
begin
if Sender is TM_Level then // проверяем, что Sender это уровень
with Sender as TM_Level do // приводим Sender к типу "TM_Level"
if AsInt < 70 then // если перем. меньше 70, то
BackgroundColor := clRed // изменить цвет фона уровня на красный
else // иначе
BackgroundColor := clBlue; // изменить цвет фона уровня на синий
end begin
if AsInt > 98 then // если перем. ,больше 98, то
BackgroundColor := clRed // изменить цвет фона уровня на красный
else // иначе
BackgroundColor := clBlue; // изменить цвет фона уровня на синий
end.
Я так понимаю, в данной структуре уже нет необходимости после обновления, можно не использовать внутреннюю переменную?Вы процитировали очень старое сообщение 2017г. Сейчас не требуется создавать подобные скрипты при работе с битами.
Имеется переменная int32, биты которой управляют разными функциями. Так же биты этой переменной могут меняться из вне.Если переменная используется для управления битами в скаде и при этом может изменяться с ПЛК, то при таком подходе в любом случае будут проблемы. В моменты, когда изменение переменной из скады и из ПЛК будет происходить близко по времени, будет создаваться впечатление, что "вновь нажатые кнопки сбрасываются". Такая же ситуация будет если к этому добавляются задержки в работе ПЛК или OPC-сервера. Немного сгладить это можно уменьшив частоту опроса переменной, но это не решит проблему полностью. Если цикл в PLC 200мс., то нет смысла задавать частоту ниже 100мс., а лучше всегда задавать максимально большую частоту опроса, которая подходит под требования проекта - это позволит снизить нагрузку на ПЛК и OPC-сервер.
Помогите пожалуйста со скриптом уровня (есть переменная уровень (шкала), задача менять цвет от значения в одном скрипте):Для подобной задачи не требуется использовать скрипты. Задайте у переменной необходимые границы (https://simple-scada.com/help/manual/varextra.html), например верхнюю аварийную 98, нижнюю аварийную 70. Затем выделите Уровень и активируйте у него свойство "Границы (https://simple-scada.com/help/manual/level.html)". После этого, уровень будет изменять цвет на красный если значение будет выше 98 или ниже 70. При необходимости можно управлять границами переменных из клиента, а также включить автогенерируемые сообщения при нарушении границ (https://simple-scada.com/help/manual/managing-bounds-var.html).
begin
// перевести значение переменной из шкалы "0 - 65535" в шкалу "-32768 - 32767" и вывести результат в Variable1
if Variable1.Value > 32767 then
Variable1.Value := Scaling(32768,65535,-32768,-1, Variable1.Value);
end.
...Наверное, более элегантно мою задачу не решить ???Разобрать переменную на поля можно посредством пользовательского типа данных - записи:
type TwoSmallint = record SmallintLo, SmallintHi: Smallint; end;
var
temp: TwoSmallint;
begin
Longword(@temp)^ := LongWord(dwInput.AsInt); // копирование "байт в байт" в переменную temp
smiHi.Value := temp.SmallintHi; // разбор данных по структуре записи
smiLo.Value := temp.SmallintLo;
end.
begin
if Sender is TM_Object then
with Sender as TM_Object do
Alpha := Trunc(Scaling(1000,0,255,0, Variable.AsInt));
end.
begin
GoToMessagesAll; // переход в меню сообщений на всех клиентах
end.
begin
Text1.Text:=IntToStr(SysGet(SYS_UNCONFIRMED_COUNT)); //кол-во неподтвержденных сообщений
Text2.Text:=IntToStr(SysGet(SYS_ACTIVE_COUNT)); //кол-во активных сообщений
end.
begin
Text1.Text:=IntToStr(SysGet(0)); // или SYS_UNCONFIRMED_COUNT в аргументе, не важно
Text2.Text:=IntToStr(SysGet(1)); // допускает только const=1. SYS_ACTIVE_COUNT - ошибка при сохранении проекта
end.
И еще такое заметил (ни на что не влияет в принципе):У нас корректно компилируются и работают обе версии. Видимо причина в чем-то другом. Может быть в проекте есть объект (или процедура/функция) с именем как у константы SYS_ACTIVE_COUNT. Просьба выслать на support@simple-scada.com текущую версию проекта в которой воспроизводится проблема, тогда мы сможем указать точную причину из-за которой код не компилируется.
Такое не компилируется. При сохранении проекта выдает ошибку компиляции скриптов - не совпадение типов Integer и VOID в строке 3:
У нас корректно компилируются и работают обе версии. Видимо причина в чем-то другом. Может быть в проекте есть объект (или процедура/функция) с именем как у константы SYS_ACTIVE_COUNT.Все верно, название скрипта было: SYS_ACTIVE_COUNT
var
aDate: TDateTime;
begin
aDate:= Tomorrow;
end.
Необходим помощь со скриптом, который возвращает значение вчерашнюю дату при открытии проекта.
begin
vrDate.Value := Yesterday;
end.
begin
Field1.Text := DateToStr(Yesterday);
end.
aQuery := 'CREATE TABLE IF NOT EXISTS' + QuotedStr(table_name.AsStr) + ' (' +
'`id` INT NOT NULL AUTO_INCREMENT,' +
'`magn_rec` VARCHAR(45) NOT NULL,' +
'`servo_rec` VARCHAR(45) NOT NULL,' +
'`gas_rec` VARCHAR(45) NOT NULL,' +
'`comment` VARCHAR(60) NULL,'+
'PRIMARY KEY(`id`),' +
'UNIQUE KEY `name_UNIQUE` (`id`)' +
') ENGINE=InnoDB DEFAULT CHARSET=utf8;';
aQuery := 'CREATE TABLE IF NOT EXISTS' + '`' + table_name.AsStr + '`' + ' (' +
Pump1DayReport : ARRAY [1..366] OF TDateTime
ArchiveValueByTime(kns_12_worktime1day_1, Pump1DayReport[i - 1], aDate);
begin
alarm_1.Value:=GetBit(bity_ot_skvazhiny.AsInt,2); // Авария частотника скважина 1
// Авария частотника скважина 1
if alarm_1.Value then
begin
if not Memory_alarm_1.Value then // не отсылалась смс
begin
SendSMS('79024194140','Авария частотника скважина 1');
slovo_1.Value:=SetBit(slovo_1.AsInt,2,true);
Memory_alarm_1.Value:=True;
end
else
begin
slovo_1.Value:=SetBit(slovo_1.AsInt,2,false);
end;
end
else
begin
Memory_alarm_1.Value:=False;
end;
end.
Хочу в отдельном скрипте в цикле выдергивать архивные значения в массив, далее по изменению интересующего элемента массива производить действия. Как обыграть? Пока ошибка несоответствия типов.Ошибка несоответствия типов возникает из-за того, что у Вас массив имеет тип TDateTime, а результат ArchiveValueByTime должен записываться в переменную(TM_Variable) - см. описание по ссылке (https://simple-scada.com/help/script/archivevaluebytime.html). Через массив данную задачу решить не получится. В любом случае придется создать необходимое количество внутренних переменных для хранения результатов ArchiveValueByTime. Можно назвать их однотипно, тогда можно будет в цикле вызвать ArchiveValueByTime и записать результаты в нужные переменные используя функцию GetVariableByName (https://simple-scada.com/help/script/getvariablebyname.html), например:
var
I: Integer;
aVar: TM_Variable;
begin
for I := 1 to 10 do
begin
aVar := GetVariableByName('MyVar' + IntToStr(I));
if aVar <> nil then // если переменная существует
ArchiveValueByTime(kns_12_worktime1day_1, aVar, aDate);
end;
end.
Появилась такая проблемма с отправкой СМС. На телефон Андроид сообщения приходят, а на служебный кнопочный старый телефон НЕ приходят.Ни скада-система, ни модем не знают и не могут знать/определить, на какой именно телефон производится отправка. Отправка всегда производится одинаково - по указанному номеру телефона и никак не зависит от типа телефона, его возраста и т.д. Если на один из телефонов SMS приходят, то значит отправка SMS настроена корректно и нужно разбираться со вторым телефоном. Проверьте, что на этот телефон вообще приходят SMS, например отправьте на него SMS с другого телефона. Убедитесь, что при тестировании у Вас точно выполняются условия скрипта. Лучше всего при тестировании использовать простейший скрипт по клику на кнопку, без каких либо условий, например:
begin
SendSMS('79024194140','Авария частотника скважина 1');
end.
Галочки: "ждать ответа" и "менять режимы" испробовал. Запись сообщений в кирилице и латинице испробовал.Не нужно просто так менять настройки отправки - их изменение имеет смысл только в случае если отправка SMS не работает. Верните настройки по-умолчанию и протестируйте, если хотя бы на один телефон SMS приходит, то настройки корректные и нужно разбираться со вторым телефоном. Также, при изменении настроек нужно обязательно перезапустить сервер скады (https://simple-scada.com/help/manual/server-status.html), чтобы изменения вступили в силу.
Хочу в отдельном скрипте в цикле выдергивать архивные значения в массив, далее по изменению интересующего элемента массива производить действия.Функция ArchiveValueByTime() асинхронная, так просто в цикле она работать не будет. Для синхронизации с циклом можно предварительно переменной-результату присвоить незначимое значение (вне области возвращаемых значений), вызвать функцию и получить правильный результат по событию "Изменилась переменная-результат" с игнорированием незначимого значения.
var
aText: String;
begin
aText := 'Часть строки' + #9 + 'Вторая часть строки';
{ прервать скрипт если файл по стандартному пути не существует }
if not FileExists('MyTextFile.txt', '') then
Exit;
{ открыть файл для записи }
TextFileOpen('MyTextFile.txt', '', fomRewrite, fcpUTF8);
{ записать в файл строку "aText" }
TextFileWriteLn(aText);
{ закрыть файл }
TextFileClose;
end.
Какая-то справка по подобным символам существует?)Да, таблица ASCII
var
aTime: TDateTime;
begin
aTime := StrToTime(Table1.Columns[0].Cells[0].Text);
MyVar.Value := HourOf(aTime) * 3600 + MinuteOf(aTime) * 60 + SecondOf(aTime);
end.
Преобразование из Table1.Columns[0].Cells[0].Text в секунды это? Различные варианты StrTo* испробовал, все что-то не то.Можно так:
MyVar.Value := SecondsBetween(StrToTime(Table1.GetCell(0, 0).Text), 0));
Хочу игнорировать FALSE в течение минутыТаймер.
Хочу игнорировать FALSE в течение минуты, если FALSE держится больше минуты, то принимаю это значение FALSE.
begin
if (Variable.AsBool = False) and (IsFirstChange = False) then
vrTimer.Value := 60;
end.
begin
if vrTimer.AsInt > 0 then
begin
// отсчитываем секунды
vrTimer.Value := vrTimer.AsInt - 1;
if vrTimer.AsInt = 0 then
begin
// Код размещенный здесь будет выполняться
// Если boolean переменная = False в течение 60 секунд
end;
end;
end.
запускаю скаду и начинаю задавать переменную , обмен с модулем падает и опс выдает , что модуль (устройство вернуло ошибку)Скада никак не может самостоятельно повлиять на ошибки устройства. См. описание выше, скада подписывается на теги (с заданной частотой), которые Вы добавили в проект, а также выполняет запись так, как сделано в проекте. Например, если OPC-сервер (или конечное устройство) выдаёт ошибку из-за того, что скада подписалась на слишком большое количество переменных, то со стороны скады можно только уменьшить количество переменных в проекте. Если OPC-сервер (или конечное устройство) выдаёт ошибку из-за попытки записи в какую-то переменную, или из-за слишком большого количества операций записи, то со стороны скады можно только убрать запись в переменные через скрипты или объекты мнемосхемы, изменив проект.
пробовал откатится до 2.5.5.0, не помоглоТак и должно быть, т.к. версия скады явно не связана с описанной ситуацией. Не поможет даже откат до первой версии скады, т.к. все базовые функции не менялись с первой версии. И последняя версия скады читает и записывает данные также, как первая, в соответствии со спецификацией OPC.
с остановленным сервером скады, все ок, можно задавать устройству значение, как только запускаю скаду- , связь падает в бэд, убрал задание с помощью скрипта, повесил на филд , задает значение модулю , но 1 раз в 5 попытокЗдесь только три варианта: первый - значение изменяемой переменной перезаписывается в скриптах проекта; второй - оно перезаписывается на ПЛК. Например, Вы записываете значение 5 и оно сразу перезаписывается другим значением (например 0) из скрипта скады или в программе ПЛК. Третий вариант: OPC-сервер не записывает значение из-за какой-то существенной проблемы (с устройством, состоянием сети и т.п.). В этом случае он обычно возвращает ошибку в журнал сервера скады (https://simple-scada.com/help/manual/server-journal.html), либо в свой собственный лог.
т..е. скада задает такой значение, на какое ругается устройство.Все значения задаются либо пользователем проекта, через управляющие компоненты, либо через скрипты. Других вариантов записи значений во внешние переменные нет. Если устройство ругается на какие-то значения, то либо удалите из проекта скрипты в которых выполняется попытка записи некорректных значений, либо удалите объекты мнемосхемы, через которые пользователь вводит некорректные значения. Или измените их так, чтобы пользователь не мог ввести некорректные значения.
2) далее , если создать новый проект и оставить 1 устройство все работает,Очень похоже на то, что причина проблемы именно в рабочем проекте (например в скриптах), ведь новый проект работает. Другой вариант: проблема связана с OPC-сервером и возможно зависит от количества используемых тегов (или частоты опроса и т.п.). Вы уверены, что с OPC-сервером или компонентами OPC Core Components всё в порядке?
3)но в моём проекте , где все было хорошо , после обновы не работает
5) далее, установил на ноут, где ногда не было новой версии , версию 2.5.5.0 , и подкинул проект , без изменений с рабочей станции , и о чудо , все снова заработало ,Это просто неполный тест и основанные на нём выводы. Может быть проект также работал бы, если установить на ноутбук последнюю версию? Если проверять варианты и выполнять сравнения, то нужно проверять оба варианта, а не один. Также нужно отталкиваться от ошибки которую выдаёт устройство, или OPC-сервер. Что это за ошибка, о чем она говорит, в каких случаях она выдаётся согласно документации OPC-сервера (или устройства). Тогда можно было бы понять как её избежать и что вообще нужно проверять.
вы оказались правы, на ноуте и новая версия заработалаНе удивительно, ведь проблема не касается версии скады. Поэтому на рабочем ПК откат на предыдущую версию ничего не изменил, ведь обе версии работают одинаково. И на ноутбуке обе версии работают тоже потому, что проблема не касается обновления. Вы можете переустанавливать их до бесконечности и они всегда будут работать одинаково.
Единственное заметил, что на ноуте, стоит просто куча опс компонентов, разных версий, от другого софта.В разнице, судя по всему и заключается проблема. На одном ПК у Вас всё установлено и работает правильно. На другом чего-то не хватает, либо установлено неправильно. Например, можно установить на один ПК 32-битные компоненты OPC Core, на другой 64-битные. И оба ПК уже будут работать по-разному. Причем всё будет вроде бы работать, но с чудесами, как Вы примерно и описываете. Подобных примеров может быть много. Чтобы решить проблему нужно найти, чем рабочий ПК отличается от нерабочего и на проблемном ПК установить всё так, как на правильно работающем. Если разобраться не получается, то проще всего установить чистую ОС и все компоненты.
а скада давала скорее всего , со знаком после запятой, хотя, в настройках тэга , стоял чисто 0Скада может что-то давать со знаком после запятой, только если Вы будете записывать в переменную вещественное число (или переменную), например vrA.Value := 0.01. Других вариантов нет и от версии скады не зависит.
больше всего и эта проблема вашей скады, что с начала все работает (пусть скрипт с багом) , скрипт, а потом, после например обновления, перестаетКак мы уже неоднократно писали - все версии скады выполняют присвоения абсолютно одинаково и сделать как-то по-другому нельзя, т.к. есть стандарт. Мы не меняли процесс присвоения начиная с первой версии и не будем его менять в будущих версиях, это невозможно сделать не нарушая работу. Верить нам не нужно, см. сообщение выше, возьмите любое ПО для снимка сетевого трафика и сверьте данные которые посылают к OPC-серверу разные версии скады.
или как в моем случае, на ноуте работает, а на рабочей станции нет, и не понятно куда бежатьСкада не знает, установлена она на ноутбук, или на рабочую станцию, или ещё на какой-то ПК. Это программа, которая на всех ПК делает одно и то же и посылает одинаковые запросы. Вы уже сами в этом убедились, но сделали прямо противоположные выводы. Почему OPC-сервер на одном ПК передаёт значение устройству правильно, а на другом с ошибкой (если это вообще так) - нужно спросить у разработчиков OPC-сервера, потому что скада в двух случаях посылает OPC-серверу один и тот же запрос. Или может быть дело вовсе не в ПК и его ПО, а в том, что "неправильное" присвоение выполняется по условию и это условие срабатывает только на рабочем ПК. Но и это от версии скады не зависит.
подскажите как сделать универсальный скрипт для объекта Календарь, чтобы запускать таймер для, отображаемой в нем, переменной DateTime, по булевой перенной (её я обозначаю как доп. переменную для объекта).Например, на событие OnDataChangeEx, написать такой скрипт:
var
aVar: TM_Variable;
begin
if Sender is TM_Calendar then
with Sender as TM_Calendar do
begin
aVar := Variable;
if VariableEx.AsBool then // в зависимости от доп. переменной календаря
TimerStart(aVar, 0) // запускаем таймер по основной переменной календаря
else
TimerReset(aVar); // и останавливаем таймер по основной переменной календаря
end;
end.
Добрый день! Подскажите, пож-та, возможно ли получение данных с помощью SQL запросов от другой бд? Т.е. не от той, к которой настроено подключение...А почему нет? Если у пользователя есть права на другую БД, проблем быть не должно.
select name from db1.components a where a.ID in (select id from db2.bunkers);
Подскажите, пож-та, возможно ли получение данных с помощью SQL запросов от другой бд? Т.е. не от той, к которой настроено подключение...USE лучше не использовать, вместо этого укажите имя БД в запросе перед именем таблицы, как описал выше Victor_P..
USE лучше не использовать, вместо этого укажите имя БД в запросе перед именем таблицы, как описал выше Victor_P..
Правильно я понимаю, что это будет работать только при условии, что обе БД на одном сервере расположены?По идее можно прилинковать и другой сервер к используемому.
var
aReport: TM_Report;
aMail: TM_Mail;
begin
if HourOf(Now) <> 15 then Exit;
DateEndAuto.Value := RecodeTime(Now, 14, 5, 0, 0);
aReport := ReportBuild('Gagarina_sutki');
aReport.UseOnePageHeaderAndFooter := 1;
aReport.ExportPageBreaks := 1;
aReport.Save(SS_SERVER_NAME, 'Отчет Гагарина за ' + DateTimeToFileName(Now, ''), 'C:\Отчеты Асино\Гагарина\', rfExcel2007);
aMail := SendMail('Simple-Scada', 'Ежедневный отчет по энергоресурсам Гагарина', 'Отчет создан автоматически.');
aMail.AttachReport(aReport, 'Отчет Гагарина за ' + DateTimeToFileName(Now, ''));
end.
Прикрепить к письму файл отчета можно при помощи команды AttachFile[/url].
var
aReport: TM_Report;
aMail: TM_Mail;
begin
if HourOf(Now) <> 15 then Exit;
vrFileName.Value := 'Отчет Гагарина за ' + DateTimeToFileName(Now, '');
DateEndAuto.Value := RecodeTime(Now, 14, 5, 0, 0);
aReport := ReportBuild('Gagarina_sutki');
aReport.UseOnePageHeaderAndFooter := 1;
aReport.ExportPageBreaks := 1;
aReport.Save(SS_SERVER_NAME, vrFileName.AsStr, 'C:\Отчеты Асино\Гагарина\', rfExcel2007);
end.
где vrFileName - это внутренняя переменная String. Затем эту переменную можно использовать при отправке e-mail.
aReport.Save(SS_SERVER_NAME, FileNameReport.AsStr, 'C:\Отчеты Асино\Гагарина\', rfExcel2007); //работает, читаю переменную FileNameReport, имя пишется правильно, файл сохраняется
aMail := SendMail('Simple-Scada', 'Отчет по энергоресурсам Гагарина', 'Отчет во вложении', Email.Value);
aMail.AttachFile('C:\Отчеты Асино\Гагарина\'+FileNameReport.Value); //письмо приходит пустое
aMail.AttachFile(' C:\Отчеты Асино\Гагарина\ ' + FileNameReport.AsStr + '.xlsx');
var
aMail: TM_Mail;
begin
if (HourOf(Now) = 15) and (MinuteOf(Now) = 5) then // если сейчас 15:05
if FileNameReport.AsStr <> '' then
begin
FileNameReport.Value := '';
aMail := SendMail('Simple-Scada', 'Ежедневный отчет по энергоресурсам Гагарина', 'Отчет создан автоматически.');
aMail.AttachFile(' C:\Отчеты Асино\Гагарина\ ' + FileNameReport.AsStr + '.xlsx');
end;
end.
Здравствуйте. Такой возможности нет.Очень зря.
begin
if (k1.AsBool = true) and (k2.AsBool = true) then
vrC.Value := vrA.AsSingle - vrB.AsSingle
end.
В скриптах с типом "Изменились переменные" можно использовать функции IsFirstChange, PrevAsBool, PrevAsInt, PrevAsFloat, PrevAsStr.Используйте функцию PrevAsBool (https://simple-scada.com/help/script/prevasbool.html)
begin
if (k1.AsBool = true) and (k2.AsBool = true) then
vrC.Value := vrA.AsSingle - vrB.AsSingle
end.
begin
if ((k1.AsBool) and (PrevAsBool) and (k2.AsBool=false)) then
vrC.Value := vrA.AsSingle - vrB.AsSingle;
end.
begin
if Variable = vrA then // если изменилась переменная vrA (а не какая-то другая переменная из списка!)
if PrevAsInt = 1 then // и её предыдущее значение равно 1, то
begin
// делаем какие-то действия
end;
end.
На странице (TM_Page) все экземпляры различных шаблонов должны иметь одно определенное значение подстановки "система". Т.е. у шаблона1, шаблона2 и шаблона 3 есть подстановка "система", все экземпляры этих шаблонов должны иметь одинаковое значение. Как убедиься, что это условие выполнено? Кроме мануального перебора и визуального контроля?1. Можно создать по одному экземпляру шаблона, задать у них нужное значение подстановки "система" и затем копировать эти экземпляры. В скопированном экземпляре значение подстановки будет сохраняться.
Можно создать по одному экземпляру шаблона, задать у них нужное значение подстановки "система" и затем копировать эти экземпляры.Да, так и сделано. Система1 содержит, например, 20 клапанов (шаблоны). Т.е. систему целиком уже шаблоном не сделать. Теперь я копирую мнемосхему системы и вставляю на вторую страницу клапаны на первой назывались система1_к1...система1_к20. После вставки я получаю переменные система1_к21..система1_к40 (по хорошему нужно переименовывать объекты) и подстановки названиеСистемы=система1. Т.е. у всех клапанов второй системы мне вручную нужно поменять подстановку. Можно выделить все и поменть один раз, но если на мнемосхеме несколько различных шаблонов и расположены они вперемежку можно что-то и пропустить. Усугубляется это тем, что для разных шаблонов разный набор подстановок и где-то кроме кода системы нужно название системы. Это я о том, что накосячить есть где, а проверять ручным перебором.
Text2.Text:=var01.Value.AsStr;
Text2.Text:=var01.AsStr;
begin
// ЦВ НЕИСП АВАР НСУХ СУХ НПИТ ПИТ МП
// 1 0 0 0 0 0 0 0
TM_Image(Sender).Frame := ((TM_Object(Sender).AsInt and 128) Shr 7) + 1
end.
Добрый день. Можете расписать как работает этот скрипт?меняется картинка в зависимости от состояния 7-го бита. Можно было проще и понятнее сделать...
А разве можно в Simple-Picture создать анимацию на 128 кадров? ;D :P, я как-то пытался, больше 30 не вмещается(при чем тут 128 кадров? Это логическая операция - and 128 - выделяем только 7-й бит. ИМХО по извращенски код написан )
при чем тут 128 кадров? Это логическая операция - and 128 - выделяем только 7-й бит. ИМХО по извращенски код написан )Ну это как посмотреть) младшим или старшим битом вперед), просто если вы говорите о выделение 7 бита, то понятное дело в 10-тичной это будет 64 в случае если там стоит 1, 64+1, 65 кадр, или я что-то не так понимаю
begin
// ЦВ НЕИСП АВАР НСУХ СУХ НПИТ ПИТ МП
// 1 0 0 0 0 0 0 0
TM_Image(Sender).Frame := ((TM_Object(Sender).AsInt and 128) Shr 7) + 1
end.
var
aPage : TM_Page;
begin
With Sender As TM_Button do
begin
aPage := GetPageByName(Variable.Asstr);
aPage.GoToPageClient(GetClientName);
end;
end.
var
aPage : TM_Page;
begin
With Sender As TM_Button do
begin
aPage := GetPageByName('Page'+IntToStr(Tag));
aPage.GoToPageClient(GetClientName);
end;
end.
Как написать простой скрипт, к кнопке привязана Переменная (Внутренний тэг - str - содержащей имя страницы). При нажатии на кнопку нужно переходить на эту страницу.Если у кнопки в свойстве "Переменная" указана переменная, то при нажатии на кнопку будет изменяться значение этой переменной в соответствии с установленными значениями в свойстве "Состояния". Поэтому описанную Вами задачу таким способом решить не получится, так как при клике на кнопку переменная с именем страницы, перед выполнением скрипта, будет перезаписана.
var
aPage: TM_Page;
aButton: TM_Button;
begin
if Sender is TM_Button then // проверяем, что Sender это кнопка
begin
aButton := Sender as TM_Button;
// ищем страницу с именем из доп.переменной
aPage := GetPageByName(aButton.VariableEx.AsStr);
if aPage <> nil then // если страница существует,
aPage.GoToPageClient(GetClientName); // то перейти на нее
end;
end
Существует метод OnClick на стандартную клавишу подтверждения события?Создать скрипт для штатной кнопки подтверждения сообщений нельзя. При необходимости, можно скрыть штатную панель сообщений (https://simple-scada.com/help/manual/edit-zone.html) и создать свою(для этого лучше всего использовать шаблон (https://simple-scada.com/help/manual/template.html)). Вместо панели сообщений можно использовать компонент "Список сообщений (https://simple-scada.com/help/manual/message-viewer.html)". Смену пользователя можно реализовать через кнопку и скрипт по событию OnClick используя процедуру ChangeUser (https://simple-scada.com/help/script/changeuser.html). Переход в меню сообщений можно реализовать используя скрипты для навигации (https://simple-scada.com/help/script/navigation.html). Для подтверждения сообщений можно использовать кнопку и процедуру ConfirmMessage (https://simple-scada.com/help/script/confirmmessage.html). В этом случае, можно будет написать необходимый скрипт по событию OnClick своей кнопки подтверждения.
> Можно добавлять комментарий?Таких возможностей нет.
> выбранная информация отображалась бы в соответствующем сообщении в колонке "Подтвердил". Это реально?
> Можно ли изменять текст сообщения после его возникновения в списке сообщений?
//Универсальное событие
var
aObject: TM_Object;
begin
if Sender is TM_Object then
begin
aObject := Sender as TM_Object;
if aObject.variable.AsBool then
begin
alarmMode(aObject);
end
else
begin
sleepMode(aObject);
end;
end;
end.
//Глобальный модуль
interface
procedure sleepMode(out AResult: TM_Object);
implementation
procedure sleepMode(out AResult: TM_Object);
begin
if AResult is TM_Text then
with AResult as TM_Text do
AResult.FontColor := RGB(146,146,146);
if AResult is TM_Image then
.....
end;
end.
При необходимости, можно скрыть штатную панель сообщений (https://simple-scada.com/help/manual/edit-zone.html) и создать свою(для этого лучше всего использовать шаблон (https://simple-scada.com/help/manual/template.html)). Вместо панели сообщений можно использовать компонент "Список сообщений (https://simple-scada.com/help/manual/message-viewer.html)".
var
I: Integer;
aArray: array[1..17] of integer;
begin
// это просто пример, здесь можно обойтись без Low и High,
// т.к. размер статического массива известен
for I := Low(aArray) to High(aArray) do
begin
// ...
end;
end.
var
I: Integer;
begin
// проходим в цикле по каждому элементу массива
for I := Variable.LowBound to Variable.HighBound do
Text1.Text := Text1.Text + ', ' + Variable.ValuesStr[I];
end.
unit uIsCorrectLevel;
interface
function isCorrctLevel(sensorArray: String): Boolean;
implementation
function isCorrctLevel(sensorArray: String): Boolean;
...
end.
unit mLevel;
interface
uses
uIsCorrectLevel;
procedure mLevel(Lvl: TM_Level; sensor: TM_Variable);
implementation
procedure mLevel(Lvl: TM_Level; sensor: TM_Variable);
var
str: String;
begin
if isCorrctLevel(TM_Variable.AsStr ) then Lvl.FlashColor := clNone else Lvl.FlashColor := clRed;
end;
end.
interface
procedure mLevel(Lvl: TM_Level; sensor: TM_Variable);
implementation
uses
uIsCorrectLevel;
procedure mLevel(Lvl: TM_Level; sensor: TM_Variable);
var
str: String;
begin
if isCorrctLevel(sensor.AsStr ) then Lvl.FlashColor := clNone else Lvl.FlashColor := clRed;
end;
end.
procedure CheckMerk(Link230: TM_Variable; Kotelnaya: string);
//проверка связи с Меркурий
begin
if Link230.AsInt = 0 then
begin
vrTimer_Gagarina.Value := vrTimer_Gagarina.Value + 1;
if vrTimer_Gagarina.AsInt = 600 then
begin
AddMessage(Now, mkAlarm, Kotelnaya, TRUE, TRUE);
SendTelegram(Kotelnaya);
end
end
else
vrTimer_Gagarina.Value := 0;
end;
//Проверка связи с СПТ
procedure CheckSPT(variable: TM_Variable; Kotelnaya: string);
begin
if variable.IsGoodQuality = False then
begin
vrTimer2_Gagarina.Value := vrTimer2_Gagarina.Value + 1;
if vrTimer2_Gagarina.AsInt = 600 then
begin
SendTelegram(Kotelnaya);
AddMessage(Now, mkAlarm, Kotelnaya, TRUE, TRUE);
end
end
else
vrTimer2_Gagarina.Value := 0;
end;
//Проверка связи с СПГ
procedure CheckSPG(variable2: TM_Variable; Kotelnaya: string);
begin
if variable2.IsGoodQuality = False then
begin
vrTimer3_Gagarina.Value := vrTimer3_Gagarina.Value + 1;
if vrTimer3_Gagarina.AsInt = 600 then
begin
SendTelegram(Kotelnaya);
AddMessage(Now, mkAlarm, Kotelnaya, TRUE, TRUE);
end
end
else
vrTimer3_Gagarina.Value := 0;
end;
//Проверка связи с ПЛК
procedure CheckPLC(variable3: TM_Variable; Kotelnaya: string; picture: TM_Variable);
begin
if variable3.IsGoodQuality = False then
begin
vrTimer4_Gagarina.Value := vrTimer4_Gagarina.Value + 1;
if vrTimer4_Gagarina.AsInt = 600 then
begin
SendTelegram(Kotelnaya);
AddMessage(Now, mkAlarm, Kotelnaya, TRUE, TRUE);
picture.Value := True;
end
end
else
begin
vrTimer4_Gagarina.Value := 0;
picture.Value := False;
end
end;
begin
//проверка меркурий
CheckMerk(Link_230_Gagarina, 'Гагарина: Нет связи с электросчетчиком');
....
CheckMerk(Link230_VES, 'ВЭС: Нет связи с электросчетчиком');
CheckMerk(Link230_PMK, 'ПМК-16: Нет связи с электросчетчиком');
//проверка спт
CheckSPT(_171_Qo_g_, 'Гагарина: Нет связи с тепловычислителем');
CheckSPT(Q_Podacha_TS_171, 'Дружба: Нет связи с тепловычислителем');
...
CheckSPT(Q_PodachaTS, 'ХДСУ: Нет связи с тепловычислителем');
//проверка спг
CheckSPG(spg_Q1, 'Гагарина: Нет связи с корректором газа');
CheckSPG(Q1_2, 'Дружба: Нет связи с корректором газа');
...
CheckSPG(Q1_11, 'ПУ-24: Нет связи с корректором газа');
//проверка плк
CheckPLC(Tnv_4, 'Гагарина: Нет связи с ПЛК', LinkPLC_Gagarina);
CheckPLC(Tnv_6, 'Нефтебаза: Нет связи с ПЛК', LinkPLC_Neftebaza);
...
CheckPLC(Tnv_15, 'ПУ-24: Нет связи с ПЛК', LinkPLC_PU24);
end.
До того, как код был оптимизирован, без процедур (и с меньшим количеством оборудования) все работало. На каждую группу приборов был отдельный скрипт, в каждом скрипте на каждый прибор повторялся код для проверки.Добрый день, подскажите а как работало раньше и сейчас в плане организации вызова? Был отдельный скрипт "По-изменению" для каждой переменной, а теперь вызываются по времени процедуры?
При использовании процедур приходит сообщение только из последней строки "CheckPLC(Tnv_15, 'ПУ-24: Нет связи с ПЛК', LinkPLC_PU24)", несмотря на то, что связь на самом деле есть. А с оборудованием, с которым связи нет, сообщение не приходит. Подскажите пожалуйста, что делаем неправильно?
До того, как код был оптимизирован, без процедур (и с меньшим количеством оборудования) все работало. Подскажите пожалуйста, что делаем неправильно?В каждой процедуре Вы используете всего одну переменную для таймера, но вызываете процедуру для множества устройств. Из-за этого, при наличии связи хотя бы с одним устройством, таймер будет постоянно сбрасываться на 0.
procedure CheckMerk(aVar, aTimer: TM_Variable; aAlarm: string; aPicture: TM_Variable);
begin
if aVar.IsGoodQuality = False then
begin
aTimer.Value := aTimer.Value + 1;
if aTimer.AsInt = 600 then
begin
SendTelegram(aAlarm);
AddMessage(Now, mkAlarm, aAlarm, TRUE, TRUE);
aPicture.Value := True;
end;
end else
begin
aTimer.Value := 0;
aPicture.Value := False;
end;
end;
begin
CheckMerk(Link_230_Gagarina, Link_230_Gagarina_Timer, 'Гагарина: Нет связи с электросчетчиком');
CheckMerk(Link230_VES, Link230_VES_Timer, 'ВЭС: Нет связи с электросчетчиком');
CheckMerk(Link230_PMK, Link230_PMK_Timer, 'ПМК-16: Нет связи с электросчетчиком');
// ...
end.
Добрый день, подскажите а как работало раньше и сейчас в плане организации вызова? Был отдельный скрипт "По-изменению" для каждой переменной, а теперь вызываются по времени процедуры?
// проверка связи с СПГ742 Гагарина
{if spg_Q1.IsGoodQuality = False then
begin
vrTimer3_Gagarina.Value := vrTimer3_Gagarina.Value + 1;
if vrTimer3_Gagarina.AsInt = 600 then
begin
SendTelegram('Гагарина: Нет связи с корректором газа СПГ742!');
AddMessage(Now, mkAlarm, 'Гагарина: Нет связи с корректором газа СПГ742!', TRUE, TRUE);
end
end
else
vrTimer3_Gagarina.Value := 0;}
// проверка связи с ПЛК Гагарина
{if Tps_4.IsGoodQuality = False then
begin
vrTimer4_Gagarina.Value := vrTimer4_Gagarina.Value + 1;
if vrTimer4_Gagarina.AsInt = 600 then
begin
SendTelegram('Гагарина: Нет связи с ПЛК');
AddMessage(Now, mkAlarm, 'Гагарина: Нет связи с ПЛК', TRUE, TRUE);
LinkPLC_Gagarina.Value := True;
end
end
else
begin
vrTimer4_Gagarina.Value := 0;
LinkPLC_Gagarina.Value := False;
end;}
//Дружба
//Проверка связи с счетчиком Меркурий Дружба
{if Link_230_Druzhba.AsInt = 0 then
begin
vrTimer_Druzhba.Value := vrTimer_Druzhba.Value + 1;
if vrTimer_Druzhba.AsInt = 600 then
begin
AddMessage(Now, mkAlarm, 'Дружба: Нет связи с электросчетчиком (NPort 5130)!', TRUE, TRUE);
SendTelegram('Дружба: Нет связи с электросчетчиком (NPort 5130)!');
end
end
else
vrTimer_Druzhba.Value := 0;}
// проверка связи с СПГ742 Дружба
{if Q1_2.IsGoodQuality = False then
begin
vrTimer3_Druzhba.Value := vrTimer3_Druzhba.Value + 1;
if vrTimer3_Druzhba.AsInt = 600 then
begin
SendTelegram('Дружба: Нет связи с корректором газа СПГ742!');
AddMessage(Now, mkAlarm, 'Дружба: Нет связи с корректором газа СПГ742!', TRUE, TRUE);
end
end
else
vrTimer3_Druzhba.Value := 0;}
// проверка связи с СПТ963 Дружба
{if SostoyanieSPT.IsGoodQuality = False then
begin
vrTimer4_Druzhba.Value := vrTimer4_Druzhba.Value + 1;
if vrTimer4_Druzhba.AsInt = 600 then
begin
SendTelegram('Дружба: Нет связи с тепловычислителем СПТ963!');
AddMessage(Now, mkAlarm, 'Дружба: Нет связи с тепловычислителем СПТ963!', TRUE, TRUE);
end
end
else
vrTimer4_Druzhba.Value := 0;}
// проверка связи с ПЛК Дружба
{if Tnv_7.IsGoodQuality = False then
begin
vrTimer2_Druzhba.Value := vrTimer2_Druzhba.Value + 1;
if vrTimer2_Druzhba.AsInt = 600 then
begin
SendTelegram('Дружба: Нет связи с ПЛК');
AddMessage(Now, mkAlarm, 'Дружба: Нет связи с ПЛК', TRUE, TRUE);
end
end
else
begin
vrTimer2_Druzhba.Value := 0;
LinkPLC_Druzhba.Value := False;
end;
//Нефтебаза
// проверка связи с ПЛК Нефтебаза
if Tps_6.IsGoodQuality = False then
begin
vrTimer_Neftebaza.Value := vrTimer_Neftebaza.Value + 1;
if vrTimer_Neftebaza.AsInt = 600 then
begin
SendTelegram('Нефтебаза: Нет связи с ПЛК');
AddMessage(Now, mkAlarm, 'Нефтебаза: Нет связи с ПЛК', TRUE, TRUE);
end
end
else
begin
vrTimer_Neftebaza.Value := 0;
LinkPLC_Neftebaza.Value := False;
end;
//Центральная
// проверка связи с ПЛК Центральная
if Tnv_8.IsGoodQuality = False then
begin
vrTimer_Centr.Value := vrTimer_Centr.Value + 1;
if vrTimer_Centr.AsInt = 600 then
begin
SendTelegram('Центральная: Нет связи с ПЛК');
AddMessage(Now, mkAlarm, 'Центральная: Нет связи с ПЛК', TRUE, TRUE);
LinkPLC_Centr.Value := True;
end
end
else
begin
vrTimer_Centr.Value := 0;
LinkPLC_Centr.Value := False;
end;
// проверка связи с СПТ963 Центральная
{if NS_SPT_Centralnaya.IsGoodQuality = False then
begin
vrTimer_Centr2.Value := vrTimer_Centr2.Value + 1;
if vrTimer_Centr2.AsInt = 600 then
begin
SendTelegram('Центральная: Нет связи с тепловычислителем СПТ963!');
AddMessage(Now, mkAlarm, 'Центральная: Нет связи с тепловычислителем СПТ963!', TRUE, TRUE);
end
end
else
vrTimer_Centr2.Value := 0;}
// проверка связи с СПГ742 Центральная
{if Q1_4.IsGoodQuality = False then
begin
vrTimer_Centr3.Value := vrTimer_Centr3.Value + 1;
if vrTimer_Centr3.AsInt = 600 then
begin
SendTelegram('Центральная: Нет связи с корректором газа СПГ742!');
AddMessage(Now, mkAlarm, 'Центральная: Нет связи с корректором газа СПГ742!', TRUE, TRUE);
end
end
else
vrTimer_Centr3.Value := 0;}
//Проверка связи с счетчиком Меркурий Центральная
{if Link_230_Centr.AsInt = 0 then
begin
vrTimer_Centr4.Value := vrTimer_Centr4.Value + 1;
if vrTimer_Centr4.AsInt = 600 then
begin
AddMessage(Now, mkAlarm, 'Центральная: Нет связи с электросчетчиком (NPort 5130)!', TRUE, TRUE);
SendTelegram('Центральная: Нет связи с электросчетчиком (NPort 5130)!');
end
end
else
vrTimer_Centr4.Value := 0;}
//ПМК-16
// проверка связи с ПЛК ПМК-16
{if Tnv_9.IsGoodQuality = False then
begin
vrTimer_PMK1.Value := vrTimer_PMK1.Value + 1;
if vrTimer_PMK1.AsInt = 600 then
begin
SendTelegram('ПМК-16: Нет связи с ПЛК');
AddMessage(Now, mkAlarm, 'ПМК-16: Нет связи с ПЛК', TRUE, TRUE);
vrTimer_PMK1.Value := True;
end
end
else
begin
vrTimer_PMK1.Value := 0;
LinkPLC_PMK16.Value := False;
end;}
//ХДСУ
// проверка связи с ПЛК ХДСУ
{if Tnv_3.IsGoodQuality = False then
begin
vrTimer_XDSU1.Value := vrTimer_XDSU1.Value + 1;
if vrTimer_XDSU1.AsInt = 600 then
begin
SendTelegram('ХДСУ: Нет связи с ПЛК');
AddMessage(Now, mkAlarm, 'ХДСУ: Нет связи с ПЛК', TRUE, TRUE);
vrTimer_XDSU1.Value := True;
end
end
else
begin
vrTimer_XDSU1.Value := 0;
LinkPLC_XDSU.Value := False;
end;}
//Иконка связи Центральная
//if Tnv_8.IsGoodQuality = False then
//Image1453.Visible := True
//else
//Image1453.Visible := False
//end.
Здравствуйте.
Если требуется отслеживать каждое устройство отдельно, то нужно добавить отдельные переменные-таймеры для каждого устройства, тогда всё будет работать корректно. Например, для устройства Link_230_Gagarina создать переменную Link_230_Gagarina_Timer, для Link230_VES создать переменную Link230_VES_Timer и т.д. Пример кода:
procedure system_sql_answer(DataSet: TM_Dataset);
var
ButtonDest: TM_Button;
VarDest: TM_Variable;
begin
if (DataSet.Sender is TM_Button)
then
begin
if DataSet.Tag=10
then
begin
ButtonDest:=GetButtonByName(DataSet.Sender.Name);
ButtonDest.States[0].Caption:=DataSet[0].AsStr;
end;
end;
end.
ButtonDest:=GetButtonByName(DataSet.Sender.Name);
ButtonDest:=GetButtonByName(DataSet.Sender.Name);
ButtonDest := DataSet.Sender as TM_Button;
var
mDate,nDate: TDateTime;
begin
mDate := RecodeDateTime(Now, YearOf(Now), MonthOf(IncMonth(Now, -1)), 1, 0, 0, 0, 0);
ArchiveValueByTime(Schetchik_gotovoy_produktsii, proizvedeno_za_proshedshiy_mesyats_Begin, mDate);
nDate:= RecodeDateTime(Now, YearOf(Now), MonthOf(Now), 1 , 0, 0, 0, 0);
nDate := incDay(nDate, 1);
ArchiveValueByTime(Schetchik_gotovoy_produktsii, proizvedeno_za_proshedshiy_mesyats_End, nDate);
if (proizvedeno_za_proshedshiy_mesyats_Begin.AsInt = 0 ) OR
(proizvedeno_za_proshedshiy_mesyats_End.AsInt = 0) then
proizvedeno_za_proshedshiy_mesyats_Result.Value := 0
else
proizvedeno_za_proshedshiy_mesyats_Result.Value := (proizvedeno_za_proshedshiy_mesyats_End.AsInt - proizvedeno_za_proshedshiy_mesyats_Begin.AsInt);
end.
mDate := RecodeDateTime(Now, YearOf(Now), MonthOf(IncMonth(Now, -1)), 1, 0, 0, 0, 0);В этом коде Вы не вычитаете месяц. Вы берёте текущую дату/время и меняете в ней год на текущий (т.е. оставляете его без изменений), месяц на предыдущий, день на 1, а время на 00:00:00.
mDate := IncMonth(Now, -1); // вычитаем месяц из текущей даты
mDate := RecodeDateTime(mDate, YearOf(mDate), MonthOf(mDate), 1, 0, 0, 0, 0); // меняем день на 1 и обнуляем время
// теперь можно использовать mDate
ЦитироватьВот код который вычитает месяц правильно:Код: (delphi)mDate := IncMonth(Now, -1); // вычитаем месяц из текущей даты
mDate := RecodeDateTime(mDate, YearOf(mDate), MonthOf(mDate), 1, 0, 0, 0, 0); // меняем день на 1 и обнуляем время
// теперь можно использовать mDate
interface
type
Tester = class
public
//function Proverka(a, b, c: Integer): Integer; //функция для проверки зависания, исполнять только в секундном скрипте!
constructor Create;
end;
implementation
constructor Tester.Create();
begin
end;
function Proverka(a, b, c: Integer): Integer;
var
flag : Boolean;
m, resA, resB : Integer;
begin
if abs(a - b) > c then
flag := True
else
begin
flag := False;
m := 0;
end;
if flag then
begin
if m < 5 then
begin
if time60s.AsBool then
begin
resA:= abs(a - resA) + resA;
resB:= abs(b - resB) + resB;
m := m + 1;
time60s.Value := False;
end; //end if time60
end; //end while
if (m >= 5) and (resA = 0) and (resB = 0) then
result := 3
else if (m >= 5) and (resA = 0) then
result := 1
else if (m >= 5) and (resB = 0) then
result := 2
else result := 0;
end; // end if flag
end; // end function
end.
procedure ChangeText(aText: string; AFontColor, AColor, aFlashColor: Cardinal);
begin
(Sender as TM_Text).Text := aText; // текст
(Sender as TM_Text).FontColor := AFontColor; // цвет текста
(Sender as TM_Text).Color := AColor; // цвет фона
(Sender as TM_Text).FlashColor := aFlashColor; // цвет мигания
end;
begin
if not (Sender is TM_Text) then Exit; // если скрипт вызван не объектом Текст, то прерываем выполнение
case (Sender as TM_Text).AsInt of // если значение переменной равно:
1 : ChangeText('Включен', clBlack, clForestGreen, clNone); // активен бит 0
2 : ChangeText('Отключен', clBlack, clDarkSalmon, clNone); // активен бит 1
4 : ChangeText('Авария', clBlack, clFireBrick, clRed); // активен бит 2
8 : ChangeText('Состояние 4', clBlack, clForestGreen, clNone); // активен бит 3
16 : ChangeText('Состояние 5', clBlack, clForestGreen, clNone); // и т.д.
32 : ChangeText('Состояние 6', clBlack, clForestGreen, clNone);
64 : ChangeText('Состояние 7', clBlack, clForestGreen, clNone);
128 : ChangeText('Состояние 8', clBlack, clForestGreen, clNone);
256 : ChangeText('Состояние 9', clBlack, clForestGreen, clNone);
512 : ChangeText('Состояние 10', clBlack, clForestGreen, clNone);
1024 : ChangeText('Состояние 11', clBlack, clForestGreen, clNone);
2048 : ChangeText('Состояние 12', clBlack, clForestGreen, clNone);
4096 : ChangeText('Состояние 13', clBlack, clForestGreen, clNone);
8192 : ChangeText('Состояние 14', clBlack, clForestGreen, clNone);
16384 : ChangeText('Состояние 15', clBlack, clForestGreen, clNone);
32768 : ChangeText('Состояние 16', clBlack, clForestGreen, clNone);
else
ChangeText('Не определено', clBlack, clGray, clNone);
end;
end.
PlayUserSound('Name', Variable.Name+'.ogg',False);
bool_ogg1:=True;
num_alarm:=Variable.Value;
var
ii: Integer;
begin
if bool_ogg1= true then ii:=ii+1;
if ii>2 then
begin
PlayUserSound('Name', IntToStr(num_alarm)+'.ogg',False);
ii:=0;
bool_ogg1:=False;
end;
end.
".wav" у меня запустить не получилось. Значит он пока не поддерживается?Wav устарел и больше не поддерживается, поэтому нужно использовать более компактный и производительный .ogg. При необходимости можно конвертировать wav-файл в ogg, например через этот онлайн конвертер (https://convertio.co/ru/wav-ogg/). В следующем обновлении файлы руководства в CHM будут обновлены, информация о Wav будет удалена.
Я ведь могу сделать так? Скрипт по изменению переменной. Изменилась одна из шести переменных номера аварии. Тогда проигрываю файл "boiler_alarm_%....%.ogg", меняю внутреннюю переменную bool_ogg1:=True, которая означает начало проигрывания "звук.файла 1"Да, Вы можете попробовать реализовать проигрывание таким образом, только вместо локальных переменных скрипта "bool_ogg1" и "ii" Вам нужно использовать внутренние переменные созданные через редактор (https://simple-scada.com/help/manual/editor.html). Пояснения по использованию локальных переменных в скриптах см. по ссылке (https://simple-scada.com/help/script/compilerchange.html)(Изменение 1).
{
"2a52cc7d-c35e-46a0-97e2-22cfbf5749e0": {
"_LastCoords": null,
"_LastData": "2023-02-28T10:25:01Z",
"ID": "2a52cc7d-c35e-46a0-97e2-22cfbf5749e0",
"Name": "ОМ1",
"LastPosition": {
"Lat": 0.0,
"Lng": 0.0
},
"DT": "2023-02-28T10:25:01Z",
"State": 0,
"Speed": -1.0,
"Course": -1.0,
"Address": "–",
"Final": {
"CosF1_ME1": 0.99,
"Freq1_ME1": 49.99,
"I1_L1_ME1": 12.656,
"I1_L2_ME1": 28.432000000000002,
"I1_L3_ME1": 18.032,
"Inst_ActivePower1_ME1": 15.518399999942631,
"Inst_ReactivePower1_ME1": 0.0,
"U1_L1_ME1": 237.94,
"U1_L2_ME1": 233.03,
"U1_L3_ME1": 233.76
},
"LastCoords": null,
"LastData": "2023-02-28T10:25:01"
},
"38acaaba-bd6b-4216-b49e-78100fb44390": {
"_LastCoords": null,
"_LastData": "2023-02-28T10:25:01Z",
"ID": "38acaaba-bd6b-4216-b49e-78100fb44390",
"Name": "ОМ2",
"LastPosition": {
"Lat": 0.0,
"Lng": 0.0
},
"DT": "2023-02-28T10:25:01Z",
"State": 0,
"Speed": -1.0,
"Course": -1.0,
"Address": "–",
"Final": {
"CosF1_ME1": 0.99,
"Freq1_ME1": 49.99,
"I1_L1_ME1": 12.656,
"I1_L2_ME1": 28.432000000000002,
"I1_L3_ME1": 18.032,
"Inst_ActivePower1_ME1": 15.518399999942631,
"Inst_ReactivePower1_ME1": 0.0,
"U1_L1_ME1": 237.94,
"U1_L2_ME1": 233.03,
"U1_L3_ME1": 233.76
},
"LastCoords": null,
"LastData": "2023-02-28T10:25:01"
}
}
aValues1 := Response['2a52cc7d-c35e-46a0-97e2-22cfbf5749e0']['Final'];
aValues2 := Response['2a52cc7d-c35e-46a0-97e2-22cfbf5749e0']['Final'];
U_L1_ME1.Value := aValues1['U1_L1_ME1'].AsFloat;
U_L2_ME1.Value := aValues1['U1_L2_ME1'].AsFloat;
U_L3_ME1.Value := aValues1['U1_L3_ME1'].AsFloat;
var
I: Integer;
aNumVar: string;
aDevice, aValues: TM_JSONNode;
aVarL1, aVarL2, aVarL3: TM_Variable;
begin
if Response.Tag = 2 then
begin
for I := 0 to Response.Count - 1 do
begin
aNumVar := IntToStr(I + 1); // номер устройства для поиска переменных
aDevice := Response.Nodes[I]; // извлекаем очередное устройство
aValues := aDevice['Final']; // получаем массив значений 'Final' из устройства
// ищем переменные, в которые нужно записать значения из массива
// переменные должны иметь однотипные имена, например Dev1_U1_L1_ME1, Dev1_U1_L2_ME1,
// Dev2_U1_L1_ME1, Dev2_U1_L2_ME1 и т.д.
aVarL1 := GetVariableByName('Dev' + aNumVar + '_U1_L1_ME1');
aVarL2 := GetVariableByName('Dev' + aNumVar + '_U1_L2_ME1');
aVarL3 := GetVariableByName('Dev' + aNumVar + '_U1_L3_ME1');
// если переменные существуют
if (aVarL1 <> nil) and (aVarL2 <> nil) and (aVarL3 <> nil) then
begin
// записываем в переменные нужные значения из массива 'Final'
aVarL1.Value := aValues['U1_L1_ME1'].AsFloat;
aVarL2.Value := aValues['U1_L2_ME1'].AsFloat;
aVarL3.Value := aValues['U1_L3_ME1'].AsFloat;
end;
end;
end;
end.
var
ACStateField: TM_Field;
varVariableACStateField: TM_Variable;
begin
ACStateField := GetTemplateObject('txtAirCoolerWTEN1State') as TM_Field;
if(ACStateField <> nil) then begin
//Log_Add(ACStateField.Name);
varVariableACStateField:= GetVariableByID(ACStateField.VariableID);
if(varVariableACStateField <> nil) then begin
{ Log_Add(varVariableACStateField.Name); }
Log_Add(varVariableACStateField.Name + ' - ' + IntToStr(varVariableACStateField.AsInt));
case varVariableACStateField.AsInt of // если значение переменной связанной с полем равно:
0: ACStateField.Text:= 'Отключено'; // изменить цвет рамки на зеленый // состояние В работе.
1: ACStateField.Text:= 'Отказ'; // изменить цвет рамки на красный // состояние Неисправность
2: ACStateField.Text:= 'Ожидание'; // изменить цвет рамки на серый // состояние Выкл.
3: ACStateField.Text:= 'Охлаждение'; // изменить цвет рамки на зеленый // состояние В работе.
4: ACStateField.Text:= 'Подготовка к оттайке'; // изменить цвет рамки на красный // состояние Неисправность
5: ACStateField.Text:= 'Оттайка'; // изменить цвет рамки на серый // состояние Выкл.
6: ACStateField.Text:= 'Задержка охлаждения'; // изменить цвет рамки на зеленый // состояние В работе.
7: ACStateField.Text:= 'Задержка вентиляции'; // изменить цвет рамки на красный // состояние Неисправность
else
ACStateField.Text:= 'Неизвестное';
end;
end;
end;
//Log_Add(ACStateField.Text);
end.
Задача состоит в выводе определенного текста в поле текущего состояния оборудования, исходя из пришедшего значения переменной integer.Для данной задачи не требуется использовать события OnInit или OnShow. Пример нужного скрипта можно найти в руководстве по ссылке (https://simple-scada.com/help/script/changetext.html)(пример №4). Далее, опишем подробнее. Если Вам требуется отображать текст, то для этого нужно использовать компонент "Текст (https://simple-scada.com/help/manual/text.html)". В свойстве "Переменная" объекта "Текст" укажите переменную, по которой должно отображаться состояние оборудования. Затем перейдите в редактор скриптов (https://simple-scada.com/help/script/index.html) и создайте новый скрипт с типом "Универсальный" (https://simple-scada.com/help/script/event-types.html) и следующим кодом:
begin
if Sender is TM_Text then // проверяем, что Sender это текст
with Sender as TM_Text do // приводим Sender к типу "TM_Text"
case AsInt of // если значение переменной связанной с текстом равно:
0 : Text := 'Отключено';
1 : Text := 'Отказ';
2 : Text := 'Ожидание';
3 : Text := 'Охлаждение';
4 : Text := 'Подготовка к оттайке';
5 : Text := 'Оттайка';
6 : Text := 'Задержка охлаждения';
7 : Text := 'Задержка вентиляции';
end;
end.
не хочется видеть гордую 1 в полеЕсли Вы привязали компонент "Поле" (https://simple-scada.com/help/manual/field.html) к переменной, то поле будет автоматически выводить значение привязанной переменной, например "1". Вы можете сколько угодно переопределять значение в поле, заменяя его текстом, но оно всегда будет возвращаться к оригинальному значению переменной. Скрипты здесь ни при чем, все они выполняются в точности как описаны, но на вышеописанное поведение не влияют. Как мы писали ранее, для решения задачи не нужны скрипты OnInit и OnShow, смотрите наш предыдущий ответ и используйте для отображения текста компонент "Текст" (https://simple-scada.com/help/manual/text.html), который именно для этого и предназначен, как видно из названия.
А то пока ответ в духе, так не задумывалось - делай по -другому.Вы можете делать как угодно, в том числе так, как не задумывалось, результат будет зависеть от того, что Вы напишете. Мы просто отвечаем как нужно решить задачу о которой Вы спрашиваете. Корректное готовое решение, без лишних скриптов, было описано в нашем первом ответе. Компонент поле выводит значение привязанной к нему переменной, в этом его смысл и причина по которой вместо текста Вы видите число. Скрипты к этому отношения не имеют.
Вы так и не ответили почему не отрабатывает данный конкретный скриптКак писали в предыдущем ответе, все скрипты отрабатывают и выполнятся как описаны. Скрипт OnInit выполняется также, как и OnShow, только в разное время. OnInit в момент запуска проекта, а OnShow в момент когда Вы открываете окно. Поэтому OnInit назначает текстовое значение в поле, затем переменная инициализируется и поле автоматически берёт числовое значение переменной. А OnShow заменяет значение на текст после инициализации переменной и вы успеваете увидеть в поле текст, который затем всё равно изменится на значение переменной. Не обязательно знать это, чтобы решить описанную Вами задачу.
Еще какие-то скрытые скрипты будут это значение возвращать?В скаде не используются скрытые скрипты. Если Вы имеете в виду работу компонентов, то они все работают по-разному. Кнопка автоматически меняет своё состояние в зависимости от значения привязанной переменной, заслонка меняет цвет, поле отображает значение привязанной переменной и так далее. Ничего "скрытого" в этом нет.
И кстати у Вас же написано в мануале при любой возможности использовать поле вместо текста, не я это придумал...Верно, прочтите внимательно в руководстве, для вывода значения переменной нужно использовать поле, без использования скриптов. Для вывода текста нужно использовать компонент Текст, для вывода изображений компонент "Изображение" и т.д.
... Тогда по идее нужно уходить от шаблонов и под каждую емкость создать окно управления...Шаблон можно использовать для вывода списка произвольных сообщений. Во вложении на первой странице примеры двух шаблонов вывода списка.
мне нужно вытащить статусы "Работа ФГУ" и "Окончание ФГУ" каждая своим BOOL сигналом. И с учетом что у загрузки и у разгрузки свои теги получаем 4 сигнала. Далее у каждой емкости в блокировке ФГУ есть состояния клапанов других емкостей по битам у загрузки 14 битов у разгрузки 12 битов и соответственно у каждой в битах свой набор клапанов.Проще всего будет добавить необходимые проверки на контроллере и свести указанные биты в одну переменную статуса, значения которой будут соответствовать статусам, например: 1 - Статус 1, 2 - Статус 2 и т.д. Далее, полученную переменную можно использовать в скада-системе. Если по каким-то причинам на контроллере это сделать невозможно, то можно попробовать реализовать это через скрипты (https://simple-scada.com/help/script/index.html), проверяя состояния требуемых битов и формируя общую переменную статуса.
... у каждой емкости в блокировке ФГУ есть состояния клапанов других емкостей по битам у загрузки 14 битов у разгрузки 12 битов и соответственно у каждой в битах свой набор клапанов.Шаблон позволяет выводить список сообщений по значению поля непосредственно в переменных, без сведения полей в одну переменную. Сообщения собраны в группы. Управление группами предусматривает вывод одной группы или запрет вывода. Пустые сообщения не выводятся.
procedure ChangeField(AFontColor, AColor, aFlashColor: Cardinal);
begin
(Sender as TM_Field).FontColor := AFontColor; // цвет текста
(Sender as TM_Field).Color := AColor; // цвет фона
(Sender as TM_Field).FlashColor := aFlashColor; // цвет мигания
end;
begin
if not (Sender is TM_Field) then Exit; // если скрипт вызван не объектом Поле, то прерываем выполнение
case (Sender as TM_Field).AsInt of // если значение переменной равно:
0 : ChangeField(clGreen, ($BAFFFF), clNone); // активен бит 0 Норма
1 : ChangeField(clBlack, clDarkSalmon, clNone); // активен бит 1 Выход за предупредительную. Квитировано
2 : ChangeField(clBlack, clForestGreen, clDarkSalmon); // активен бит 2 Выход за предупредительную снизу
3 : ChangeField(clBlack, clForestGreen, clDarkSalmon); // активен бит 3 Выход за предупредительную сверху
4 : ChangeField(clGreen, clFireBrick, clGreen); // Выход за аварийную. Квитировано
5 : ChangeField(clBlack, clYellow, clFireBrick); // Выход за аварийную снизу
6 : ChangeField(clBlack, clYellow, clFireBrick); // Выход за аварийную сверху
else
ChangeField(clBlack, clGray, clNone);
end;
end.
case (Sender as TM_Field).VariableEx.AsInt of
var
aController: Variant;
aTerminalN: Integer;
aTerminalAddress: Integer;
aChannelNumber: Integer;
begin
aController:= CreateOleObject("Controller.ScAuto"); // создаем OLE-объект
aChannelNumber := 1; // здесь нужно указать реальный номер канала
aTerminalN := 1; // здесь нужно указать реальный номер терминала
aTerminalAddress := aController.GetTerminalAddress(aChannelNumber, aTerminalN); // получаем адрес терминала
Text1.Text := aController.GetStatus(aTerminalAddress, aChannelNumber); // вызываем метод GetStatus и выводим результат в компонент Text1
end.
у меня в проекте есть объекты которые имеют индекс с нижним подчеркиваниемОпишите максимально подробно, какую именно задачу Вы пытаетесь решить? Может быть требуется менять цвет этих объектов, другие свойства или что-то еще? Проще всего будет, если Вы пришлете на support@simple-scada.com для проверки текущую версию Вашего проекта из директории "..\Simple-Scada 2\Projects\", подробно опишите задачу и укажите шаблон/объект/скрипт который нужно проверить. После проверки проекта мы укажем наиболее простой способ решения задачи.
begin
if vrListInt.AsInt = 1 then
begin
vrListText.Value := "Отчет_Мотор_1";
end;
...
end.
var
aReport: TM_Report;
begin
aReport := ReportBuild(vrListText.AsStr);// построить отчёт
aReport.View(GetClientName); // открыть для просмотра
end.
Возможно ли значение текстовой переменной использовать при формировании отчета путем подстановки ее значения ?Да, возможно. Но, если имя отчета отличается только индексом (1,2,3 и тд), то можно обойтись без дополнительной строковой переменной и использовать следующий код для кнопки формирования отчета:
var
aReport: TM_Report;
begin
aReport := ReportBuild('Отчет_Мотор_' + vrListInt.AsStr);
aReport.View(GetClientName);
end.
насколько я понял из документации и тестов что нельзя вызвать внешние dll командами LoadLibrary, так как отсутствует dynlibs (object pascal)Возможности подключать внешние dll нет.
Как возможно подключить внешние dll для формул (требование метрологии)?
Возник такой вопрос (простите если дубль, ибо беглый поиск по форуму не дал мне ответа), а возможно ли с помощью скрипта менять шкалы переменной. Имеются в виду уже забитые шкалы в интерфейсе переменные.Шкала переменной задается через редактор и с помощью скриптов изменить ее не получится. Через скрипты можно получить только минимум/максимум шкалы переменной(свойства Minimum (https://simple-scada.com/help/script/varminimum.html) и Maximum (https://simple-scada.com/help/script/varmaximum.html)), но изменить их нельзя.
Подскажите пожалуйста можно ли из КЛИЕНТА менять имена пользователей, в частности ФИО, ввиду текучки операторов?Это одна из приоритетных задач, такая возможность обязательно будет добавлена в одно из будущих обновлений.
Хотелось бы сделать при выборе любой другой емкости, чтобы сбрасывался выбор предыдущей емкости. Ибо сейчас их можно все разом выбрать. Как это реализовать скриптом пока не понимаю.Сбрасывать значение переменных других емкостей и включать выбранную.
для каждого маршрута отдельно нарисовать трубу? И как решить вопрос с выходом нужного слоя поверх остальных?Конечно, делать участки трубопровода (которые нужно окрасить) отдельным объектом и менять цвет конкретного объекта в зависимости от переменной. Слой можно менять через свойство "Слой".
Подскажите, можно ли в объекте «Таблица» заполнять только первые две колонки по запросу из БД а в третьей колонке вычислять сколько коробок собрано за промежуток времени (т.е нужно от кол-ва во второй строке отнять кол-во в первой строке, от кол-ва в третьей строке отнять кол-во во второй строке и т.д.)?Не нужно переносить функции СУБД на скаду, тогда теряется сам смысл СУБД. Сделайте запрос который сразу сформирует готовый результат, чтобы дополнительно ничего не высчитывать в скаде, например:
SELECT
my_table.id,
my_table.value,
LEAD(my_table.value, 1) OVER win - my_table.value AS "Разница"
FROM my_table
WINDOW win AS (ORDER BY my_table.id)
Сбрасывать значение переменных других емкостей и включать выбранную.А можно пример как по кнопке с фиксацией button1 сбросить button2 и button3? Универсальным скриптом тут не получится как я понимаю.
Слой можно менять через свойство "Слой".
Всем здравия, я с очередной порцией вопросов...1. Для выбора только одной или не более чем одной емкости можно использовать так называемую "радиокнопку" - зависимый набор с одной или не более чем одной нажатой кнопкой.
А можно пример как по кнопке с фиксацией button1 сбросить button2 и button3? Универсальным скриптом тут не получится как я понимаю.Можно использовать универсальный скрипт для группы кнопок. Например, кнопка Button1 связана с переменной vrA1, Button2 – с vrA2 и Button3 – с vrA3. Переменные имеют тип "Boolean". Тогда можно создать новый скрипт (https://simple-scada.com/help/script/via-script-editor.html) с типом "Универсальный скрипт (https://simple-scada.com/help/script/universal-event.html)" и назначить его на событие OnClick требуемых кнопок:
begin
if Sender is TM_Object then // проверяем, что Sender это объект
with Sender as TM_Object do // приводим Sender к типу "TM_Object"
begin
if Variable = vrA1 then // если переменная связанная с кнопкой это vrA1, то ..
begin
vrA2.Value := 0; // сбрасываем другие кнопки
vrA3.Value := 0;
end else
if Variable = vrA2 then
begin
vrA1.Value := 0;
vrA3.Value := 0;
end else
if Variable = vrA3 then
begin
vrA1.Value := 0;
vrA2.Value := 0;
end;
end;
end.
Слой то понятно можно менять, а как из пирога в 6 слоев сделать так, чтобы именно определенный слой вышел поверх остальных? Или сделать статичный общий вид и вызывать через visible нужный маршрут нужным цветом?Самым простым способом будет отдельными объектами "Трубопровод (https://simple-scada.com/help/manual/pipeline.html)" отрисовать маршруты и менять их цвет (https://simple-scada.com/help/script/colorchange.html) в зависимости от значения связанной с ними переменой. Если пересечение участков маршрута достаточно сложное и обычным изменением цвета не обойтись, то можно отрисовать несколько маршрутов и менять их видимость с помощью свойства "Visible (https://simple-scada.com/help/script/visible.html)".
var
g : Integer;
t_var : TM_Variable;
begin
g := Table1.Tag;
t_var.Value := GetVariableByName('rate_hour' + IntToStr(g));
ArchiveValueByTime(t_var, t_rate1, Now - 60);
ArchiveValueByTime(t_var, t_rate2, Now - 120);
end.
var
g : Integer;
t_var : TM_Variable;
aTable : TM_Table;
begin
aTable := GetTemplateObject('Table1') as TM_Table;
g := aTable.Tag;
t_var := GetVariableByName('rate_hour' + IntToStr(g));
ArchiveValueByTime(t_var, t_rate1, Now - 60);
ArchiveValueByTime(t_var, t_rate2, Now - 120);
end.
Field1.Value := 10; // записать значение в основную переменную связанную с объектом
Field1.VariableEx.Value := 10; // записать значение в доп. переменную связанную с объектом
MyVar.Value := 10; // записать значение в переменную MyVar
А если Объекту не назначена основная переменная, то можно ли присваивать Value значение?Если объект не будет связан с переменной, то записывать значение будет некуда и при попытке записи возникнет ошибка "Обращение к несуществующему объекту" (https://simple-scada.com/help/script/debugscripts.html?anchor=acviol).
Если локальной переменной присвоить значение OPC переменной:Да, сработает. В данном случае локальная переменная aVariable будет хранить ссылку на переменную vrOPCVariable, поэтому работа с aVariable будет аналогична работе с переменной vrOPCVariable.
aVariable := vrOPCVariable;
if aVariable.IsGoodQuality then ...
Эта проверка сработает?
И сохранится ли признак качества при присвоении этой переменной значения:Как мы указали выше, в переменной aVariable будет храниться ссылка на внешнюю переменную vrOPCVariable, поэтому у переменной aVariable будет то качество, которое имеет переменная vrOPCVariable на момент выполнения скрипта.
aVariable.Value := SetBit(aVariable.Value,0,True);
Мы работаем с самопальным DA OPCServer (2008г), который берет данные из базы MS SQL. Ни каких проблем с MasterScada не было на Win7. Сейчас установили его на Win10 и с MS3.12 проблем нет, а вот у SimpleScada нет данных.Simple-Scada представляет собой OPC-клиент и работает в соответствии со спецификациями OPC-DA (https://opcfoundation.org/developer-tools/specifications-classic/data-access/)/ OPC-UA. В этих спецификациях описаны правила обмена сообщениями с OPC-серверами, размер сообщений в байтах, ограничения и т.д., независимо от OPC-сервера. Т.е., для работы с любыми OPC-серверами и контроллерами в Simple-Scada используется один и тот же код. Поэтому, со всеми OPC-серверами и контроллерами Simple-Scada работает совершенно одинаково, передает те же самые запросы, в соответствии со стандартами OPC. Если OPC-сервер соответствует спецификации OPC (https://opcfoundation.org/developer-tools/specifications-classic/data-access/), то проблем в работе с ним не будет.
Причем нет данных и у других клиентов OPC (подключаются, но значения 0 bad).Это говорит о том, что в работе данного OPC-сервера явно имеются проблемы. Возможно он работает корректно только при подключении с одного OPC-клиента (можно это протестировать), либо имеются какие-то другие ограничения и проблемы. Точную причину может определить только разработчик данного OPC-сервера.
Есть возможность вычислить в скрипте, какая страница открыта на клиенте?Такая возможность есть. Парные события "Пользователь перешел на эту страницу" и "Пользователь покинул эту страницу" позволяют контролировать пару страница - клиент (у которого по определению только ОДИН пользователь).
В проекте сделана замена штатного интерфейса кнопками и все на шаблонах. Хочется менять границу кнопки открытой подстраницы.При замене штатной системы навигации страниц на каждой странице присутствуют уникальные кнопки навигации, либо как набор отдельных кнопок, либо в составе шаблона. В первом случае кнопку выбранной страницы можно изменить на этапе редактирования проекта, во втором - при инициализации шаблона.
... Из минусов только то, что надо пройтись по всем экранным формам, где используется этот шаблон и "ручками" вбить номер кнопки, которая должна подсвечиваться.Определить, что кнопка должна подсвечиваться, можно по совпадению владельца и страницы перехода. В шаблоне редактируются ссылки на страницы и заголовки кнопок.
begin
if Sender is TM_Object then // проверяем, что Sender это объект
with Sender as TM_Object do // приводим Sender к типу "TM_Object"
if Variable.OriginalAsInt > Maximum then
AddMessage(Now, mkAlarm, 'Значение переменной "' + Variable.Name +
'" вышло за пределы диапазона допустимых значений: ' + AsStr, True, False);
end.
Как нажав кнопку можно проквитировать все сообщения этой группы?Подтвердить сообщения определенной группы, возможности нет. Можно подтвердить либо последнее неподтвержденное сообщение (процедура "ConfirmMessage (https://simple-scada.com/help/script/confirmmessage.html)"), либо все неподтвержденные сообщения (процедура "ConfirmAllMessages (https://simple-scada.com/help/script/confirmallmessages.html)").
Все эти сообщения привязаны к битам переменных, которые сбрасываются при квитировании сообщений. Если я сбрасываю этот бит, то появляется дата завершения, но сообщение остается не подтвержденным.В группе "Неподтвержденные" отображаются все неподтвержденные сообщения проекта. Для подтверждения сообщения его необходимо квитировать через клиент, либо через скрипты, как это описано выше. Сброс бита сообщения не приведет к его квитированию.
begin
if Sender is TM_Shape then // проверяем, что Sender это фигура
with Sender as TM_Shape do // приводим Sender к типу "TM_Shape"
if AsBool then // если значение основной переменной связанной с фигурой равно True/1
Alpha := 255 // изменить прозрачность фигуры на 255
else // иначе
Alpha := 0 // изменить прозрачность фигуры на 0
end.
begin
if Sender is TM_Shape then // проверяем, что Sender это фигура
with Sender as TM_Shape do // приводим Sender к типу "TM_Shape"
if VariableEx.AsBool then // если значение доп.переменной связанной с фигурой равно True/1
FlashColor := clRed // включить мигание объекта красным цветом
else // иначе
FlashColor := clNone; // отключить мигание объекта
end.
var
Speedbotle_temp: integer;
begin
if Dicount2.Value = 0 then
Speedbotle_min.Value := 0
else
Speedbotle_min.Value := Dicount2.Value - Speedbotle_temp;
Speedbotle_temp := Dicount2.Value
end.
begin
if Dicount2.AsInt = 0 then
Speedbotle_min.Value := 0
else
Speedbotle_min.Value := Dicount2.AsInt - Speedbotle_temp.AsInt;
Speedbotle_temp.Value := Dicount2.AsInt;
end.
И как создать скрипт чтобы он постоянно выполнялся, не каждую секунду, минуту, час, а постоянно в цикле программы.Такой скрипт создать нельзя. Скрипт можно создать только по одному из доступных событий (https://simple-scada.com/help/script/event-types.html).
...Есть большое количество однотипных агрегатов у которых 8 переменных по которым определяется аварийное и рабочее состояние. Задача заключается в создании шаблона который менял цвет в зависимости от 5 переменных, в шаблонном окне по нажатию кнопки сбрасывались 4 булевские переменные, ...Как правильно заметили разработчики Simple-Scada, то что можно следует загонять в ПЛК. Однако, если такой возможности нет, а количество агрегатов большое, то можно использовать шаблоны.
var i, j: integer;
begin
j := SetBit(0, 0, GetTemplateObject('TempObj_1').AsBool); // младший бит вкл./выкл.
for i := 1 to 4 do
with GetTemplateObject('TempObj_' + IntToStr(i)) do
j := SetBit(j, i, VariableEx.AsBool); // собрать весь вектор
// интерпретация вектора - по таблице, case - оператором или набором условных операторов
end.