FinsUDP

概略

PCはEthernet経由でFINSコマンドをPLCに送信することでPLCのメモリの値を読み書きすることができます
ここではFinsUDPを使ってDMの値を読み出します
Com

PLCのIPアドレス 192.168.250.1
PCのIPアドレス 192.168.250.48
送信コマンドの内容 DM100から10CH分の値を読出す

PLCのメモリの値
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9
DM100 0001 0002 0003 0004 0005 0006 0007 0008 0009 000A

以下サンプルコードは同期通信でのやり方と非同期通信でのやり方をC#で書いてあります
フォームの作成(ボタンの配置など)は省略しているので適宜ボタンなどは配置してください

同期通信

同期通信はコマンド送信してから受信待ちの状態になります
受信タイムアウトは1000msとしているので1秒以内にレスポンスがなければ受信待ち状態から抜けます

private void UdpFinsMessage()
{
  System.Net.IPAddress LocalIP = System.Net.IPAddress.Parse("192.168.250.48");    // PCのIPアドレス
  System.Net.IPEndPoint LocalEP = new System.Net.IPEndPoint(LocalIP, 9600);
  System.Net.Sockets.UdpClient udp = new System.Net.Sockets.UdpClient(LocalEP);
  udp.Client.ReceiveTimeout = 1000;

  byte[] cmd = new byte[18];
  // ----------------- FINSヘッダ
  cmd[0] = 0x80;      // ICF
  cmd[1] = 0x00;      // RSV
  cmd[2] = 0x02;      // GCT
  cmd[3] = 0;         // DNA  相手先ネットワークアドレス
  cmd[4] = 1;         // DA1  相手先ノードアドレス (PLCのIPアドレスの最終桁と合わせる)
  cmd[5] = 0;         // DA2  相手先号機アドレス
  cmd[6] = 0;         // SNA  発信元ネットワークアドレス
  cmd[7] = 48;        // SA1  発信元ノードアドレス  (PCのIPアドレスの最終桁と合わせる)
  cmd[8] = 0;         // SA2  発信元号機アドレス
  cmd[9] = 1;         // SID  識別子 00-FFの任意の数値
  // ----------------- FINSコマンド
  cmd[10] = 0x01;     // MRC  読出しコマンド 0101
  cmd[11] = 0x01;     // SRC
  cmd[12] = 0x82;     // MemoryType = DM
  cmd[13] = 0x00;     // ReadAddress = 100CH
  cmd[14] = 0x64;
  cmd[15] = 0x00;
  cmd[16] = 0x00;     // ReadSize = 10CH
  cmd[17] = 0x0A;

  udp.Send(cmd, cmd.Length, "192.168.250.1", 9600);

  System.Net.IPEndPoint TargetIp = null;
  byte[] rcv = udp.Receive(ref TargetIp);

  textBox1.AppendText(BitConverter.ToString(rcv) + "\r\n");
  // 期待レスポンス
  // C0-00-02-00-30-00-00-01-00-01-01-01-00-00-00-01-00-02-00-03-00-04-00-05-00-06-00-07-00-08-00-09-00-0A
  // ~~~~~~~|~~~~~~~~~~~~~~~~~~~~~ ~~|~~ ~~|~~ ~~~~~~|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  //        +FINSヘッダ              |     |         +-- 読出し値=000100020003000400050006000700080009000A
  //                                 |     +-- 正常終了
  //                                 +--読出しコマンド

  udp.Close();
}
							

解説

3-5行目


System.Net.IPAddress LocalIP = System.Net.IPAddress.Parse("192.168.250.48");    // PCのIPアドレス
System.Net.IPEndPoint LocalEP = new System.Net.IPEndPoint(LocalIP, 9600);
System.Net.Sockets.UdpClient udp = new System.Net.Sockets.UdpClient(LocalEP);
								

PCのIPアドレスとUDPポートNoを指定してUDPクライアントを作成します

6行目


udp.Client.ReceiveTimeout = 1000;
								

受信タイムアウトを1秒(1000ms)に設定します
UDPのReceiveを実行したときにタイムアウトを設定していないと永遠に受信待ち状態になります

8-28行目


byte[] cmd = new byte[18];
// ----------------- FINSヘッダ
cmd[0] = 0x80;      // ICF
~
cmd[17] = 0x0A;
								

