ツールの作り方 – 通信編/FINSコマンド

ツール

今回はOMRONのFINSコマンドを説明します。
※Ethernet=Binary通信のみ
※FINSコマンドのマニュアルが手元にないと理解できないので前回を参考に入手してください

フレーム構成の確認

コマンドフレーム構成(PC→PLC)

No項目備考
1※TCPのみ
ヘッダ
FINSAsciiコード
(0x46494E53)
2※TCPのみ
データ長
********No3以降のデータ長
3※TCPのみ
コマンド
0x00000000
0x00000002
ノードアドレス情報送信
通常コマンド
4※TCPのみ
エラーコード
0x00000000固定値
5※TCPのみ
クライアント
ノードアドレス
0x00000000No3=0の場合のみ有効
Connect後に1回だけ送信
(2回以上送信しないこと)
6ICF0x80Bit7=1固定
Bit6=コマンド(0)
Bit0=レスポンス要(0)
7RSV0x00固定値
8GCT
*許容ブリッジ通過数
0x02
(or 0x07)
基本的に0x02
(8階層までのネットワーク越えを使う場合)
9DNA
*相手先ネットワークアドレス
0x00
0x01~7F
同一ネットワーク内
他のネットワーク→相手先ネットワーク
10DA1
*相手先ノードアドレス
0x00
(0x01~20)
(0x01~FE)
(0xFF)
自PLC内
(Controller Linkの場合)
(Ethernetの場合)
(一斉同報)
11DA2
*相手先号機アドレス
0x00
(0xFE)
(0x10~1F)
(0xE1)
CPUユニット
(ControllerLink or Ethernetユニット)
(CPU高機能ユニット)
(INNERボード)
12SNA
*発信元ネットワークアドレス
0x00
0x01~7F
同一ネットワーク内
他のネットワーク→発信元ネットワーク
13SA1
*発信元ノードアドレス
0x01~FETCP:レスポンスのNo5を指定
UDP:PC側IPアドレスの4桁目
14SA2
*発信元号機アドレス
0x00固定値(CPU扱い)
15SID
*サービスID
0x00~FF任意
発信元の識別子
16コマンドコード0x0101
0x0102 など
読出
書込
17テキスト****デバイス、書込値など

TCPの場合はConnect後に1回だけノードアドレス情報送信(≒問合せ)を実行し、その応答データ内にあるクライアントノードアドレスを使うのが確実です。(詳細は後述)

No1~No5はTCPの場合のみ必要になります。しかもFINSコマンドのマニュアルには記載されていないので注意してください。Ethernetユニットのアプリケーション構築編(SBCD-330E)を参照してください。(PDFファイル内の”FINS/TCPヘッダ”で検索すれば見つかるハズです。)

No9~No14についてはMCプロトコルと同様に他局(PLC間のネットワーク越しに)アクセスすることも可能ですが同一ネットワーク内でアクセスするパターンがほとんどと思うので上段側の値を使います。このあたりの説明はマニュアル内でコマンドとレスポンスを一緒くたに説明しているせいかすごく意味が分かりづらいです。

心の声
心の声

むしろ教えて欲しいくらい。。

レスポンスフレーム構成(PLC→PC)

