ADSを介して変数読み書きするPowerShellスクリプト実装#
こちらのInfoSysにて、様々な方法でデータを書き込む事例が紹介されています。ここでは構造体などの複雑なデータモデルも、メモリイメージのまま一括して送る方法や、単体の変数ノードを指定して送る方法などが紹介されています。
ここでは、最もPowerShellの高次レベルでデータモデルに読み出し、PowerShell内で一旦CSVファイルに出力し、CSVで編集した値をTwinCATの変数の値へ書き戻す実装例(リスト 6.29)をご紹介します。
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}
このサンプルコードの使い方#
まずリスト 6.29のスクリプトを拡張子 .ps1 としたテキストファイルとして保存し、IPC内に配置します。このとき、58行目の
-path
に続く個所を、読み書き対象の構造体変数名に書き換えます。$symbol = $session | Get-TcSymbol -path 'GVL.test_data'
スクリプトを実行します。
original.csv
というファイルが出来上がります。このファイルには、スクリプトを実行した時点での指定した変数ツリーの全変数パスとその値が一覧されます。original.csv
を基にして、import.csv
という名前のCSVファイルを作成してください。このCSVを編集し、任意の変数パスの値を書き換える値に変更します。書き換えが不要な行は削除してください。もう一度実行すると、
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