FINSコマンドをバイト配列にセットします
ここではDM100から10CH分のデータを読み出すコマンドをセットしています
FINSコマンドの内容についてはFINSコマンドを参照してください

30行目


udp.Send(cmd, cmd.Length, "192.168.250.1", 9600);
								

送信先のホスト名またはIPアドレスとポートNoを指定してcmdのバイト配列に入っているFINSコマンドを送信します

32-33行目


System.Net.IPEndPoint TargetIp = null;
byte[] rcv = udp.Receive(ref TargetIp);
								

Receiveメソッドでデータを受信するまで待ちます
受信したデータはバイト配列として受け取り、中身はFINSコマンドに対するレスポンスデータが入っています
また、6行目で設定したタイムアウト値を超えても受信できないときはエラーが発生します

35行目


textBox1.AppendText(BitConverter.ToString(rcv) + "\r\n");
								

受信したデータをテキストボックスに表示します
正常に読み出せたときは37行目の期待レスポンスに書いてある文字列がテキストボックスに表示されます


非同期通信

非同期通信はコマンド送信(SEND)したら処理を終了します
受信データが来るとイベントが発生するようにして受信データを取り込むようにします

System.Net.Sockets.UdpClient U;

private void button2_Click(object sender, EventArgs e)
{
  UdpFinsMessageASync();
}

private void UdpFinsMessageASync()
{
  if (U==null)
  {
    System.Net.IPEndPoint EP = new System.Net.IPEndPoint(System.Net.IPAddress.Any, 9600);
    U = new System.Net.Sockets.UdpClient(EP);
    U.BeginReceive(ReceiveCallbak, U);
	}

  byte[] cmd = new byte[18];
  // ----------------- FINSヘッダ
  cmd[0] = 0x80;      // ICF
  cmd[1] = 0x00;      // RSV
  cmd[2] = 0x02;      // GCT
  cmd[3] = 0;         // DNA  相手先ネットワークアドレス
  cmd[4] = 1;         // DA1  相手先ノードアドレス (PLCのIPアドレスの最終桁と合わせる)
  cmd[5] = 0;         // DA2  相手先号機アドレス
  cmd[6] = 0;         // SNA  発信元ネットワークアドレス
  cmd[7] = 48;        // SA1  発信元ノードアドレス  (PCのIPアドレスの最終桁と合わせる)
  cmd[8] = 0;         // SA2  発信元号機アドレス
  cmd[9] = 1;         // SID  識別子 00-FFの任意の数値
  // ----------------- FINSコマンド
  cmd[10] = 0x01;     // MRC  読出しコマンド 0101
  cmd[11] = 0x01;     // SRC
  cmd[12] = 0x82;     // MemoryType = DM
  cmd[13] = 0x00;     // ReadAddress = 100CH
  cmd[14] = 0x64;
  cmd[15] = 0x00;
  cmd[16] = 0x00;     // ReadSize = 10CH
  cmd[17] = 0x0A;

  // 受信再開
  U.BeginSend(cmd, cmd.Length, "192.168.250.1", 9600, SendCallback, U);
}

private void SendCallback(IAsyncResult ar)
{
  System.Net.Sockets.UdpClient udp = (System.Net.Sockets.UdpClient)ar.AsyncState;

  try
  {
    udp.EndSend(ar);
  }
  catch (System.Net.Sockets.SocketException ex)
  {
    Console.WriteLine(ex.Message);
  }
  catch (ObjectDisposedException ex)
  {
    Console.WriteLine("Already Closed");
  }
}

private delegate void AddStringDelegete(byte[] str);
private void AddText(byte[] msg)
{
  if (msg == null)
      return;

  textBox1.AppendText(BitConverter.ToString(msg) + "\r\n");
}

private void ReceiveCallbak(IAsyncResult ar)
{
  System.Net.Sockets.UdpClient udp = (System.Net.Sockets.UdpClient)ar.AsyncState;
  System.Net.IPEndPoint remoteEP = null;
  byte[] receiveBytes = udp.EndReceive(ar, ref remoteEP);
  System.Net.IPAddress receiveAddress = remoteEP.Address;

  AddStringDelegete dlg = new AddStringDelegete(AddText);
  this.Invoke(dlg, new object[] { receiveBytes });

  U.BeginReceive(ReceiveCallbak, U);
}
							