No項目備考
1※TCPのみ
ヘッダ
FINSAsciiコード
(0x46494E53)
2※TCPのみ
データ長
********No3以降のデータ長
3※TCPのみ
コマンド
0x00000001
0x00000002
0x00000003
ノードアドレス情報送信
通常コマンド
エラー通知→受信したら通信回線Close
4※TCPのみ
エラーコード
********正常時は0x00000000
No3=2の場合はチェック不要
5※TCPのみ
クライアント側(PC側)の
ノードアドレス
0x00000000
~
0x000000FE
コマンドのNo3=0の場合のみ有効
コマンドのNo13(SA1)に指定するデータ
6※TCPのみ
サーバー側(PLC側)の
ノードアドレス
0x00000000
~
0x000000FE
コマンドのNo3=0の場合のみ有効
基本はPLCのIPアドレスの4桁目
7ICF0xC1Bit7=1固定
Bit6=レスポンス(1)
Bit0=レスポンス不要(1)
8RSV**システム使用→無視
9GCT
*許容ブリッジ通過数
**システム使用→無視
(送信値から越えたネットワーク数減算)
10DNA
*相手先ネットワークアドレス
0x00
0x01~7F
同一ネットワーク内
他のネットワーク→相手先ネットワーク
11DA1
*相手先ノードアドレス
**PLCから見たPCのノードアドレス
基本はPC側のIPアドレスの4桁目
12DA2
*相手先号機アドレス
0x00固定値(CPU扱い)
13SNA
*発信元ネットワークアドレス
0x00
(0x01~7F)
自ネットワーク
(相手先ネットワーク)
14SA1
*発信元ノードアドレス
**(固定値=0?)
15SA2
*発信元号機アドレス
0x00CPU
16SID
*サービスID
**送信したSIDと同じ値
17コマンドコード0x0101
0x0102 など
読出
書込
18終了コード****正常時は0x0000
19テキスト****読出データ
書込の場合はテキストが無い

No1~No6はTCPの場合のみ必要になります。

チェックすべきはNo3、No5、No18、No19です。他は無視しても構いません。

No3で3を受信した場合はサーバ側(PLC側)が通信回線Closeするのでクライアント側(PC側)も通信回線をCloseする必要があります。(必要に応じて再度回線Open)

No18の終了コードは必ずチェックしてください。終了コードが0ならば正常ですが、通信自体が正常でも0以外になることがあります。終了コード(2Byte)のBit7(本体停止異常)とBit6(本体継続異常)は無視するのが手っ取り早い方法(0xFF3Fでマスク)です。
※PLCのバッテリ異常などが該当します。(プログラムモード(停止状態)も該当したような。。?)

コマンドとデバイスの指定

コマンドについて

※デバイス(=I/Oメモリ)の読み書きを抜粋

名称コマンド機能
I/Oメモリエリアの読出0x0101連続したエリアの読出
I/Oメモリエリアの書込0x0102連続したエリアへ書込
I/Oメモリエリアの複合読出0x0104不連続なエリアの読出
※MCプロトコルのランダム読出相当

デバイス指定について

※CS/CJ/CP/NSJシリーズの場合(CVM1/CVシリーズはメモリ種別の値が異なる)

エリア種別
(デバイス種別)
データ
種類
メモリ
種別
備考
チャネルI/O(CIO)ビット0x30
チャネル0xB0
内部補助リレー(W)ビット0x31
チャネル0xB1
保持リレー(H)ビット0x32
チャネル0xB2
特殊補助リレー(A)ビット0x33
チャネル0xB3
タイマアップフラグ(T)ビット0x09
タイマ現在値チャネル0x89
カウンタアップフラグ(C)ビット0x09要アドレスオフセット
カウンタ現在値チャネル0x89要アドレスオフセット
データメモリ(D)ビット0x02
チャネル0x82
拡張データメモリ(E)ビット0x0AビットはCJ2のみアクセス可
 ※カレントバンクチャネル0x98
拡張データメモリ(E0~EF)ビット0x20~2F
 ※バンク指定チャネル0xA0~AF
拡張データメモリ(E10~E18)ビット0xE0~E8
 ※バンク指定チャネル0x60~68

デバイス指定=I/Oメモリアドレス指定は4Byteです。
⇒メモリ種別1Byte+チャネル2Byte+ビット1Byte
※ビットは0~15(0x00~0F)、チャネル単位=ワード単位でアクセスする場合は0を指定
※タイマおよびカウンタのアップフラグのビット指定は常に0

タイマとカウンタはメモリ種別が同じことに注意してください。
このためカウンタのみ、2Byteのチャネル指定(カウンタ番号)に0x8000のオフセットが必要となります。
※マニュアルに一覧表があり、その直下に例としていくつかのI/Oメモリアドレス指定する値が記載されています。。が、その中にオフセットが必要なカウンタの例はありません

