実際、すべてはSCADAシステムで始まりました。 ショッピングセンター向けのSCADAの開発と実装のためのプロジェクトを獲得できたことは幸運でした。 まあ、なんて幸運なのか...一般に、このシステムの開発は別の話です。読者が興味を持っているなら、詰め物のコーンを教えて共有します。
簡単に:
-部屋の温度制御;
-スケジュール上の集中換気制御;
-DHW充電制御;
-水と電気の計量。
この記事は電気についてです。
電気技師は、特別な問題がないため、モデルの選択に耳を傾けました。 私は水星にとどまることにしました。 3フェーズ-単相ネットワークの234番目(電気技師が230を購入)、206番目のモデル。 さらに、電気技師はショッピングセンター全体で3相メーターのみを押すことが判明しました。 まあ、私は問題が少ないだけです。 理由はわかりませんが。
以前は主にPLCと小さなスクリプトをC#でプログラミングしました。
アイデアはこれでした:
-アンケートサーバーは、データベースでエネルギー会計を実施します。
-SCADAシステムは視覚化を担当します
ポーリング方法
調査は、1つのRS485ポートでの無限ループで簡単に実装されました。 一般的に、派遣システムの運用のために、私はMOXA UPORT 1650-16を取り上げました。 調査では、ポートを1つだけ指定しましたが、スターを作成しないようにするため(RS-485ではこれは望ましくありません)、リピーターを使用しました。 多数の入力を持つブルジョアリピータRS-485が見つからなかったことは奇妙です。 しかし、12の港に国内の仕掛けTachyon PRT-1 / 8(12)がありました。 うまくいけば、全体の作業のビデオを投稿します。 うまくいきます。 MOXA UPORT 1650-16のみに苦情がありますが、それは別の話です。
アンケート自体の開発
デザインパターンについても聞いたことがありません。 すぐに間違えたから 継承によって持ち去られます(あまり有能ではありません)。 スマートブックによると、構成を適用する必要がありました。 将来的には、ライブラリ全体を作り直す必要があります。
継承チェーンは次のとおりです。
MeterDevice -> Mercury230 -> Mercury230_DatabaseSignals
MeterDeviceは、すべてのカウンターに共通のクラスです。 Modbus同様のプロトコルでCOMポート経由の交換を実装します。
Mercury230-特定のカウンターのポーリング関数のセットを持つクラス。
Mercury230_DatabaseSignals-特定のカウンターパラメーター(電流、電圧など)とそれらを更新する機能を持つクラス。 継承は便利な解決策ではありませんでした。 なぜなら その後、オブジェクトのシリアル化と逆シリアル化の問題を飲みました。 しかし、このクラスはコードに深く入り込んでおり、バックトラックすることは不可能でした。
ポーリング機能の鍵は、 RXmes構造を作成することでした 。 応答の結果と応答バイトの配列をその中に保存します。 ポーリング関数(たとえば、シリアル番号の要求)は、次の構造を使用してそれ自体で動作します。
public enum error_type : int { none = 0, AnswError = -5, // CRCErr = -4, NoAnsw = -2, // WrongId = -3, // NoConnect = -1 // }; public struct RXmes { public error_type err; public byte[] buff; public byte[] trueCRC; public void testCRC() { err = error_type.CRCErr; if (buff.Length < 4) { err = error_type.CRCErr; return; } byte[] newarr = buff; Array.Resize(ref newarr, newarr.Length - 2); byte[] trueCRC = Modbus.Utility.ModbusUtility.CalculateCrc(newarr); if ((trueCRC[1] == buff.Last()) && (trueCRC[0] == buff[(buff.Length - 2)])) { err = error_type.none; } } public void ReadArr(byte[] b) { buff = b; testCRC(); } } public RXmes SendCmd(byte[] data) { RXmes RXmes_ = new RXmes(); byte[] crc = Modbus.Utility.ModbusUtility.CalculateCrc(data); Array.Resize(ref data, data.Length + 2); data[data.Length - 2] = crc[0]; data[data.Length - 1] = crc[1]; rs_port.Write(data, 0, data.Length); System.Threading.Thread.Sleep(timeout); if (rs_port.BytesToRead > 0) { byte[] answer = new byte[(int)rs_port.BytesToRead]; rs_port.Read(answer, 0, rs_port.BytesToRead); RXmes_.ReadArr(answer); if (RXmes_.err == error_type.none) { DataTime_last_contact = DateTime.Now; } return RXmes_; } RXmes_.err = error_type.NoConnect; return RXmes_; }
したがって、Mercury 230カウンターのシリアル番号を取得する機能は次のとおりです。
public byte[] GiveSerialNumber() { byte[] mes = {address, 0x08 , 0}; RXmes RXmes = SendCmd(mes); if (RXmes.err == error_type.none) { byte[] bytebuf = new byte[7]; Array.Copy(RXmes.buff, 1, bytebuf, 0, 7); return bytebuf; } return null; }
他の機能を見たい人-あなたはソースを見ることができます。
SCADAとの通信プロトコル
当初、単純なTCP北プロトコルは単純でした。 SCADeのTCP応答は、Mercury 230thでこのように見えました。
「タイプ= merc230 * add = 23 *ボルト= 1:221-2:221-3:221 * cur = 1:1.2-2:1.2-3:1.2」
Scudaデータが解析され、対応するカウンターのアイコンに表示されました
すべては問題ありませんが、顧客はテーブル形式のすべてのデータが必要であると判断しました(そして彼の角を休めました)。 はい。運用中にすべてのパラメーターの制限を設定したかったのです。 そして、限界を超えることが示されるべきです。
なぜなら SCADAは表形式のデータを表示する方法を知りませんでした。視覚化のために別のプログラムを作成するために座っていました。
あなたのプロトコルはすでに特に不便になっています。なぜなら、 パラメータの数が増えました。 たとえば、現在の上部チャペル、事故の状態、事故のヒステリシスが表示されます。
パラメーター用に別のクラスが形成されたことが判明しました。
public MetersParameter() { minalarm = false; maxalarm = false; } public MetersParameter(float min, float max, float hist, float scalefactor = 1) { MinValue = min; MaxValue = max; Hist = hist; minalarm = false; maxalarm = false; ScalingFactor = scalefactor; } public string alias{set; get;} public float MaxValue { set; get; } public float MinValue { set; get; } public float ScalingFactor { set; get; } // . public float Hist { set; get; } private bool minalarm; private bool maxalarm; public bool ComAlarm { get { return MinValueAlarm || MaxValueAlarm ; } } public virtual bool MinValueAlarm { get{ return minalarm; } } public virtual bool MaxValueAlarm { get{ return maxalarm; } } public virtual void RefreshData() { if (null != ParametrUpdated) { ParametrUpdated(); } if ((MinValue == 0) && (MaxValue == 0)) { return; } float calc_par = parametr * ScalingFactor; if (calc_par < (MinValue - Hist)) { minalarm = true; } if (calc_par > (MinValue + Hist)) { minalarm = false; } if (calc_par < (MaxValue - Hist)) { maxalarm = false; } if (calc_par > (MaxValue + Hist)) { maxalarm = true; } } float parametr; public bool UseScaleForInput = false; public virtual float Value { set{ parametr = UseScaleForInput ? value / (ScalingFactor <= 0 ? 1 : ScalingFactor) : value; RefreshData(); } get { return parametr * ScalingFactor; } } public void CopyLimits(MetersParameter ext_par) { this.MinValue = ext_par.MinValue; this.MaxValue = ext_par.MaxValue; this.Hist = ext_par.Hist; } }
ここで、オブジェクトのシリアル化が助けになりました。 Byte、XML、およびJSONのシリアル化を試みたため、JSON(DataContractJsonSerializer)で停止することが決定されました。 目で読むのは簡単で、取得されるデータの量はXMLよりも少なかった。 一般に、DataContractJsonSerializerは引数なしのコンストラクターの欠如を許しました。 これにより、寿命が大幅に短縮されました。
データベース
もちろん、最も重要な瞬間は、メーターの測定値を記録することでした。 なぜなら ScadaシステムはMySqlと連携し、アンケートはそれと結びつけることになりました。 特別な問題はありませんでした。
質問は1つだけでした-「どのデータを記録するのですか?」 カウンターにはかなりの数のオプションがあります。 実際にリクエストのコード:
public enum peroidQuery : byte { afterReset = 0x0, thisYear = 1, lastYear = 2, thisMonth = 3, thisDay = 4, lastDay = 5, thisYear_beginning = 9, lastYear_beginning = 0x0A, thisMonth_beginning = 0x0B, thisDay_beginning = 0x0C, lastDay_beginning = 0x0D }
最初は、月ごとおよび日ごとの消費量を記録することが決定されました。 さらに、1か月間読み取りを行うための簡単なメカニズムが実装されました。 また、このデータの可用性を制御します。 十分なデータがない場合は、追加されました。
まとめ
現時点では、プログラムは約70のカウンターをポーリングします。 コンソールアプリケーションはサーバー上で実行され、クライアント部分はユーザーのワークステーション上で実行されます。
GitHubにアンケートのソースを投稿します 。 後でクライアント部分へのリンクを投稿しようとします。
PS Mercury 230とSET-4tmのプロトコルの類似性について
誰も出会わなかった場合。 それは、彼らにそのような植物です。 フルンゼ(ニジニノヴゴロド)。 そして、それらのカウンターは非常に類似したプロトコルで動作します。 私は両方のマニュアルを調べました-1対1。 しかし、プロトコルにはいくつかの違いがあると聞きました(まだ入っていません)。 手元にSETがないのは残念です。
類似点は、水星が元フルンゼの労働者によって開発されたという事実から成長します。 そのようなこと。 なぜ聴力にもっと水星があるのかは奇妙です。