プロセス制御システムのかなりの数のエンジニアが、「何か」をスケジュールどおりに動作させたいという要求に直面したと思います。 SCADAサーバーの一部としてスケジュールを実装する方法を示します。
基本的に、PLCでスケジュールを見ました。 そして、通常、毎週のスケジュールがあります。 1日に複数の切り替えポイント。
PLCの場合、このような実装は理解できます。 たとえば、制限されたメモリ。 そして、あなたはもうあざける必要はありません。
しかし、それでも、トリッキーな労働条件は実行しません。 「休日に含まれます」と入力します。
アイデア
Linuxにはそのようなユーティリティがあります-cron。 「特定の時間の定期的なタスクの場合。」 Cron命令は次のように書かれています
分 時間 day_month month day_week チーム
- 曜日(0-7)(日曜日= 0または= 7)
- 月(1-12)
- 日(1-31)
- 時間(0-23)
- 分(0-59)
例えば
0 0 * * 1-毎週月曜日の0:00分
ここで、*-任意の値を意味します
Cronにはまだたくさんのチップがあります。 Wikipediaで詳細を確認できます
これで十分です。
この場合にのみ、特定の瞬間ではなく、一定の期間が必要です。 まあ、私たちはレコードに年を付けます(ささいにならないように)。 次のエントリを取得します。
<分> <時間> <月_日> <月> <週_週> <年> <分単位の期間>
「優先度」がまだ必要です。 結局のところ、ある命令が別の命令をブロックする可能性があります。
実装
SCADAシステムの最初の段階では、すべてがxmlファイルにありました。
<?xml version="1.0" encoding="utf-8" ?> <timemode> <device ID="1" > <mode timeperiod="* * * * * * 5" type="" priority="0" /> <mode timeperiod="0 7 * * * * 60" type="*;*80;*80;*21" priority="1" /> </device> </timemode>
どこで
「Dir * IMP; PV * 80; BB * 80; U * 21インチ-パルス動作、供給ファン速度の80%、排気ファン速度の80%、室温設定-21°C
換気設備のすべてが行われました。 たとえば、2つのルールが表示されます。
1つは、システムの絶え間ない「停止」です。 毎日午前7時に2回目は、換気の設置を1時間開始します。
ディスパッチはSCADA +に組み込まれています。 この環境はC#スクリプトをサポートしています。 スクリプトは、出力変数(プロパティ)を持つオブジェクトとして形成されます。 スケジュール読み取りスクリプトの場合、変数は次のようになります。
スクリプトのタスクは、ArrayList型の出力配列を形成することです。 次のような文字列が含まれます
PV1>停止
PV2>停止
PV3>停止
さらに、特定の換気設備を担当するサブルーチンは、アレイ内で検出され、換気設備の動作モードを(必要に応じて)変更します。
そして今、コード
私は美しくて正しいコードのふりをしません。 また、ここでのいくつかの決定は、ランタイム自体によって引き起こされます。
構成ファイルを読み取り、モードの出力配列を形成する機能:
public void XMLread() { 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; }
そして、innerInterval関数。 インストールが特定の期間に該当するかどうかを判断します。
int innerInterval(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) { return 0; } return 1; } } catch (FormatException) { return -1; } catch { return -10; } } return -1; }
さて、興味のある完全なコード
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; namespace ClassLibrary { public class MyClass { ArrayList ArrayListVentModes_local; public ArrayList ArrayListVentModes{ get{ return ArrayListVentModes_local; } } public string FilePath{ set{ this.filepath_local = value ; } } public string Massege_str{ get; set; } public string CountElement{ get; set; } string filepath_local ; ArrayList arr; int innerInterval(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) { return 0; } return 1; } } catch (FormatException) { return -1; } catch { return -10; } } return -1; } int clamp = 0; public void main_metod() { if (this.clamp != 1) { Thread tRec = new Thread(new ThreadStart(XMLread)); tRec.Start(); this.clamp = 1 ; } } public void XMLread() { 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; } } }
SCADA +に影響
また、プログラムを再カウントするときに呼び出すメソッドを指定する必要があります。
現在の実装によると。 顧客は、モードをカスタマイズする機能を要求しました。 もちろん、彼はxmlファイルの編集を拒否しました。 XMLからMySQLのテーブルにすべてを転送し(SCADAを備えたサーバーがリモートにあるため、AWPから編集できるようにするため)、編集用の簡単なプログラムを作成しました。
以上です。 あなたにとって興味深いプロジェクト。
PS
同様のスケジュールをMasterSCADAで上げることができます-C#もサポートします。 さらに便利なデバッグのためにVisual Studioを接続することもできます(SCADA +については知りません)。
現在、私の考えはCodesysのSTでこのアイデアを実現するためです。 ARIES PLC63専用。 可能であれば、続編を書きます。