心の声
心の声

○MRONさん、わざとですか?
と聞いてみたい。

ノード番号について

FINSコマンドのデータ送信時に発信元ノードアドレス(SA1)を指定する必要がありますが前述のとおりTCPの場合はConnect後にノードアドレス情報を問い合わせる方法が確実です。
その際のサンプルコードを下記に示します。

※CopyMemoryとして定義しているRTLMoveMemory APIについては前回を参照

Imports System.Net
Imports System.Net.Sockets

    ' 送受信バッファと変数間のコピーで使うAPI。ByRefにしてポインタ渡し。VBAやVB6.0以前だと"**** As Any"という型が使えたけどVB.net以降はAnyが使えない。
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Integer, ByVal length As Integer)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByRef Source As Byte, ByVal length As Integer)

    Private _sock As Socket
    Private _sa1 As Integer

    Private Sub GetTCPNodeAddressSample()
        Dim temp As Integer
        Dim tx() As Byte            ' 送信バッファ
        Dim idx As Integer          ' 送信バッファ アクセスIndex
        Dim sentLen As Integer      ' 送信Byte数


        ' 送信データ
        ReDim tx(4095)  ' バッファサイズは適当に。
        idx = 0

        ' 固定値("FINS") *4Byte
        tx(idx + 0) = CByte(&H46)   ' F
        tx(idx + 1) = CByte(&H49)   ' I
        tx(idx + 2) = CByte(&H4E)   ' N
        tx(idx + 3) = CByte(&H53)   ' S
        idx += 4
        ' データ長 *4Byte
        ' コマンド(4)+エラーコード(4)+クライアントノードアドレス(4)で計12Byte
        temp = IPAddress.HostToNetworkOrder(12)
        CopyMemory(tx(idx), temp, 4)
        idx += 4
        ' コマンド(ノードアドレス情報送信) *4Byte
        temp = 0
        CopyMemory(tx(idx), temp, 4)
        idx += 4
        ' エラーコード(=0)で固定 *4Byte
        temp = 0
        CopyMemory(tx(idx), temp, 4)
        idx += 4
        ' クライアントノードアドレス *4Byte
        ' ※0を指定するとサーバ側(PLC側)がノードアドレスを返してくれる
        temp = 0
        CopyMemory(tx(idx), temp, 4)
        idx += 4

        ' 送信
        ' _sockはSocketクラスで既にConnectされているものとする
        sentLen = _sock.Send(tx, idx, SocketFlags.None)



        Dim rx() As Byte            ' 受信バッファ
        Dim recvLen As Integer      ' 受信Byte数

        ' 受信
        ReDim rx(4095)  ' バッファサイズは適当に。
        recvLen = _sock.Receive(rx)
        idx = 0

        ' チェック
        If recvLen <> 24 Then Return ' この応答は24Byte以外そもそもNG。
        ' 固定値("FINS")
        If rx(idx + 0) <> &H46 OrElse rx(idx + 1) <> &H49 OrElse rx(idx + 2) <> &H4E OrElse rx(idx + 3) <> &H53 Then Return
        idx += 4
        ' データ長4Byteは無視
        idx += 4
        ' コマンド4Byteはノードアドレス情報送信(=1)
        CopyMemory(temp, rx(idx), 4)
        temp = IPAddress.NetworkToHostOrder(temp)
        If temp <> 1 Then Return
        idx += 4
        ' エラーコード4Byte
        CopyMemory(temp, rx(idx), 4)
        temp = IPAddress.NetworkToHostOrder(temp)
        If temp <> 0 Then Return
        idx += 4
        ' クライアントノードアドレス4Byte
        CopyMemory(temp, rx(idx), 4)
        _sa1 = IPAddress.NetworkToHostOrder(temp)   ' この値を次回以降の通信で指定するSA1として保持(値は1~254)
        idx += 4
        ' サーバノードアドレス4Byteは無視
        idx += 4

    End Sub

