FinsUDP
概略
ここではFinsUDPを使ってDMの値を読み出します

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からここで紹介したソースがダウンロードできます