あらすじ
ビルドしたsimulinkモデルを用いて制御対象を動かすと同時にデータをロギングする際、ロギングツールによっては変数名にデータ型が付記されていた方が便利になる。
何故なら、そのロギングツールでロギング対象の変数を選択する際、正しいデータ型を選ばないと正常にロギングできないため。よって、ロギングする変数を追加する場合は、正しい型を指定できるように常にsimulinkモデルを見て型を確認しながらロギングリストに追加していく必要がある。これはかなり骨が折れる作業である。
本記事では、変数名にデータ型を付記することで、いちいちsimulinkモデルを確認しなくても、変数名からデータ型が分かるようにする。
手動で変数名を付記していくのは当然面倒なので、matlabでデータ型を自動で付記するスクリプトを作成する。
例えば
上記simulinkモデルのライン名に、それぞれのデータ型を付記する
(subsystem内部のライン(2枚目)も含め)
ただし、ライン名が最初から存在しないものには、何もしない。
① 変数のデータ型を取得する
所望のスクリプトを実現するため、まずは変数名のデータ型を取得する方法について考える。
方法としては、単純にそれぞれのラインのハンドルを取得し、ハンドルからデータ型のプロパティにアクセスすることで、それぞれのラインのデータ型を取得しようと考えた。
% 各ラインのハンドルを取得
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line');
※ ハンドル … simulink上のオブジェクトのID
しかし、ここで問題が。
ブロックのパラメータにアクセスするための共通プロパティ(下記)は存在するが、ライン用のプロパティが見当たらない。
https://jp.mathworks.com/help/simulink/slref/common-block-parameters.html
ただ、試してみると上記のページもブロック用プロパティと言いながらも、ラインも同様のプロパティを保持している場合があるようだ。
例えば下記のように、取得したラインのハンドルを引数として、各ラインの名前を取得することができる。
% 各ラインのハンドルを取得
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line');
lname = get_param(lhd, 'Name'); % 各ラインの名前を取得
データ型を取得できるプロパティを探すと、「CompiledPortDataTypes」なるプロパティの説明に「ブロック線図を更新した後の端子信号のデータ型」とある。
試しにやってみた。
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line');
test([],[],[],'compile'); % コンパイルを開始(「CompiledPortDataTypes」を使用するため)
bpara = zeros(1, length(lhd)); % 実行速度を上げるため、for文中の変数の配列サイズを予め指定
% 各ブロックの出力のデータ型を取得
for i = 1:size(lhd) % ライン数の分だけ処理を実行
bpara = get_param(lhd(i), 'CompiledPortDataTypes'); % コンパイル後の各ラインのパラメータを取得
end
結果は、エラーが出た。エラーメッセージは
「line に 'CompiledPortDataTypes' という名前のパラメーターがありません」
ラインのデータ型は、出力となるブロックの設定に依存するため、ライン自体がデータ型のプロパティを持っていなくても不自然ではない。
ラインにデータ型のプロパティがないのなら、もうブロックの入出力のプロパティにアクセスしてデータ型を取得するしかない。
その場合、「ブロックの入出力ラインから取得したデータ型」と「ブロックの入出力となっているラインのハンドル」を紐付けなくてはならない。
以下ページを見ると、ラインのハンドルを引数として、そのラインに接続されている
ブロックのハンドルを取得できるようだ。
https://jp.mathworks.com/matlabcentral/answers/94271-simulink-line
これにより、以下手順を踏めば、各ライン(=変数)のデータ型を取得できると考えた。
1.ラインのハンドルを取得
2.1で取得したラインのハンドルを引数とし、ラインに接続されている
ブロックのハンドルを取得
3.2で取得したブロックのハンドルを引数とし、ブロックの出力となる
ラインのデータ型を取得
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line'); % ラインのハンドルを取得
bhd = get_param(lhd, 'SrcBlockHandle'); % 各ラインのハンドルから各ブロックのハンドルを取得
lname = get_param(lhd, 'Name'); % 各ラインの名前を取得
bdtype = num2cell(zeros(1, length(lhd))); % 実行速度を上げるため、for文中の変数の配列サイズを予め指定
test([],[],[],'compile'); % コンパイルを開始(「CompiledPortDataTypes」を使用するため)
% 各ブロックの出力のデータ型を取得
for i = 1:size(lhd) % ライン数の分だけ処理を実行
if ~(strcmp(lname(i), '')) % 名前がない(空欄の)ラインは除外
bdtype(i) = get_param(cell2mat(bhd(i)), 'CompiledPortDataTypes'); % コンパイル後の各ラインのデータ型を取得
end
end
これでbdtypeにデータ型を取得できたはず。。と思いきやエラーが。メッセージは「struct から cell に変換できません。」
どうやらget_paramで取得したはずのデータ型が、構造体になっているようだ。
試しに対話モードでコマンドウィンドウにget_param(cell2mat(bhd(i)), 'CompiledPortDataTypes')を出力してみると
>> bdtype(1)
ans =
Inport: {'int32' 'double'}
Outport: {'int32'}
Enable: []
Trigger: []
State: []
LConn: []
RConn: []
Ifaction: []
Reset: []
どうやら、bdtypeに格納されたパラメータから更にInport/Outportのフィールドにアクセスすることで、データ型を取得できるようだ。
(下記ページにも書いてあった)
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line'); % 各ラインのハンドルを取得
bhd = get_param(lhd, 'SrcBlockHandle'); % 各ラインのハンドルから各ブロックのハンドルを取得
bdtype = num2cell(zeros(1, length(lhd))); % 実行速度を上げるため、for文中の変数の配列サイズを予め指定
test([],[],[],'compile'); % コンパイルを開始(「CompiledPortDataTypes」を使用するため)
% 各ブロックの出力のデータ型を取得
for i = 1:size(lhd) % ライン数の分だけ処理を実行
bpara = get_param(cell2mat(bhd(i)), 'CompiledPortDataTypes'); % コンパイル後の各ブロックのパラメータを取得
if ~(isempty(bpara.Outport)) %「Display」や「Out」など出力を持たないオブジェクトを除く
bdtype(i) = bpara.Outport; % 各ブロックの出力のデータ型を取得
end
end
これで各ラインのデータ型を取得することができた。
※ 因みにこれらの検証は前に実行した時の変数がワークスペース上に残っていて
エラーを引き起こすので、実行の都度"clear all"でワークスペース上の
変数をクリアするとうまくいく。
② 変数名を自動で変更する
次に、ライン(=変数)を任意の名前に自動で変更する方法を考える。
まずは、ラインの名前を取得する。
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line'); % 各ラインのハンドルを取得
lname = get_param(lhd, 'Name'); % 各ラインの名前を取得
次に、取得したラインの名前に適当な文字列を付記して、ラインの名前を
変更する。
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line'); % 各ラインのハンドルを取得
lname = get_param(lhd, 'Name'); % 各ラインの名前を取得
newlname = zeros(1, length(lhd)); % 実行速度を上げるため、for文中の変数の配列サイズを予め指定
for i = 1:size(lname) % ライン数の分だけ処理を実行
newlname = horzcat(lname(i,1), '_', 'datatype'); % ライン名と任意の文字列を結合
set_param(lhd(i,1), 'Name', newlname); % 各ラインの名前を上書き
end
しかし、エラーがでてしまった。
新しい変数名を格納したnewlnameを見てみると
newlname =
'(ハンドルで指定したラインの名前)' '_' 'datatype'
なんだこれは…。
どうやら取得したライン名はセル配列のようで、そのセル配列の要素をhorzcatで普通に連結しようとしても、要素の中身を連結せず、配列の要素として連結(というか追加)してしまうようだ。
セル配列 … 文字通りExcelのセルのようなイメージで、各セルに
任意の型のデータが格納できる配列のこと。
下記より、strjoinを使用すれば、文字ベクトルのセル配列を結合して単体の文字ベクトルにすることができるようだ。
strjoinを使用して、試してみる。
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line'); % 各ラインのハンドルを取得
lname = get_param(lhd, 'Name'); % 各ラインの名前を取得
newlname = zeros(1, length(lhd)); % 実行速度を上げるため、for文中の変数の配列サイズを予め指定
% 変数名の頭or末尾にデータ型を追加
for i = 1:size(lname) % ライン数の分だけ処理を実行
newlname = strjoin(horzcat(lname(i,1), '_', 'datatype')); % ライン名と任意の文字列を結合(セル配列)
if ~(strcmp(lname(i), '')) % 名前がない(空欄の)ラインは除外
set_param(lhd(i,1), 'Name', newlname); % 各ラインの名前を上書き
end
end
今度はエラーが出なかった。
再度、newlnameを見てみると
newlname =
(ハンドルで指定したラインの名前) _ datatype
アンダースコアの前後にスペースが挿入されてしまっている。strjoinでセル配列要素の文字列を連結すると、スペースが入ってしまうのだろうか。不要なので、スペースを削除する処理を入れる。
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line'); % 各ラインのハンドルを取得
lname = get_param(lhd, 'Name'); % 各ラインの名前を取得
newlname = zeros(1, length(lhd)); % 実行速度を上げるため、for文中の変数の配列サイズを予め指定
% 変数名の頭or末尾にデータ型を追加
for i = 1:size(lname) % ライン数の分だけ処理を実行
newlname = strjoin(horzcat(lname(i,1), '_', 'datatype')); % ライン名と任意の文字列を結合(セル配列)
% セル配列要素の文字列をstrjoin&horzcatで繋げると、間に勝手にスペースが入ってしまうため、削除
newlname(length(cell2mat(lname(i,1))) + 1) = [];
newlname(length(cell2mat(lname(i,1))) + 2) = [];
if ~(strcmp(lname(i), '')) % 名前がない(空欄の)ラインは除外
set_param(lhd(i,1), 'Name', newlname); % 各ラインの名前を上書き
end
end
これでようやくうまくいった。
③ ①と②を組み合わせ、変数のデータ型を変数名に自動で追加する
あとは①と②で作成したスクリプトを、変数名などに気を付けながら結合する。
(実際には、単純に結合するだけではなく細かいところを変更している)
また、データ型をライン名の頭につけるか、末尾につけるかを選択するためのダイアログ機能を追加した。
lhd = find_system('test', 'FindAll', 'on', 'Type', 'line'); % 各ラインのハンドルを取得
bhd = get_param(lhd, 'SrcBlockHandle'); % 各ラインのハンドルから各ブロックのハンドルを取得
lname = get_param(lhd, 'Name'); % 各ラインの名前を取得
bdtype = num2cell(zeros(1, length(lhd))); % 実行速度を上げるため、for文中の変数の配列サイズを予め指定
newlname = num2cell(zeros(1, length(lhd))); % 同上
addplace = questdlg('データ型は変数名の文頭と末尾、どちらに追加しますか?', 'データ型の追加場所', '文頭','末尾', 'cancel');
test([],[],[],'compile'); % コンパイルを開始(「CompiledPortDataTypes」を使用するため)
% 各ブロックの出力のデータ型を取得
for i = 1:size(lhd) % ライン数の分だけ処理を実行
bpara = get_param(cell2mat(bhd(i)), 'CompiledPortDataTypes'); % コンパイル後の各ブロックのパラメータを取得
if ~(isempty(bpara.Outport)) %「Display」や「Out」など出力を持たないオブジェクトを除く
bdtype(i) = bpara.Outport; % 各ブロックの出力のデータ型を取得
end
end
test([],[],[],'term'); % シミュレーションを停止
% 変数名の頭or末尾にデータ型を追加
for i = 1:size(lname) % ライン数の分だけ処理を実行
if strcmp(addplace, '末尾') % データ型を追加する場所が変数名の末尾だった場合
newlname = strjoin(horzcat(lname(i,1), '_', bdtype(i))); % ライン名と任意の文字列を結合(セル配列)
% セル配列要素の文字列をstrjoin&horzcatで繋げると、間に勝手にスペースが入ってしまうため、削除
sps = length(cell2mat(lname(i,1))) + 1;
else % データ型を追加する場所が変数名の頭だった場合
newlname = strjoin(horzcat(bdtype(i), '_', lname(i,1))); % ライン名と任意の文字列を結合(セル配列)
% セル配列要素の文字列をstrjoin&horzcatで繋げると、間に勝手にスペースが入ってしまうため、削除
sps = length(cell2mat(bdtype(i))) + 1;
end
if ~(strcmp(lname(i), '')) % 名前がない(空欄の)ラインは除外
newlname(sps) = [];
newlname(sps+1) = [];
set_param(lhd(i,1), 'Name', newlname); % 各ラインの名前を上書き
end
end
disp('終了しました。');
スクリプトを、以下のsimulinkモデル"test.slx"に対して実行した。
結果は、下記のようになった。
まずはライン名の頭に追加した場合:
次に、ラインの名の末尾に追加した場合:
どちらの場合も、データ型をうまく追加できた。
最後に
ここはこうしたほうがいいぞ!ここ間違っているぞ!等あれば、お手数ですがご指摘いただけますと大変喜びます。