このノードアドレス情報の問合せ・取得はConnect直後に1回だけ実行し、2回以上実行しないでください。

またマルチバイト(2,4,8などの複数Byte)の扱いには注意が必要です。FINSコマンドはビッグエンディアン、Windowsの内部処理はリトルエンディアンでバイトオーダーが逆となるため上位⇔下位Byteの並べ替えが必須になります。
上記では
 System.Net.IPAddress.HostToNetworkOrderメソッド
 System.Net.IPAddress.NetworkToHostOrderメソッド
を使用して並べ替えています。

ここでいうHostとはPC側=Windows内部処理データ、Networkとは実通信データを指します。内部処理データ→送信データにするときはHostToNetworkOrderを使い、受信データ→内部処理データにするときはNetworkToHostOrderを使うのが一般的です。が、実のところどちらを使っても処理結果自体は変わりません。(上位⇔下位の並べ替えなのでどちらもやることは同じ)

ちなみにUDPの場合は上記の方法が使えないのでPC側IPアドレスの4桁目をノードアドレスとして指定すればよいのですが、何かしらの手段でユーザが設定しなければならない作り方にすると使い勝手が良くありません。
なのでPC側のネットワークインターフェース情報を取得・検索する方法が便利です。
以下にサンプルコードを示します。

Option Explicit On
Imports System.Net
Imports System.Net.NetworkInformation

    ' 送受信バッファと変数間のコピーで使うAPI。ByRefにしてポインタ渡し。VBAやVB6.0以前だと"**** As Any"という型が使えたけどVB.net以降はAnyが使えない。
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByRef Source As Byte, ByVal length As Integer)

    Private _sa1 As Integer

    Private Sub GetUDPNodeAddressSample()
        Dim nis() As NetworkInterface = NetworkInterface.GetAllNetworkInterfaces()
        Dim ipprop As IPInterfaceProperties
        Dim temp() As Byte
        Dim plcip As Integer
        Dim mask As Integer
        Dim pcip As Integer

        ' PC側に接続先IPと同じサブネットが見つからない場合は仮に254としておく
        _sa1 = 254

        ' 接続先PLCのIP(4Byte)情報
        temp = IPAddress.Parse("192.168.1.1").GetAddressBytes()
        CopyMemory(plcip, temp(0), 4)

        ' PCのネットワーク情報
        For Each ni In nis
            If ni.OperationalStatus = NetworkInformation.OperationalStatus.Up AndAlso _
               ni.NetworkInterfaceType <> NetworkInformation.NetworkInterfaceType.Loopback AndAlso _
               ni.NetworkInterfaceType <> NetworkInformation.NetworkInterfaceType.Tunnel Then

                ipprop = ni.GetIPProperties()
                If ipprop IsNot Nothing Then
                    For Each ip In ipprop.UnicastAddresses  ' ←コレはIPv4とIPv6が含まれる
                        If ip.Address.AddressFamily = Sockets.AddressFamily.InterNetwork Then   ' IPv4のみ検索対象とする
                            ' サブネットマスク
                            temp = ip.IPv4Mask.GetAddressBytes()
                            CopyMemory(mask, temp(0), 4)
                            ' IPアドレス
                            temp = ip.Address.GetAddressBytes()
                            CopyMemory(pcip, temp(0), 4)
                            ' サブネットが接続先PLCと同じものを検出
                            If (pcip And mask) = (plcip And mask) Then
                                _sa1 = temp(3)   ' IPの4桁目。
                                Return
                            End If
                        End If
                    Next
                End If

            End If
        Next

    End Sub

最近のPCにはネットワークインターフェースが複数あるパターン(無線LANと有線LANなど)も珍しくありませんので、何も考えずにPC内のネットワーク情報を取得したIPアドレスを使うような処理では使い物になりません。(たまたま運良く動く可能性もあるが。。)

