ADSを介して変数読み書きするPowerShellスクリプト実装#

こちらのInfoSysにて、様々な方法でデータを書き込む事例が紹介されています。ここでは構造体などの複雑なデータモデルも、メモリイメージのまま一括して送る方法や、単体の変数ノードを指定して送る方法などが紹介されています。

ここでは、最もPowerShellの高次レベルでデータモデルに読み出し、PowerShell内で一旦CSVファイルに出力し、CSVで編集した値をTwinCATの変数の値へ書き戻す実装例(リスト 6.29)をご紹介します。

リスト 6.29 PowerShellスクリプトによるCSVを通じた変数操作サンプルコード#
  1<#
  2TcXaeMgmtによるTwinCAT変数のCSV出力と書き戻しPowerShellスクリプト例
  3#>
  4
  5function write-value {
  6    Param(
  7        $ads_value, # データオブジェクト
  8        $symbol, # TwinCATのシンボルオブジェクト
  9        [ref]$dict, # 変数のシンボルパスと値の連想配列を参照渡し
 10        [bool]$ReadOnly = $false, # TwinCAT変数へは書込みを行わないオプションを有効にする
 11        [bool]$MergeDict = $false # $dictの定義内容をTwinCAT変数へ反映する動作を有効にする
 12    )
 13
 14    if ($ads_value -is [array]){
 15        # 配列ならば要素ごとに再起呼び出し
 16        for ($i=$ads_value.GetLowerBound(0);$i -le $ads_value.GetUpperBound(0); $i++){
 17            write-value $ads_value.item($i) $symbol.item($i) $dict $ReadOnly $MergeDict
 18        }
 19    }elseif($symbol.IsContainerType){
 20        # 構造体ならば要素ごとに再起呼び出し
 21        foreach ($sym in $symbol.MemberInstances){
 22            $instance_name = $sym.InstanceName
 23            write-value $ads_value.$instance_name $sym $dict $ReadOnly $MergeDict
 24        }
 25    }else{
 26        # プリミティブの場合の処理
 27        if(!$ReadOnly){
 28            # ReadOnlyでなければADS経由で変数に書き込み
 29            if ($MergeDict -and $dict.Value.ContainsKey($symbol.InstancePath) -and $dict.Value[$symbol.InstancePath] -ne $ads_value){
 30                # マージオプションが有効なら、ディクショナリの値と$ads_dataの内容が異なる場合にディクショナリの内容に書き換える
 31                $ads_value = $dict.Value[$symbol.InstancePath]
 32            }
 33            $symbol | Write-TcValue -Value $ads_value -Force
 34        }
 35        # ディクショナリを更新
 36        if($dict.Value.ContainsKey($symbol.InstancePath)){
 37            $dict.Value[$symbol.InstancePath] = $ads_value
 38        }else{
 39            $dict.Value.add($symbol.InstancePath, $ads_value)
 40        }
 41
 42    }
 43}
 44
 45<#
 46メインロジック
 47#>
 48
 49
 50# ローカルPCのPLCへ接続
 51$session = New-TcSession -NetId '127.0.0.1.1.1' -Port 851
 52
 53<#
 54構造体で定義された複雑なデータ構造を持つ変数の現在値を一括して読み出して"original.csv"という名前のCSVファイルに出力する。
 55#>
 56
 57# 読み出し
 58$symbol = $session | Get-TcSymbol -path 'GVL.test_data'
 59$data = $symbol | Read-TcValue
 60
 61
 62# エクスポート用の連想配列
 63$result = [System.Collections.Generic.Dictionary[String, PSObject]]::new()
 64
 65# 現在値を保存するCSVファイル名の指定
 66$current_data_file_name = "original.csv"
 67
 68# 変数の内容を全検索して、エクスポート用の連想配列にセット
 69# -ReadOnly $trueによりTwinCATの変数には反映しない
 70write-value $data $symbol ([ref]$result) -ReadOnly $true
 71# $result 連想配列を加工し、original.csvファイルにCSVファイルとして出力
 72$result.GetEnumerator() | Select @{N="Variable"; E={$_.Key}}, @{N="Value"; E={$_.Value}} | Export-Csv $current_data_file_name -Encoding Default -NoTypeInformation
 73
 74<#
 75CSVファイルで定義したデータをインポートして反映し、マージ後の全データをafter_merge.csvに保存する。
 76#>
 77
 78$import_file_name = "import.csv"
 79$after_merge_data_file_name = "after_merge.csv"
 80
 81if(Test-Path $import_file_name){
 82
 83    # CSVファイルを読みだしてオブジェクト作成
 84    $import_data = Import-Csv -Path $import_file_name -Encoding Default
 85
 86
 87    # インポート用の連想配列新規作成
 88    $import = [System.Collections.Generic.Dictionary[String, PSObject]]::new()
 89
 90    # CSVファイルから読みだしたオブジェクトを、$import連想配列に変換
 91    foreach($row in $import_data){
 92        if($import.ContainsKey($row.Variable)){
 93            $import[$row.Variable] = $row.Value
 94        }else{
 95            $import.add($row.Variable, $row.Value)
 96        }
 97    }
 98
 99    # 連想配列のデータをマージモードでTwinCATへ書き出し
100    # -ReadOnly $true を指定しないとデフォルト設定が有効となり、TwinCATへの変数書込みが実施される
101    # -MergeDict $true で$importの連想配列の値を反映させる。
102    write-value $data $symbol ([ref]$import) -MergeDict $true
103
104    # マージ後の全データをafter_merge.csvにCSVファイルとして出力
105    $import.GetEnumerator() | Select @{N="Variable"; E={$_.Key}}, @{N="Value"; E={$_.Value}} | Export-Csv $after_merge_data_file_name -Encoding Default -NoTypeInformation
106
107}