非同期通信とは
同期通信では
Sendを実行後にReceiveを実行して受信データを待ちますが、待っている間はアプリケーションは画面更新やキーボード入力やマウス入力などが出来ずにロック状態(受信待ち状態のまま)になります

非同期通信では
1. BeginReceiveを実行しておいて受信があれば指定したメソッドが呼び出されるようします
2. データの送信はBeginSendで実行したら処理は一旦終了します
このように送信と受信を別々に処理するためアプリケーションはロック状態になりません
受信データが届けばBeginReceiveで指定したメソッドが実行されます

NOTE
同期通信でスレッドを分けるなど他の方法もありますが、ここではシングルスレッドで非同期通信する方法の説明になります


解説

1行目


System.Net.Sockets.UdpClient U;
									

UdpClientのインスタンスを作成します

3-6行目


private void button2_Click(object sender, EventArgs e)
{
  UdpFinsMessageASync();
}
									

フォーム上のButton2をクリックしたらUdpFinsMessageASyncメソッドを実行します

10-15行目


if (U==null)
{
  System.Net.IPEndPoint EP = new System.Net.IPEndPoint(System.Net.IPAddress.Any, 9600);
  U = new System.Net.Sockets.UdpClient(EP);
  U.BeginReceive(ReceiveCallbak, U);
}
									

UdpClientのインスタンスが空であればEndPointを設定してnewで参照をします
そのあと14行目のBeginReceiveで非同期で受信します
受信したときはReceiveCallbakメソッドが実行されます

17-37行目


byte[] cmd = new byte[18];
// ----------------- FINSヘッダ
cmd[0] = 0x80;      // ICF
~
cmd[17] = 0x0A;
									

FINSコマンドをバイト配列にセットします
ここではDM100から10CH分のデータを読み出すコマンドをセットしています
FINSコマンドの内容についてはFINSコマンドを参照してください

40行目


U.BeginSend(cmd, cmd.Length, "192.168.250.1", 9600, SendCallback, U);
									

非同期的にデータを送信します
データを送信したらSendCallbackメソッドを実行します

43-59行目


private void SendCallback(IAsyncResult ar)
{
  System.Net.Sockets.UdpClient udp = (System.Net.Sockets.UdpClient)ar.AsyncState;

  try
  {
    udp.EndSend(ar);
  }
  catch (System.Net.Sockets.SocketException ex)
  {
    Console.WriteLine(ex.Message);
  }
  catch (ObjectDisposedException ex)
  {
    Console.WriteLine("Already Closed");
  }
}
									

データを送信したら終了処理をします
エラーがあれば53行目でコンソールにエラー出力します
すでに閉じられていたら57行目でコンソールにコメントを出力して終了します

61-68行目


private delegate void AddStringDelegete(byte[] str);
private void AddText(byte[] msg)
{
  if (msg == null)
      return;

  textBox1.AppendText(BitConverter.ToString(msg) + "\r\n");
}
									

テキストボックスにメッセージを表示するためのデリゲートを準備します
AddStringDelegeteで渡された文字列をテキストボックスに追加します
これは次に出てくるReceiveCallbakが別スレッドのため、ReceiveCallbakで受信したデータをDelegeteを参照してフォームのテキストボックスに値を渡します

70-81行目


private void ReceiveCallbak(IAsyncResult ar)
{
  System.Net.Sockets.UdpClient udp = (System.Net.Sockets.UdpClient)ar.AsyncState;
  System.Net.IPEndPoint remoteEP = null;
  byte[] receiveBytes = udp.EndReceive(ar, ref remoteEP);
  System.Net.IPAddress receiveAddress = remoteEP.Address;

  AddStringDelegete dlg = new AddStringDelegete(AddText);
  this.Invoke(dlg, new object[] { receiveBytes });

  U.BeginReceive(ReceiveCallbak, U);
}
									

14行目のBeginReceiveで指定した通りデータを受信するとReceiveCallbakが実行されます
74行目のEndReceiveで受信データをreceiveBytesのバイト配列に格納します
77行目でAddStringDelegeteを作成して78行目のInvokeで受信したデータを渡して実行します
処理を抜ける前に再度BeginReceiveを実行して非同期受信するようにしています


サンプルソース

GitHub SampleFins

GitHubからここで紹介したソースがダウンロードできます


この記事へのコメント