接続先PLCのIPアドレスサブネットマスクを使って同じネットワーク内のものであることをチェックすべきです。もし上記の処理で接続先PLCと同じサブネットのIPアドレスが見つからないのであればPLCと通信出来ないので。。

また知っているとは思いますが念のため。(仕事上でもたまに知らない人に出くわすことがあるんですよね。。)
IPアドレスとサブネットマスクで論理積(AND演算)をとった値(ネットワークアドレスなどと呼ぶ)が同じものは、同じサブネットである/同一ネットワークであると判別できます。

PLCPC
IPアドレス
サブネットマスク
ネットワークアドレス
192.168.1.1
255.255.255.0
192.168.1.0
192.168.1.100
255.255.255.0
192.168.1.0
同じネットワーク
IPアドレス
サブネットマスク
ネットワークアドレス
192.168.1.1
255.255.255.0
192.168.1.0
192.168.2.100
255.255.255.0
192.168.2.0
別のネットワーク
IPアドレス
サブネットマスク
ネットワークアドレス
192.168.1.1
255.255.0.0
192.168.0.0
192.168.2.100
255.255.0.0
192.168.0.0
同じネットワーク
(見かけない
サブネットマスクだが。)

デバイス読出

例としてD1000から100点読み出す場合のコードです。

Option Explicit On
Imports System.Net
Imports System.Net.Sockets

    ' 送受信バッファと変数間のコピーで使うAPI。ByRefにしてポインタ渡し。VBAやVB6.0以前だと"**** As Any"という型が使えたけどVB.net以降はAnyが使えない。
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Short, ByVal length As Integer)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Short, ByRef Source As Byte, ByVal length As Integer)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Integer, ByVal length As Integer)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByRef Source As Byte, ByVal length As Integer)

    Private _sock As Socket
    Private _sa1 As Integer
    Private _IsTCP As Boolean

    Private Sub ReadSample()
        Dim temp As Integer
        Dim temp2 As Short
        Dim tx() As Byte            ' 送信バッファ
        Dim idx As Integer          ' 送信バッファ アクセスIndex
        Dim sentLen As Integer      ' 送信Byte数

        Dim cmd As Short = &H101    ' コマンド(読出)
        Dim devTyp As Byte          ' I/Oメモリ種別(デバイスコード)
        Dim devChn As Short         ' チャネル番号(デバイス番号)
        Dim devBit As Byte          ' ビット番号
        Dim readLen As Short        ' 読出点数

        Dim iDNA As Integer = 0     ' 相手先ネットワークアドレス
        Dim iDA1 As Integer = 0     ' 相手先ノードアドレス
        Dim iDA2 As Integer = 0     ' 相手先号機アドレス


        ' 送信データ
        ReDim tx(4095)  ' バッファサイズは適当に。
        idx = 0

        ' FINS/TCPヘッダ ※TCPの場合のみ
        If _IsTCP Then
            ' 固定値("FINS") *4Byte
            tx(idx + 0) = CByte(&H46)   ' F
            tx(idx + 1) = CByte(&H49)   ' I
            tx(idx + 2) = CByte(&H4E)   ' N
            tx(idx + 3) = CByte(&H53)   ' S
            idx += 4
            ' データ長 *4Byte
            ' コマンド(4)+エラーコード(4)+FINSヘッダ(10)+コマンドコード(2)で計12Byte
            temp = IPAddress.HostToNetworkOrder(12)
            CopyMemory(tx(idx), temp, 4)
            idx += 4
            ' コマンド(通常コマンド) *4Byte
            temp = IPAddress.HostToNetworkOrder(2)
            CopyMemory(tx(idx), temp, 4)
            idx += 4
            ' エラーコード(=0)で固定 *4Byte
            temp = 0
            CopyMemory(tx(idx), temp, 4)
            idx += 4
        End If
        ' FINSヘッダ *10Byte
        tx(idx + 0) = CByte(&H80)           ' ICF
        tx(idx + 1) = CByte(&H0)            ' RSV
        tx(idx + 2) = CByte(&H2)            ' GCT (2 or 7)
        tx(idx + 3) = CByte(iDNA And &HFF)  ' DNA
        tx(idx + 4) = CByte(iDA1 And &HFF)  ' DA1
        tx(idx + 5) = CByte(iDA2 And &HFF)  ' DA2
        tx(idx + 6) = CByte(&H0)            ' SNA
        tx(idx + 7) = CByte(_sa1 And &HFF)  ' SA1
        tx(idx + 8) = CByte(&H0)            ' SA2
        tx(idx + 9) = CByte(&H0)            ' SID
        idx += 10

        ' コマンドコード *2Byte
        temp2 = IPAddress.HostToNetworkOrder(cmd)
        CopyMemory(tx(idx), temp2, 2)
        idx += 2

        ' デバイス *1+2+1=4Byte
        devTyp = CByte(&H82)    ' D(チャネル単位)
        idx += 1
        devChn = 1000S          ' チャネル番号
        temp2 = IPAddress.HostToNetworkOrder(devChn)
        CopyMemory(tx(idx), temp2, 2)
        idx += 2
        devBit = CByte(0)       ' ビット番号
        tx(idx) = devBit
        idx += 1
        ' 読出点数 *2Byte
        readLen = 100
        temp2 = IPAddress.HostToNetworkOrder(readLen)
        CopyMemory(tx(idx), temp2, 2)
        idx += 2

        ' 送信
        ' _sockはSocketクラスで既にConnectされているものとする
        sentLen = _sock.Send(tx, idx, SocketFlags.None)



        Dim rx() As Byte            ' 受信バッファ
        Dim recvLen As Integer      ' 受信Byte数
        Dim resLen As Integer       ' 応答データ長
        Dim endCode As Short        ' 終了コード
        Dim readData() As Short     ' 読出データ(デバイス値)
        Dim i As Integer

        ' 受信
        ReDim rx(4095)  ' バッファサイズは適当に。
        recvLen = _sock.Receive(rx)
        idx = 0


        ' FINS/TCPヘッダ ※TCPの場合のみ
        If _IsTCP Then
            ' 固定値("FINS")(4Byte)
            If rx(idx + 0) <> &H46 OrElse rx(idx + 1) <> &H49 OrElse rx(idx + 2) <> &H4E OrElse rx(idx + 3) <> &H53 Then Return
            idx += 4
            ' データ長(4Byte)
            CopyMemory(temp, rx(idx), 4)
            resLen = IPAddress.NetworkToHostOrder(temp)
            If recvLen <> resLen + 8 Then Return
            idx += 4
            ' コマンドコード(4Byte)
            CopyMemory(temp, rx(idx), 4)
            temp = IPAddress.NetworkToHostOrder(temp)
            If temp = 3 Then
                ' FINSフレーム送信エラー通知
                ' ※通信回線Closeが必要
                Return
            End If
            idx += 4
            ' エラーコード(4Byte)は無視
            idx += 4
        End If
        ' FINSヘッダ(10Byte)は無視
        idx += 10
        ' コマンドコード(2Byte)は無視
        idx += 2
        ' 終了コード(2Byte)
        CopyMemory(temp2, rx(idx), 2)
        endCode = IPAddress.NetworkToHostOrder(temp2)
        endCode = endCode And &HFF3FS   ' 本体停止異常(Bit7)と本体継続異常(Bit6)を無視 *PLC的には異常だが通信自体はOKなので。
        idx += 2
        If endCode <> 0 Then Return ' 正常時は0

        ' テキスト=読出データ(読出点数×2Byte)
        If (recvLen - idx) = readLen * 2 Then Return ' ※チャネル単位読出は1要素=2Byte、ビット単位は1要素=1Byte
        ReDim readData(readLen - 1)
        For i = 0 To readLen - 1
            CopyMemory(temp2, rx(idx), 2)
            readData(i) = IPAddress.NetworkToHostOrder(temp2)
            idx += 2
        Next

    End Sub