このサンプルコードの使い方#

  1. まずリスト 6.29のスクリプトを拡張子 .ps1 としたテキストファイルとして保存し、IPC内に配置します。このとき、58行目の-pathに続く個所を、読み書き対象の構造体変数名に書き換えます。

    $symbol = $session | Get-TcSymbol -path 'GVL.test_data'
    
  2. スクリプトを実行します。original.csvというファイルが出来上がります。このファイルには、スクリプトを実行した時点での指定した変数ツリーの全変数パスとその値が一覧されます。

  3. original.csvを基にして、import.csvという名前のCSVファイルを作成してください。このCSVを編集し、任意の変数パスの値を書き換える値に変更します。書き換えが不要な行は削除してください。

  4. もう一度実行すると、after_merge.csvというファイルができあがります。このファイルはimport.csvの定義内容が反映された全ての変数パスの値が一覧されます。

サンプルコードの解説#

write-value関数#

Get-TcSymbolおよびRead-TcValueにて得たシンボル、およびデータオブジェクトを通じて、データを読み書きする関数を定義します。パラメータは以下の通りです。

$ads_value

書き込みたいデータオブジェクト

$symbol

TwinCATのシンボルオブジェクト

[ref]$dict

連想配列オブジェクトの参照をセットする。空の連想配列が渡された場合、本関数を実行することで、ADSを通じて読み込まれた変数のシンボルパスと値の辞書が生成される。

また、あらかじめ変数シンボルパスと値をセットした連想配列オブジェクトの参照を渡した上でMergeDictオプションが$trueにすると、連想配列の定義内容に従いTwinCAT上の該当変数パスの値を書き換える。

[bool]$ReadOnly

デフォルト値 : $false

TwinCAT変数へは書込みを行わないオプションを有効にする

[bool]$MergeDict

デフォルト値 : $false

$dictの定義内容でTwinCAT変数を書き換える動作を有効にする

ADS接続とシンボルおよびデータオブジェクトの作成#

最初にADS接続セッションオブジェクトを作成します。本例ではIPC上のWindows上で実行することを前提としていますので、ローカルターゲットである127.0.0.1.1.1で、1台目のPLCのTcCOMであるADSポート851を指定します。

# ローカルPCのPLCへ接続
$session = New-TcSession -NetId '127.0.0.1.1.1' -Port 851

つづいてシンボルおよびデータオブジェクトを作成します。

まず、Get-TcSymbolにてシンボルオブジェクトを取得します。ここで読み書き対象の変数を-path指定します。この例ではグローバル変数 GVL.test_data の宣言で実装したグローバル変数を指定します。

