Schedule creation for cron-based SCADA systems
In continuation of the SCADA system of my “beloved” shopping center,
I think quite a few engineers in the process control system were faced with the requirement to make “something” work on schedule. I will show how I implemented the schedule as part of the SCADA server.
I mainly saw schedules in the PLC. And there usually is a weekly schedule. Several switching points per day.
For a PLC, such an implementation can be understood. Limited memory, for example. And you don’t really have to fool around anymore.
But still, you won’t do any tricky working conditions. Type "will be included in the holidays."
There is such a utility in linux - cron. "For periodic tasks at certain times." Instructions in cron are written in this form
for example
Cron still has a bunch of chips. For spicy details, you can go to Wikipedia.
And this is quite enough for us.
Only in our case we need a period of time, and not a specific moment in time. Well, we also attach a year to the record (so as not to be trifled). We get the following record:
Still need "priority." After all, it may be that one instruction will block another.
At the first stage in the SCADA system, everything was in the xml file:
Where
Everything was done for ventilation installations. For example, 2 rules are shown.
One is the constant “stop” of the system. The second every day at 7 am will start the ventilation installation for 1 hour.
Dispatching is built in SCADA + . This environment supports C # scripts. Scripts are formed as objects with output variables (properties). For our schedule reading script, the variables look like this:

The task of the script is to form an output array of type ArrayList. It will contain strings like
Further on, the subroutine responsible for a specific ventilation installation will find itself in the array and change (if necessary) the operation mode of the ventilation installation.
I do not pretend to the beauty and correctness of the code. Also, some decisions here are caused by the runtime itself.
Function for reading the configuration file and forming the output array of modes:
And the innerInterval function. It determines whether the installation falls into a specific period of time:
According to the current implementation. The customer requested the ability to customize the modes. Of course, he refused to edit the xml file. We transferred everything from xml to a table in MySQL (so that you can edit it from the workstation, because the server with SCADA is located remotely) and made a simple program for editing

That's all. Interesting projects for you.
A similar schedule can be raised on MasterSCADA - it also supports C #. You can even connect Visual Studio for more convenient debugging (I don’t know about SCADA +).
Now my hands are combing to realize this idea on ST for Codesys. Specifically for ARIES PLC63. If possible, I’ll write a sequel.
I think quite a few engineers in the process control system were faced with the requirement to make “something” work on schedule. I will show how I implemented the schedule as part of the SCADA server.
I mainly saw schedules in the PLC. And there usually is a weekly schedule. Several switching points per day.
For a PLC, such an implementation can be understood. Limited memory, for example. And you don’t really have to fool around anymore.
But still, you won’t do any tricky working conditions. Type "will be included in the holidays."
Idea
There is such a utility in linux - cron. "For periodic tasks at certain times." Instructions in cron are written in this form
minute hour day_month month day_week team
- Day of the week (0 - 7) (Sunday = 0 or = 7)
- Month (1 - 12)
- Day (1 - 31)
- Hour (0 - 23)
- Minute (0 - 59)
for example
0 0 * * 1 - Every Monday at 0:00 minutes
where * - means any value
Cron still has a bunch of chips. For spicy details, you can go to Wikipedia.
And this is quite enough for us.
Only in our case we need a period of time, and not a specific moment in time. Well, we also attach a year to the record (so as not to be trifled). We get the following record:
<Minutes> <Hours> <Month_days> <Months> <Week_weeks> <Years> <Time span in minutes>
Still need "priority." After all, it may be that one instruction will block another.
Implementation
At the first stage in the SCADA system, everything was in the xml file:
<?xml version="1.0" encoding="utf-8" ?><timemode><deviceID="ПВ1" ><modetimeperiod="* * * * * * 5"type="СТОП"priority="0" /><modetimeperiod="0 7 * * * * 60"type="Реж*ИМП;ПВ*80;ВВ*80;У*21"priority="1" /></device></timemode>
Where
"Dir * IMP; PV * 80; BB * 80; U * 21 ”- pulse operation, 80% of the supply fan speed, 80% of the exhaust fan speed, room temperature setting - 21 ° C
Everything was done for ventilation installations. For example, 2 rules are shown.
One is the constant “stop” of the system. The second every day at 7 am will start the ventilation installation for 1 hour.
Dispatching is built in SCADA + . This environment supports C # scripts. Scripts are formed as objects with output variables (properties). For our schedule reading script, the variables look like this:

The task of the script is to form an output array of type ArrayList. It will contain strings like
"PV1> STOP"
"PV2> STOP"
"PV3> STOP"
Further on, the subroutine responsible for a specific ventilation installation will find itself in the array and change (if necessary) the operation mode of the ventilation installation.
And now the code
I do not pretend to the beauty and correctness of the code. Also, some decisions here are caused by the runtime itself.
Function for reading the configuration file and forming the output array of modes:
publicvoidXMLread() {
arr = new ArrayList();
int prio;
XmlDocument xmlDocument = new XmlDocument();
try{
xmlDocument.Load(filepath_local);
Massege_str = "Открыт успешно" ;
}
catch
{
Massege_str = "Ошибка открытия файла" ;
return;
}
foreach (XmlNode device in xmlDocument.SelectNodes("/timemode/device"))
{
prio = -1;
string strmode = "???" ;
foreach (XmlNode mode in xmlDocument.SelectNodes("/timemode/device[@ID=\"" + device.Attributes["ID"].Value + "\"]/mode"))
{
int prioNew = Convert.ToInt16(mode.Attributes["priority"].Value) ;
if ((innerInterval(mode.Attributes["timeperiod"].Value) > 0 ) && prio < prioNew)
{
strmode = mode.Attributes["type"].Value;
prio = prioNew ;
}
}
string newmes = (device.Attributes["ID"].Value + ">" + strmode);
arr.Add(newmes);
}
ArrayListVentModes_local = arr;
CountElement = ">" + ArrayListVentModes.Count.ToString();
Thread.Sleep(5000);
clamp = 0;
}
And the innerInterval function. It determines whether the installation falls into a specific period of time:
intinnerInterval(string CronFormatStr){
string[] word = CronFormatStr.Split(' ');
DateTime dt = DateTime.Now;
if (word.Length == 7)
{
try
{
int dayOfWeekArray = 0;
if (word[4] != "*") {
dayOfWeekArray = Convert.ToInt32(word[4]);
}
DateTime dt_start = new DateTime(
(word[5] == "*") ? dt.Year : Convert.ToInt32(word[5]),
(word[3] == "*") ? dt.Month : Convert.ToInt32(word[3]),
(word[2] == "*") ? dt.Day: Convert.ToInt32(word[2]),
(word[1] == "*") ? dt.Hour : Convert.ToInt32(word[1]),
(word[0] == "*") ? 0 : Convert.ToInt32(word[0]),
0 );
DateTime dt_end = dt_start.AddMinutes(Convert.ToInt32(word[6]));
if (dt >= dt_start && dt <= dt_end)
{
if (dayOfWeekArray != Convert.ToInt32(dt.DayOfWeek) && dayOfWeekArray > 0)
{
return0;
}
return1;
}
}
catch (FormatException)
{
return-1;
}
catch
{
return-10;
}
}
return-1;
}
Well, the full code, who are interested
using System;
using System.Collections;
using System.Collections.Generic;
//using System.Linq;using System.Text;
using System.Xml;
using System.Threading;
using System.Xml.Linq;
namespaceClassLibrary
{
publicclassMyClass
{
ArrayList ArrayListVentModes_local;
public ArrayList ArrayListVentModes{
get{
return ArrayListVentModes_local;
}
}
publicstring FilePath{
set{ this.filepath_local = value ; }
}
publicstring Massege_str{
get; set;
}
publicstring CountElement{
get; set;
}
string filepath_local ;
ArrayList arr;
intinnerInterval(string CronFormatStr){
string[] word = CronFormatStr.Split(' ');
DateTime dt = DateTime.Now;
if (word.Length == 7)
{
try
{
int dayOfWeekArray = 0;
if (word[4] != "*") {
dayOfWeekArray = Convert.ToInt32(word[4]);
}
DateTime dt_start = new DateTime(
(word[5] == "*") ? dt.Year : Convert.ToInt32(word[5]),
(word[3] == "*") ? dt.Month : Convert.ToInt32(word[3]),
(word[2] == "*") ? dt.Day: Convert.ToInt32(word[2]),
(word[1] == "*") ? dt.Hour : Convert.ToInt32(word[1]),
(word[0] == "*") ? 0 : Convert.ToInt32(word[0]),
0 );
DateTime dt_end = dt_start.AddMinutes(Convert.ToInt32(word[6]));
if (dt >= dt_start && dt <= dt_end)
{
if (dayOfWeekArray != Convert.ToInt32(dt.DayOfWeek) && dayOfWeekArray > 0)
{
return0;
}
return1;
}
}
catch (FormatException)
{
return-1;
}
catch
{
return-10;
}
}
return-1;
}
int clamp = 0;
publicvoidmain_metod() {
if (this.clamp != 1) {
Thread tRec = new Thread(new ThreadStart(XMLread));
tRec.Start();
this.clamp = 1 ;
}
}
publicvoidXMLread() {
arr = new ArrayList();
int prio;
XmlDocument xmlDocument = new XmlDocument();
try{
xmlDocument.Load(filepath_local);
Massege_str = "Открыт успешно" ;
}
catch
{
Massege_str = "Ошибка открытия файла" ;
return;
}
foreach (XmlNode device in xmlDocument.SelectNodes("/timemode/device"))
{
prio = -1;
string strmode = "???" ;
foreach (XmlNode mode in xmlDocument.SelectNodes("/timemode/device[@ID=\"" + device.Attributes["ID"].Value + "\"]/mode"))
{
int prioNew = Convert.ToInt16(mode.Attributes["priority"].Value) ;
if ((innerInterval(mode.Attributes["timeperiod"].Value) > 0 ) && prio < prioNew)
{
strmode = mode.Attributes["type"].Value;
prio = prioNew ;
}
}
string newmes = (device.Attributes["ID"].Value + ">" + strmode);
arr.Add(newmes);
}
ArrayListVentModes_local = arr;
CountElement = ">" + ArrayListVentModes.Count.ToString();
Thread.Sleep(5000);
clamp = 0;
}
}
}
Affects SCADA +
Еще нужно будет указать какой метод вызывать при пересчете программы.


According to the current implementation. The customer requested the ability to customize the modes. Of course, he refused to edit the xml file. We transferred everything from xml to a table in MySQL (so that you can edit it from the workstation, because the server with SCADA is located remotely) and made a simple program for editing

That's all. Interesting projects for you.
PS
A similar schedule can be raised on MasterSCADA - it also supports C #. You can even connect Visual Studio for more convenient debugging (I don’t know about SCADA +).
Now my hands are combing to realize this idea on ST for Codesys. Specifically for ARIES PLC63. If possible, I’ll write a sequel.