注意点は前回(MCプロトコル)とほぼ同じですが、MCプロトコルとは最大点数が異なります。
FINSコマンドでの1回の交信(送信&受信)の読出は最大999点(ワード)の制約があります。(ランダム読出の場合167)

デバイス書込

例としてW100.10をSET(1)する場合のコードです。

Option Explicit On
Imports System.Net
Imports System.Net.Sockets

    ' 送受信バッファと変数間のコピーで使うAPI。ByRefにしてポインタ渡し。VBAやVB6.0以前だと"**** As Any"という型が使えたけどVB.net以降はAnyが使えない。
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Short, ByVal length As Integer)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Short, ByRef Source As Byte, ByVal length As Integer)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByRef Source As Integer, ByVal length As Integer)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByRef Source As Byte, ByVal length As Integer)

    Private _sock As Socket
    Private _sa1 As Integer
    Private _IsTCP As Boolean

    Private Sub WriteSample()
        Dim temp As Integer
        Dim temp2 As Short
        Dim tx() As Byte            ' 送信バッファ
        Dim idx As Integer          ' 送信バッファ アクセスIndex
        Dim sentLen As Integer      ' 送信Byte数

        Dim cmd As Short = &H102    ' コマンド(書込)
        Dim devTyp As Byte          ' I/Oメモリ種別(デバイスコード)
        Dim devChn As Short         ' チャネル番号(デバイス番号)
        Dim devBit As Byte          ' ビット番号
        Dim writeLen As Short       ' 書込点数

        Dim iDNA As Integer = 0     ' 相手先ネットワークアドレス
        Dim iDA1 As Integer = 0     ' 相手先ノードアドレス
        Dim iDA2 As Integer = 0     ' 相手先号機アドレス


        ' 送信データ
        ReDim tx(4095)  ' バッファサイズは適当に。
        idx = 0

        ' FINS/TCPヘッダ ※TCPの場合のみ
        If _IsTCP Then
            ' 固定値("FINS") *4Byte
            tx(idx + 0) = CByte(&H46)   ' F
            tx(idx + 1) = CByte(&H49)   ' I
            tx(idx + 2) = CByte(&H4E)   ' N
            tx(idx + 3) = CByte(&H53)   ' S
            idx += 4
            ' データ長 *4Byte
            ' コマンド(4)+エラーコード(4)+FINSヘッダ(10)+コマンドコード(2)で計12Byte
            temp = IPAddress.HostToNetworkOrder(12)
            CopyMemory(tx(idx), temp, 4)
            idx += 4
            ' コマンド(通常コマンド) *4Byte
            temp = IPAddress.HostToNetworkOrder(2)
            CopyMemory(tx(idx), temp, 4)
            idx += 4
            ' エラーコード(=0)で固定 *4Byte
            temp = 0
            CopyMemory(tx(idx), temp, 4)
            idx += 4
        End If
        ' FINSヘッダ *10Byte
        tx(idx + 0) = CByte(&H80)           ' ICF
        tx(idx + 1) = CByte(&H0)            ' RSV
        tx(idx + 2) = CByte(&H2)            ' GCT (2 or 7)
        tx(idx + 3) = CByte(iDNA And &HFF)  ' DNA
        tx(idx + 4) = CByte(iDA1 And &HFF)  ' DA1
        tx(idx + 5) = CByte(iDA2 And &HFF)  ' DA2
        tx(idx + 6) = CByte(&H0)            ' SNA
        tx(idx + 7) = CByte(_sa1 And &HFF)  ' SA1
        tx(idx + 8) = CByte(&H0)            ' SA2
        tx(idx + 9) = CByte(&H0)            ' SID
        idx += 10

        ' コマンドコード *2Byte
        temp2 = IPAddress.HostToNetworkOrder(cmd)
        CopyMemory(tx(idx), temp2, 2)
        idx += 2

        ' デバイス *1+2+1=4Byte
        devTyp = CByte(&H31)    ' W(ビット単位)
        idx += 1
        devChn = 100S           ' チャネル番号
        temp2 = IPAddress.HostToNetworkOrder(devChn)
        CopyMemory(tx(idx), temp2, 2)
        idx += 2
        devBit = CByte(10)      ' ビット番号
        tx(idx) = devBit
        idx += 1
        ' 書込点数 *2Byte
        writeLen = 1
        temp2 = IPAddress.HostToNetworkOrder(writeLen)
        CopyMemory(tx(idx), temp2, 2)
        idx += 2

        ' 送信
        ' _sockはSocketクラスで既にConnectされているものとする
        sentLen = _sock.Send(tx, idx, SocketFlags.None)



        Dim rx() As Byte            ' 受信バッファ
        Dim recvLen As Integer      ' 受信Byte数
        Dim resLen As Integer       ' 応答データ長
        Dim endCode As Short        ' 終了コード

        ' 受信
        ReDim rx(4095)  ' バッファサイズは適当に。
        recvLen = _sock.Receive(rx)
        idx = 0


        ' FINS/TCPヘッダ ※TCPの場合のみ
        If _IsTCP Then
            ' 固定値("FINS")(4Byte)
            If rx(idx + 0) <> &H46 OrElse rx(idx + 1) <> &H49 OrElse rx(idx + 2) <> &H4E OrElse rx(idx + 3) <> &H53 Then Return
            idx += 4
            ' データ長(4Byte)
            CopyMemory(temp, rx(idx), 4)
            resLen = IPAddress.NetworkToHostOrder(temp)
            If recvLen <> resLen + 8 Then Return
            idx += 4
            ' コマンドコード(4Byte)
            CopyMemory(temp, rx(idx), 4)
            temp = IPAddress.NetworkToHostOrder(temp)
            If temp = 3 Then
                ' FINSフレーム送信エラー通知
                ' ※通信回線Closeが必要
                Return
            End If
            idx += 4
            ' エラーコード(4Byte)は無視
            idx += 4
        End If
        ' FINSヘッダ(10Byte)は無視
        idx += 10
        ' コマンドコード(2Byte)は無視
        idx += 2
        ' 終了コード(2Byte)
        CopyMemory(temp2, rx(idx), 2)
        endCode = IPAddress.NetworkToHostOrder(temp2)
        endCode = endCode And &HFF3FS   ' 本体停止異常(Bit7)と本体継続異常(Bit6)を無視 *PLC的には異常だが通信自体はOKなので。
        idx += 2
        If endCode <> 0 Then Return ' 正常時は0

        ' テキスト=書込の場合は無し

    End Sub

注意点は読出とほぼ同様です。
上記はビット1点書込ですがワード単位書込でも交信1回の最大数に制約があります。(詳細はマニュアルを参照ください)
※読出と書込では最大数が異なるので注意

PLC側の通信設定

OMRONの場合はCPU内蔵Ethernetと単体のEthernetユニットで設定はほぼ同じです。

  • IPアドレス
    任意値を設定します。192.168.***.***あたりを使うパターンが一般的に多いです。
  • サブネットマスク
    通常は255.255.255.0を使います。
  • その他
    基本的にFINSコマンドを使う上では設定不要です。
  • FINS/UDPポート
    初期値(9600)のままでOKです。ユーザ定義=任意値にすることも可能です。
  • その他
    基本的に初期値のままでOKです。
  • FINS/TCPポート
    初期値(9600)のままでOKです。ユーザ定義=任意値にすることも可能です。
  • その他
    基本的に初期値のままでOKです。

三菱と比較した場合、必要な設定はIPアドレスのみなので簡単です。
通信回線は同一ポートに対して16まで(?)使えます。
※マニュアルに記載されていたような気がするのですが記憶がイマイチ(笑)

コメント