また、シンボルオブジェクトをパイプを通してRead-TcValueを実行することで、PLC変数を読みだしたデータオブジェクトを生成します。

# 読み出し
$symbol = $session | Get-TcSymbol -path 'GVL.test_data'
$data = $symbol | Read-TcValue

読みだしたデータオブジェクトは、下記に示すエントリポイント以下からは、TwinCAT上で構築したデータモデルそのままのモデルとしてPowerShell上で直接編集可能です。

CSV用の連想配列オブジェクト生成#

write-valueの引数$dictは、参照渡しで連想配列オブジェクトを渡します。次の通りnew()により空の連想配列オブジェクトへの参照([ref]$result)として引数に渡しています。

なお、ここでは変数の内容を読み取って連想配列オブジェクトを生成することが目的なので、TwinCAT変数への書き込みは行いません。したがって、-ReadOnly $trueオプション指定とします。

# エクスポート用の連想配列
$result = [System.Collections.Generic.Dictionary[String, PSObject]]::new()
   :
write-value $data $symbol ([ref]$result) -ReadOnly $true

write-value関数により処理された$result連想配列オブジェクトには、指定した構造体変数の中身のシンボルパスと値のリストが格納されています。次の行で、CSVファイルに出力します。なお、$current_data_file_nameにあらかじめ出力したいCSVのファイル名を記述します。ディレクトリを省略していますので、スクリプト実行時のカレントディレクトリ上に指定した名前のCSVファイルoriginal.csvが生成されます。

# 現在値を保存するCSVファイル名の指定
$current_data_file_name = "original.csv"
 :
$result.GetEnumerator() | Select @{N="Variable"; E={$_.Key}}, @{N="Value"; E={$_.Value}} | Export-Csv $current_data_file_name -Encoding Default -NoTypeInformation

import.csvを読み込んで変数の値を上書きする#

まず、Test-Pathにて、import.csvがあるかどうか確かめます。存在すれば、import.csvの定義による値の上書き処理が実行されます。

$import_file_name = "import.csv"
$after_merge_data_file_name = "after_merge.csv"

if(Test-Path $import_file_name){
    :
}

まず、以下の処理ブロックにて、CSVファイルを読み取り、1行づつVariableという先頭行のヘッダの列をキー、Valueという先頭行のヘッダの列を値として順次読み取り、$importという連想配列オブジェクトにセットします。

    # CSVファイルを読みだしてオブジェクト作成
    $import_data = Import-Csv -Path $import_file_name -Encoding Default


    # インポート用の連想配列新規作成
    $import = [System.Collections.Generic.Dictionary[String, PSObject]]::new()

    # CSVファイルから読みだしたオブジェクトを、$import連想配列に変換
    foreach($row in $import_data){
        if($import.ContainsKey($row.Variable)){
            $import[$row.Variable] = $row.Value
        }else{
            $import.add($row.Variable, $row.Value)
        }
    }

このあと、作成した連想配列オブジェクトを参照渡ししwrite-valueを呼び出します。こんどは-ReadOnly $trueは指定せず、代わりに-MergeDict $trueを指定します。-ReadOnlyはデフォルト値$falseが指定されているので、指定しないことにより書き込みモードとしてふるまい、$dataの内容に加えて、$importの連想配列オブジェクトの内容を上書きした値がTwinCAT上の変数に反映されます。

    # 連想配列のデータをマージモードでTwinCATへ書き出し
    # -ReadOnly $true を指定しないとデフォルト設定が有効となり、TwinCATへの変数書込みが実施される
    # -MergeDict $true で$importの連想配列の値を反映させる。
    write-value $data $symbol ([ref]$import) -MergeDict $true

その後、$importには、元々$import_file_nameで定義されたリスト以外の全ての変数パスも加えられた状態で、反映後の変数パスとその値がセットされます。次の行でafter_merge.csvという名前でそのデータが出力されます。

    # マージ後の全データをafter_merge.csvにCSVファイルとして出力
    $import.GetEnumerator() | Select @{N="Variable"; E={$_.Key}}, @{N="Value"; E={$_.Value}} | Export-Csv $after_merge_data_file_name -Encoding Default -NoTypeInformation