はじめに
第1章のバックナンバーはこちら
Modelicaによるプラント設計。
それをFMUにして他のプラットフォーム(Python等)での再利用。
さらに制御器との様々な接続方法(ASAM XCP、MDF等)の事例を紹介していく話。
Modelica用ツールとしてはOpenModelicaを使用する。
OpenModelica
ASAM XCP
ASAM MDF
参考書籍等
Modelica関連の書籍はそこそこあるが、どうしても情報不足は否めない。
また、それを実際に利用する場合、CANやXCPなどの通信側の知識を必要とされることは少なくない。
導入編
- 恒例の太郎くんの悩み事からスタート。
- プラントモデルを作成する必要がある。
- それっぽいものを作るだけならば過去の記事を参照すればOK。
- 一次遅れ系。
- モーター伝達関数。
- それっぽいものを作るだけならば過去の記事を参照すればOK。
- 本シリーズはModelicaを使用した話に踏み込む予定。
- Modelica用のツールは雰囲気的にはSimulinkに似ていることが多い。
Modelica
- Modelicaの説明。
- オブジェクト指向のマルチドメイン・モデリング言語。
- 機械、電気、電子、油圧、熱、制御などの領域を横断して記述できる。
- Modelicaはプログラミング言語だが、一般的な言語とは性質が異なる。
- シミュレーション特化言語。
- モデル定義としてパラメータと方程式を内包。
- Modelicaのプログラミング言語として性質を知るためモデル定義確認。
- Massモデルを参照。
- ある質量とある長さをもった完全剛体を1Dとして両端に力を加えられるモデル。
- 一般的なプログラムと異なり代入式ではなく方程式を定義していく。
- シーケンシャル且つ代入になる書き方もあるが一般的なプログラムのそれとは異なる。
- Massモデルの中にあるextendsは継承を示すキーワード。
- PartialキーワードはModelの不完全性を示しており、継承以外の利用ができないよう制約を掛けている。
- OOPの抽象class的なもの。
- PartialRigidモデルは剛体モデル。
- 直線運動する剛体モデルを作る際はこのモデルを継承すると吉。
- Flangeの説明。
- 様々な領域で使われる用語ということもあり「円筒形の物体」程度の定義。
- ModelicaのFlange。
- connectorタイプでModel間の接続用インターフェース。
- flow接頭辞は運動の第3法則(作用・反作用)を実現するのに便利。
- 力(Force)の宣言で利用されてる。
Modelicaツール
- Modelicaを扱えるツールは様々。
- Amesim、Simplorer、Dymola、MapleSim、SimulationX、OpenModelica。
- 自動車業界ではMapleSim、SimulationXが多め。
- HILS、Simulinkの相性の都合。
- OpenModelicaはオープンソースなツール。
OpenModelica
- OpenModelicaの説明。
- Wikiepdia英語ページから引用。
- 自動車、水処理、発電所の領域で使われている。
- 自動車業界でも開発フェーズだと相互運用性都合でプロプライエタリ品を使うことが多い。
- 複数のツールで構成されている。
- コンパイラ、エディタ、インターフェス、プラグインなど。
- OpenModelica Compiler (OMC)はコンパイラ。
- C言語を生成する。
- インタプリタ用言語を生成してデバッグ動作を実現。
- OpenModelica Connection Editor (OMEdit)
- グラフィックエディタ。
- C++/Qtで作成されているためマルチプラットフォーム。
- OpenModelica Shell (OMShell)について説明。
- その名の通りシェル。
- MATLABのコマンドウィンドウに近い。
- OpenModelica Notebook (OMNotebook)の説明。
- コマンドの実行とその結果を含めてドキュメント化できる機能。
- Jupyter Notebookに近い。
- OpenModelica Python Interface (OMPython)
- Python自動化インターフェース
- OpenModelica Matlab Interface (OMMatlab)
- 上記のMATLAB版
- Modelica Development Tooling (MDT)
- Eclipseインターフェース
- OpenModelicaのダウンロード。
- Windows、Linux、Mac版がある。
- 32bit、64bit用に分かれている。
- OpenModelicaのインストール。
- 基本はウィザードに従って「次」へ進んでいくだけ。
- トータルで10Gbyteほどのサイズになるので、15Gbyteくらいの空き容量があった方が良い。
massモデル
- 使用するモデルはMassとconstantForce。
- 直線運動をふんわり知って置いた方が良い。
- 加速度、速度、距離、力、運動量、仕事、仕事率。
- OpenModelica Connection Editorで各モデルを配置&接続
- Modelica→Mechanics→Translationalに目的のモデルがある。
- 前回作成したモデルが何を示しているか確認。
- massを引っ張り合ってるモデル。
- グラフィックエディタだと分かりにくいがマイナス符号を付けないと逆向きの力にはならない。
- シミュレーション結果を事前に予測してみる。
- 運動方程式を使用する。
- 質量と力が分かっているので、加速度が求められる。
- 加速度から速度、速度から距離。
- シミュレーションするモデルと前回の予測を再掲。
- constantForce、massの組み合わせ。
- \(1[m/s^2]\)の加速度。
- シミュレーション開始方法。
- OpenModelica Connection Editor上部の矢印アイコンをクリックするだけ。
- シミュレーション結果確認。
- 予測通りの結果が得られた。
- 他のシミュレーション結果のパラメータを確認
- 加速度に加えて、速度と移動距離。
- OpenModelica Connection Editorのプロットの画面の変数ブラウザでチェックを入れるだけで確認可能。
- それぞれのパラメータの関係性を確認。
- 加速度を積分して速度。
- 速度を積分して移動距離。
- OpenModelicaはローコード、ノーコードの性質がある。
- しかし、コードの読み書きも出来ていた方が良い。
- massモデルのソースコードを確認。
- equationではconnectキーワードで接続と定義。
- annotationキーワードでグラフィカルな情報が追記されソースコード内で情報が完結している。
- Modelicaコードを弄ることでいろいろと効率化される可能性がある。
- 実際にparameterキーワードを使って変数を定義。そして、それをconstantForceに設定。
- 今のままでは動作は何も変わらない。
- ソースコード上でパラメータ調整をし易くなったくらいの効能しかない。
- 即値で調整するのでは労力に差はさなそう。
- 「Modelicaのソースコードに変数を設置」の効能はモデル編集ではなくシミュ―ション時。
- シミュレーションをするためには毎回モデル検査、Cコード生成、コンパイルが入る。
- 規模が大きくなれと結構待たされる。
- parameterキーワードで宣言した変数はコンパイル後にも修正可。
- コンパイルせずに再シミュレーション可能。
- Modelicaコードで変数追加後のOpenModelica Connecter Editor上の見え方確認。
- まずは普通にシミュレーション。
- 変数ブラウザで変数を書き換えたあとに再シミュレーション。
- モデルチェック、コンパイル無しで即シミュレーション結果が得られた。
- ちょっとしたテクニックをしってるだけで効率化可能。
- Modelicaコードに追加したパラメータが増えると管理が大変。
- 注釈が欲しい。
- 他のモデルもModelicaコードで書かれたものであるが変数ブラウザで注釈が確認できる。
- つまり、注釈が入れられるはず。
- 変数の隣にダブルクォーテーションでくくった文字を入れれば注釈。
- 変数ブラウザでも確認可能。
- ソースブロックによっては様々な信号を入力できる。
- ソースブロックは信号を生成してくれるブロック。
- 信号を生成するソースブロックと物理量に変換するソースブロックがある。
- 今回はtrapezoidが信号生成、forceが物理量変換。
- Modelicaライブラリは大量にあるがライブラリブラウザで検索ができるようになってる。
- trapezoidブロックとForceブロックを配置。
- ForceブロックはModelica→Mechanics→Translational→Sourcesにある。
- trapezoidブロックはModelica→Blocks→Sources→Trapezoidにある。
- 本来はtrapezoidを修正する必要があるが今回は不要。
- trapezoidの設定もせずにおもむろにシミュレーション。
- 矩形波的な出力になった。
- 変数ブラウザでtrapezoidの設定を編集。
- 台形波的な出力に変化。
- このように、変数ブラウザからパラメータ変更が可能なソースブロックはそこそこある。
- これらを知っているといろいろとサボれて楽できる。
DCモータ
- ModelicaのDCモータモデルのサンプルの位置をライブラリブラウザで確認。
- DCモータモデルをとりあえずシミュレーション。
- 制御電圧、制御電流、角速度の結果を確認。
- 今回は電圧をランプ関数で制御したシンプルなもの。
- ランプ関数は0を起点に徐々に上がっていく関数。
- ModelicaのDCモータモデルをちょっと掘り下げ。
- 以下が絡んでくる。
- 電気/電子領域。
- 古典力学領域。
- 幾何学(材料力学)領域。
- 以下が絡んでくる。
- 物理モデリングは伝達経路、伝達関数、微分方程式解決、シミュレーションの4つの工程がある。
- Modelicaは伝達関数、微分方程式解決をサボれるツール。
- DCモータモデルのModelicaコードを確認。
- 半分くらいはannotationなのでそれほど規模は大きくない。
- 見るべきポイントを列挙。
- 先頭のparameter部。
- 中間のモデル宣言部。
- 真ん中DcPermanentMagnetData。
- これが今回のサボりポイントの目玉となる。
- Modelicaコードのparameter部を確認。
- parameterに関しては以前やった。
- しかし、今回はReal型ではない。
- 厳密には、Real型に単位の定義を付加したもの。
- 電圧だったら”V”。
- トルクなら”N.m”。
- SI単位系で存在するものはModelica.SIunitsの中にすでに定義済み。
- Modelicaコード モデル宣言部を確認した。
- 以下が存在しており、OpenModelica Connection Editor上にもある。
- DC_PermanentMagnet、Ramp、SignalVoltage、Inertia、TorqueStep。
- 以下が存在しており、OpenModelica Connection Editor上にもある。
- DcPermanentMagnetDataが特殊な位置づけ。
- DcPermanentMagnetDataをOpenModelica Connection Editorで確認。
- UI上で様々なパラメータを設定可能。
- さらにそのパラメータをDC_PermanentMagnetに渡すことでモデル初期化している。
- 初期化するモデルが一個だとあまり意味がないかもだが、同特性モデルが複数あるとサボれる。
FMI(Fumction Mockup Interface)
- Modelicaモデルを外部から利用する手段は一応ある。
- OpenModelicaからFMIをもったFMUを出力可能。
- FMIは物理モデルをモジュール化したものの標準インターフェース。
- MODELISARプロジェクトで策定。
- その後、Modelica Association Project(MAP)で管理。
- FMI仕様の公開場所確認。
- FMI-Standardにて公開されている。
- FMI/FMUはMATLAB/Simulinkで言うところのS-Functionみたいなもの。
- コンセプトとしてはほぼ一緒。
- FMI/FMU側は標準仕様と言うことでもうマルチプラットフォームを意識したものとなっている。
- FMU/FMIの存在価値について確認。
- S-Functionと同等とすると存在価値が薄くなる。
- 自動車業界なりの狙いはある。
- サプライヤから納入される部品と同等の振る舞いするモデルモジュールをもらい、完成車メーカ側で統合する。
- FMU/FMIはSimulink、LabViewをプラットフォームとして入出力を繋げられる。
- 知ってる範囲でFMU/FMIに対応しているツールを調べてきた。
- 自動車業界限定且つメジャー所だと5社ほど。
対応Versionやアドオン追加などのの制約はある。
対応ツールは多いので結構使えそう。- ただし、Vector社製品のようにCANoeは対応しているが、CANapeは対応していない。などはある。
- 自動車業界限定且つメジャー所だと5社ほど。
- 各社ツールでFMU/FMIの利用で追加費用は発生しない。
- 非競争領域と考えて広めることを重要視している可能性が高い。
- 2016年くらいから流行り始めている。
- SDKのリリースが2014年なのが理由かも。
- FMU/FMIのVersionは1.0と2.0がある。
- ただし、互換性はない点に注意が必要。
- FMU/FMIのシミュレーション方式は2種類ある。
- Model Exchange(通称ME)。
- 外部にSolver。
- Co-Simulation(通称CS)。
- 内部にSolver。
- Model Exchange(通称ME)。
- SolverはODE Solverのこと。
- 常微分方程式を解決する機能。
- オイラー法、ホルン法などが有名
- 常微分方程式を解決する機能。
- FMU/FMIのシミュレーション方式とSolverの位置づけを図解した。
- MEは近似精度を調整したい場合に有利。
- ECUの粗い制度を再現したい。
- プラントの演算負荷を下げてシミュレーションを高速化したい。
- CSは内部にSolverがあり、繋ぐだけで動くので設定が簡単。
- CSのみのサポートしかしていないツールもある。
- FMU/FMIはあまり一般的に知られているものではないので利用方法の情報が皆無。
- よって、仕様に踏み込まないと利用方法も見えない。
- 仕様書を読み込むのも大変なのでFMU自体の中身を見て行った方が理解としては楽そう。
- 実はFMUは特定のファイルとフォルダ構成をzip圧縮したもの。
- つまり解凍して中身を参照できる。
- 実はFMUは特定のファイルとフォルダ構成をzip圧縮したもの。
- FMUとzipとして解凍してみた。
- 何個かのフォルダとxmlファイルがあった。
- binariesにプラットフォーム別のライブラリが格納。
- resourcesフォルダに依存関係があるファイル群を格納。
- modelDescription.xmlにinput。output、内部パラメータが定義されている。
- FMU/FMIのプラットフォーム上での位置づけを再確認。
- FMUのユーザ視点に於いての位置づけを確認。
- modelDescription.xmlとMotor.DLLの位置づけなど。
- これを元に仕様の性質から予測される利用手順を列挙。
- やはりmodelDescription.xmlの中身が気になるので、簡単に説明予定。
- modelDescription.xmlの中身を確認。
- name、valueReference、description、variability、causality、Real unitが存在。
- valueReferenceについて仕様確認。
- 変数ハンドル用で数値の衝突は禁止。
- ただし、エイリアスはその限りではない。
- modelDescription.xmlのvariabilityの仕様について。
- constant(定数)、fixed(固定値)、tunable(調整可能値)、discrete(離散)、continuous(連続)。
- fixedはシミュレーション前であれば変更可能。
- tunableは変更時にODE演算のイベントが発生。
- constant(定数)、fixed(固定値)、tunable(調整可能値)、discrete(離散)、continuous(連続)。
- modelDescription.xmlのcausalityの内容を確認。
- parameter、calculatedParameter、input、output、local、independentの6種が存在。
- parameterはModelicaのparameter相当。
- calculatedParameterは初期値関連。
- FMU/FMIを読めそうなツールを確認。
- 以前、太郎くんが調査した情報をベースに確認。
- CANoeがすぐ使えそうだった。
- 以前、太郎くんが調査した情報をベースに確認。
- CANoeでFMU/FMIの変数確認。
- 問題無く確認できた。
- FMU/FMIの変数をCANoeのシステム変数に割り付ける形。
- よって、CAPLからでも簡単に制御できそう。
FMILibrary
- C言語からFMU/FMIを制御するライブラリはFMILibrary
- Windows版のFMILibraryはあるにはあるが…。
- win32版のみ。
- 64bit版は自分でビルドする必要がある。
- OpenModelica64bit版のFMUは恐らく64bitビルド。
- FMILibrary 64bit版を用意することに。
- FMILibraryのビルド環境準備。
- Visual Studio 2017 express。
- cmake。
- コンパイラ非依存なビルド自動化ツール。
- FMILibraryのソースコード。
- zipアーカイブかGithubで入手できる。
- 今回はFMILibrary-2.0.3-src.zipを使用。
- FMILibraryのビルド手順を確認。
- 開発者コマンドプロンプト起動、cmakeでビルド、ライブラリインストール。
- cmakeへのオプションがちょっとややこしい。
- FMILIB_INSTALL_PREFIXでインストール先。
- FMILIB_FMI_PLATFORMでプラットフォーム。
- -Gでビルドする環境指定。
- cmakeの前回のコマンドを使ってコンフィグレーション開始。
- 問題無く完了。
- そのままcmakeでビルド開始。
- ビルドの途中でエラーが発生。
- xmlparse.cの中でBLOCKとsという構造体とメンバ変数の絡みでエラーが起きたようだが・・・。
- とりあえず、頑張って調べてみる。
- FMILibraryのビルドエラーの原因究明。
- stddef.hにoffsetofが定義されていなかった。
- 厳密にはC++向けには定義されていたが、C言語向けには定義されていなかった。
- Visual Studio自体がC++を想定した環境であるためC言語のケアが薄いためなのかもしれない。
- 再びFMILibraryのビルドにチャレンジ。
- 今回は無事ビルドが通った。
- ライブラリが生成されたのも確認。
- その後、指定したインストール先にライブラリ及びヘッダファイルが配置された。
- 今回は無事ビルドが通った。
- FMILibraryビルド時にサンプルプロジェクトが生成されているので、今後はこれをベースに話を進める予定。
- FMILibraryのサンプルプロジェクトを確認。
- 大量にある。
- 今回はfmi2_import_cs_testを使用。
- すでにビルド済みのものが存在。
- fmi2_import_cs_testは引数を要求するタイプの実行プログラム。
- よって、ただ起動しただけでは何もしてくれない。
- 引数については次回説明予定。
- fmi2_import_cs_testの引数について確認。
- fmu_fileとtemporary_dir。
- fmu_fileはその名の通りFMUを指定。
- 今回はFMI2.0且つCSのFMUであるBouncingBall2_cs.fmuが該当。
- temporary_dirはFMUを展開するためのワーク用。
- fmi2_import_cs_testの起動時パラメータ確認。
- FMUとテンポラリディレクトリのPathを設定。
- 実行と結果を取得。
- 「log level = VERBOSE」ってのはFMILibraryの内部のデバッグログ。
- Ball height、Ball speedとその次に続いている数値がシミュレーション上重要。
- fmi2_import_cs_testの実行結果のうちシミュレーション部分のところだけ抜き出し。
- Ball heightとBall speedのパラメータがある。
- 本シミュレーションはボールを投げたあとのバウンドに伴うボールの高さと縦方向の速度を示したもの。
- Excelでグラフ表示してみたところ確かにそんな感じ。
- シミュレーション時間とシミュレーションステップはFMUの外側の制御の仕方次第で確定する。
- FMU処理自体はfmi2_import_do_stepという関数の中で指定時間分実施する動き。
- サンプルプロジェクトfmi2_import_cs_testの場合はhstepとtendを調整すればOK。
- 時間は「秒」である点に注意。
- fmi2_import_cs_testのシミュレーションステップとシミュレーション時間を変えてみた。
- 上記のシミュレーションを実行。
- 精度を細かくしたのと、シミュレーション時間を延ばしたことでデータ量は増えた。
- グラフで確認。
- 前回の100msサンプリングと同じ特性で精度、時間が変わっていた。
- FMUのシミュレーションパラメータを変更することができる。
- ただし、イニシャルモード中。
- イニシャルモードを指定するAPIが存在。
- fmi2_import_set_realというAPIでパラメータ変更が可能。
- 型に応じたAPIになっており、他にinteger、boolean、string用のAPIが存在する。
- fmi2_import_set_realのAPI仕様確認。
- FMI statusの定義確認。
- モデル記述オブジェクトはfmi2_import_parse_xmlで取得できるfmi制御用のハンドル。
- FMUを展開した後に出てくるのmodelDescription.xmlを指定する必要がある。
- modelDescription.xmlでインターフェース定義を確認。
- fmi2_import_set_realに渡すvalueReferenceはmodelDescription.xmlに定義されてるvalueReferenceを渡せばOK。
- これを踏まえた上で最もシンプルと思われるコードサンプル提示。
- 修正コードができたので確認。
- 内容の詳細説明。
- 読み出すvalueReference群の定義。
- fmi2_import_get_realで一気に読み出し。
- ボールの初期の高さだけ変更。
- fmi2_import_set_realで一気に書き戻し。
- 数値解析ツール由来のベクトルで一気に制御する方式になっている。
- 修正済みfmi2_import_cs_testを実行。
- パッと見変化がわからないので以前の実行結果と比較。
- 明らかに初期のボールの高さは変わっている。
- グラフにして確認。
- 初期のボールの高さが変わっているので、跳ね方も変わる。
- パッと見変化がわからないので以前の実行結果と比較。
- このように初期パラメータもFMILibrary経由で変更可能。
- FMUはパラメータ名とvalueReferenceの紐づけが出来た方が運用し易い。
- FMILibraryはmodelDescription.xmlの内部情報を構造的に抱えている。
- よって、APIで各種情報を取得可能。
- 「modelDescription.xmlの内容を列挙」までの流れを確認。
- 手順は多いが、流れはシンプル。
- fmi2_import_parse_xmlについては以前やったのでスルー。
- fmi2_import_get_variable_listはmodelDescription.xmlの情報取得の起点。
- ソートルールを切り替えられる。
- 型/valueReferenceでソートがちょっと特殊。
- ベクトル的アクセスで使えそう。
- 型/valueReferenceでソートがちょっと特殊。
- ソートルールを切り替えられる。
- 「fmi2_import_get_variable_list_sizeによる変数リスト数の取得」の仕様確認。
- やってることはそのままでリストの要素数を取得。
- 「fmi2_import_get_variableによる変数オブジェクトの取得」の仕様確認。
- 変数オブジェクトは変数関連の情報にアクセスするハンドルのようなもの。
- valueReferenceの取得方法確認。
- 変数オブジェクトを渡すと取得できる。
- 変数名の取得。
- これも変数オブジェクトを渡すと取得できる。
- その他のdescription、variability、causality、initial。
- これも一緒で変数オブジェクトを渡して取得。
- fmi2_import_get_variability、fmi2_import_get_causality、fmi2_import_get_initialと併用して使う便利APIが存在。
- 上記関数戻り値のenumに準じて文字列を返してくれるAPI。
- 中身はswich分で実現してるだけ。
- 次回は実際にソースコード作成。
- modelDescription.xml内部変数列挙の処理手順確認。
- コード追加箇所説明。
- fmi2_import_enter_initialization_modeとfmi2_import_exit_initialization_modeの間。
- コード提示。
- 前回までに説明したAPI(文字列変換含む)を全部使用した。
- 「modelDescription.xml内の変数情報を列挙」を実施。
- 問題無く動作。
- modelDescription.xmlに記載されてる変数がすべて列挙されていることを確認。
- ソートルールは「XMLファイルに記載されているオリジナルの順序」
- こちらも想定通りの動作になっていることを確認。
- 変数リストのソートルールが複数あることを思い出した。
- よって、他のソートルールも試した。
- それぞれ想定通りの動作になっていることを確認。
- ソートの変更は現実的にはあまり出番は無さそう。
- HILS、RAPIDコントローラで使うかもしれないが、それらもそこそこの性能があるのでやはり使わない?
- 変数リストを取得する以外のvalueReference取得方法がある。
- パラメータ名文字列を指定してvalueReference取得したいが、直接それができるAPIは無い。
- パラメータ名文字列を元に変数オブジェクトを取得するAPIはある。
- 変数オブジェクトが取得できれば、そこからvalueReferenceは取得できる
- 「パラメータ名文字列から変数オブジェクト取得」のAPI確認。
- fmi2_import_get_variable_by_nameというAPI。
- パラメータ名文字列を渡せば、変数オブジェクトが返ってくる。
- 修正箇所は恒例のイニシャルモード中。
- 今後のことも考え複数のvalueReferenceを取得する予定。
- パラメータ名文字列を元にFMU内パラメータ変更の実験コード提示。
- 変数オブジェクトを取得し、そこからvalueReference取得。
- valueReferenceが分かれば、FMU内パラメータは好き勝手できる。
- これにより今後、他のFMUを使う場合になっても比較的楽にパラメータアクセスができそう。
- パラメータ名文字列からFMUの制御までを動作確認。
- valueReferenceの取得OK。
- その後のvalueReferenceを使用したパラメータ変更も当然OK。
- 今後の予定としては、OpenModelicaがexportしたFMUを使っていろいろやっていこうと画策している。
- ぶっちゃけると手探り状態。
FMIライブラリ(DCモータ)
- 「OpenModelicaで作ったFMUをFMILibraryで制御する」のプランを提示。
- モデルは以前扱ったDCモータモデルとする。
- Rampをそのまま電圧としてDCモータに印加するモデル。
- ただし、そのまま使わずPID制御を追加してみる。
- オープンループ制御からクローズループ制御のモデルに変更。
- 以前使ったDCモータモデルにPID制御器を付けた。
- PID制御器はModelicaライブラリに最初から存在。
- 実際にはLimPID。
- パラメータはKp、Ki、KdではなくKp、Ti、Tdな点に注意。
- とりあえず、クローズループ(PID)制御のDCモータモデルが出来た。(つもり)
- クローズループ制御にしたDCモータモデルの動作確認を実施。
- ちゃんと動いてるっぽい。
- Outputブロック設置
- 各種信号にエイリアスを振るために設置。
- 目標値をtarget。
- 指令電圧値をvoltage。
- 電流センサで取れる電流をcurrent。
- 角速度センサで取れる角速度をspeed。
- 各種信号にエイリアスを振るために設置。
- OpenModelicaからFMUをexportするための設定確認が必要。
- FMIオプション内を確認&修正。
- バージョン:2.0。
- タイプ:Co-Simulation。
- Platforms:Static。
- Linux環境だとDynamicでもOKだろうが、Windows環境だとStatic推奨。
- FMIオプション内を確認&修正。
- OpenModelicaから無事FMUをexport。
- FMU内部のmodelDescription.xmlを参照。
- Outputブロック名のパラメータの存在を確認。
- 上記のvalueReferenceと同値のパラメータも確認。
- モデル上、同一の信号線上のパラメータが該当。
- 利用するのはOutputブロック側。
- OpenModelicaからexportしたFMUをFMILibraryで読み込んでみた。
- 無事読み込み成功。
- 変数リストによる列挙もできた。
- 必要なパラメータの情報は問題無く取得出来ている。
- シミュレーションをするために若干の改造が必要。
- シミュレーションループにvalueReferenceが渡るように修正。
- シミュレーションループにvalueReferenceを渡すためのfmi2_import_cs_testのソースコード修正を確認。
- 流れは以下。
- 欲しいパラメータ名文字列列挙。
- 変数オブジェクト取得。
- valueReference取得。
- valueReferenceをシミュレーションループで利用。
- 改造版fmi2_import_cs_testの実行してみた。
- 問題無く動作している様子。(目標値、制御電圧、モータ電流、モータ角速度)
- 試しにグラフで表示。
- 期待通りの波形が得られた。
- これに伴い、OpenModelicaからexportしたFMUもFMILibraryで制御可能と言える。
- FMU内部の固定値パラメータの変更ができるか?
- 以前やった方法で実現可能。
- これのソースコード改造実施。
- Rampの開始タイミング、\(0→1\)の期間を設定できるように改造。
- “ramp.duration”が\(0.8[s]→1.5[s]\)。
- “ramp.startTime”が\(0.2[s]→0.3[s]\)。
- Rampの開始タイミング、\(0→1\)の期間を設定できるように改造。
- 固定値パラメータの変更の挙動確認。
- Rampの挙動を変えた。
- 目標値(target)の挙動を変えたため、それに合わせて全体の挙動が変化。
- 狙った挙動になっている。
- パラメータになっていれば、おおよそ変更可能。
- 変えられないのはアルゴリズムそのものや信号線の繋ぎぐらい。
- FMILibraryについての感想。
- 標準仕様であるが故の恩恵であるが、他ツールで出力したものを再利用できるのは助かる。
- CAN、A/D、D/Aなどと繋げるとさらに強力な使い方ができるかも?
- 今後はPythonベースの環境を構築してみる。
- ググっても情報少ないので手探り状態の失敗覚悟状態で進める。
PyFMI
- PyhthonからFMUを制御するPyFMIの紹介。
- 内部でFMILibraryを使用している。
- JModelicaの一部。
- JModelicaはmodelon AB社のModelicaPlatform。
- 2019年にClosed Sourceに移行。
- FMILibraryも開発元はmodelon AB社。
- JModelicaはmodelon AB社のModelicaPlatform。
- PyFMIのインスト―ルについてあれこれ。
- 依存関係がヤバイ。
- FMILibraryの64bitが必要。
- Assimuloが依存したsundials、GLIMDAのsolverの64bit品が必要。
- condaだと依存関係を一撃で解決してくれる。
- python-canなどはconda管理になっていないなど万能では無い。
- PyFMIの動作確認方法を列挙。
- 実験用のFMUを作って、それをPyFMIで制御しつつmatplotlibで波形表示する。って流れ。
- DCモータモデル改造。
- 改造と言ってもInputブロックを追加した程度。
- InputブロックもOutputブロックと同様にエイリアスは生成される。
- このエイリアスにアクセスする予定。
- FMUをPythonで使用する上で必要ライブラリのimport。
- PyFMIのload_fmu。
- numpy。
- matplotlib。
- load_fmuの戻りのオブジェクトはFMU次第。
- FMUModelCS1。
- FMUModelCS2。
- FMUModelME1。
- FMUModelME2。
- 時間軸作った。
- とりあえず、0秒から2秒の等差数列で作った。
- Ramp作った。
- 等差数列で斜めにプロットした後にmax,minでサチらせた。
- 時間軸とRamp入力と統合&縦方向に。
- vstackとtransposeを使用。
- 入力オブジェクト作った。
- voltageに入力行列を紐づけた。
- シミュレーション実施。
- 開始時刻、終了時刻、入力オブジェクトを渡すことで実施可能。
- シミュレーション結果取得。
- simulate関数の戻り値が連想配列になっている。
- voltage = res[‘voltage’]のような指定方法。
- シミュレーション結果のグラフ表示。
- matplotlibでプロットするのみ。
- PyFMIでFMU制御するPythonコードを開示。
- 割とあっさり実現。
- Pythonなのでmatpotlibでそのままグラフ表示。
- FMILibraryと比べるとvalueReferenceに振り回されることが無い点がとても良い。
- PyFMIによるFMU制御の有用性がなんとなく見えてきた。
マルチFMU
- 「完璧に把握したかもしれん」は幻。
- ダニング=クルーガー効果。
- FMUの本体の目的は「完成車メーカがサプライヤからの提供されたFMUを統合する」
- よって、複数のFMUを作成。
- DCモータモデルを分解して複数のFMUを作ってみる方針。
- よって、複数のFMUを作成。
- とりあえず上記をやってみて課題が出たら、それを次のネタにする。
- 元にするDCモータモデルは使いまわし。
- Ramp、PID制御器、DCモータの構成が一番部品が多い。
- モデルの分解もRamp、PID制御器、DCモータの単位でやってく予定。
- 念のため、OpenModelicaで現状の動作結果を取得しておく。
- PyFMIで統合したときの成功/失敗の判定用。
- 次はモデル分解。
- DCモータモデル分解とFMU exportを実施。
- 前回までのやり方で簡単にできるはず。
- (よって詳細説明は端折った)
- PyFMIからのFMU呼び出しをする際のおおまかな構成を提示。
- 各FMUの信号はPython側で接続するイメージ。
- マルチFMU制御ならではの処理。
- (それだけでは解決しない話もあったり)
- 各FMUの信号はPython側で接続するイメージ。
- PyFMIでマルチFMU制御する際にある程度の前提知識が必要になる。
- FMUロード。
- 以前はload_fmuを使ったが、今回はFMUModelCS2を使用。
- 引数が増えている。
- PATHの指定がファイル名のPATHに分けることが可能。
- DLLロード有無の引数がある。
- つまりロードしない使い方が・・・。
- Masterが土台になるが必要な情報がある。
- モデルセット。
- FMUをロードしたモデルをリスト化。
- モデル間接続セット。
- モデルの出力、入力を1セットとしたリスト。
- モデルセット。
- モデルセットとモデル間接続セットをMasterに渡せばシミュレーションをする準備はおおよそ完了。
- まだ、若干の調整はある。
- Masterにオプション設定が可能。
- 今回はstep_sizeを調整。
- デフォルト値が0,01秒なので0,001秒に変更。
- 今回はstep_sizeを調整。
- step_size以外にも大量のオプションがある。
- 補間の仕方、並列処理の有無、結果出力、ログ出力などなど。
- どういうものがあるかだけ把握し、必要になった際に再度確認すればOK。
- マルチFMU制御用のコード提示。
- 前半は前回まで説明した内容。
- ロード、モデルセット、モデル間接続セット、Master定義
- 後半はシミュレーション結果取得と波形表示。
- 前半は前回まで説明した内容。
- FMILibraryの時とは異なり、結果取得と波形表示が楽なのは本当に有難い。
- 結果は連想配列で取得。
- 波形表示はmatplotlib使用。
- マルチFMU制御用のコード実行結果を提示。
- 事前に取っていたOpenModelicaのシミュレーション結果も一緒に提示。
- 今回の方法だと目的のHILS環境としては課題がある。
- シミュレーションが1関数の中で閉じてるのでリアルタイムに外部とのやり取りができない。
- ググっても情報出て来ないのでいろいろ模索するしかない。
ダミーFMU
- HILSに於いてのシミュレーション時間と実時間の合わせこみ方法は大きく2種類。
- HILS自体が時間保証。
- モデルの一部で時間の辻褄合わせ。
- 今回はモデルの一部で辻褄合わせの方針。
- PyFMIでダミーFMUを定義できそう。
- 「Undocumented specification」なので当たって砕けろ方式。
- PyFMIでダミーFMUを定義できそう。
めだよ。
- ダミーFMU定義に向けての方針を提示。
- FMUのロードの仕方を再確認。
- _connect_dllという引数があった。
- _connect_dllをFalseにするとFMUがロードされずインターフェースだけが定義される。
- インターフェースを利用してでPython側からFMU出力制御可。
- しかし、時間の制御はできない。
- FMUロード関数を一旦整理。
- Dummy_*という謎関数が各FMUタイプ別に存在。
- Dummy_FMUModelCS2を題材として掘り下げ。
- FMUModelCS2を継承している。
- オーバーライドしているメソッド多数。
- 重要なのはdo_step。
- cpdef定義なので外部から上書きできないのを回避している。
- do_stepをオーバーライドしている理由の説明。
- Cythonによる静的関数でそのままではPython側からの上書きができない。
- do_stepの重要性の説明。
- masterモジュールからシミュレーションステップ毎に呼ばれるメソッドだから。
- これを自由に書き換えられれば時間制御ができる。
- 実時間に追いつくまで待たせる。
- Dummy_FMUModelCS2を使用する実験構成を提示。
- 以前作ったマルチFMUの構成をベースにちょい修正の方針。
- ついでにPythonからstep毎の出力も制御してみる。
- 各FMUのロードのコード。
- Dummy_FMUModelCS2で既存のFMUを指定しておくとインターフェース仕様だけは取り込める。
- do_stepの実装方針を決めた。
- Ramp UpとRamp Down。
- 出力信号のソースコード。
- シミュレーション時間を見ながら出力信号を決定する方式。
- 時刻同期のソースコード。
- 単にタイマーを使ってシミュレーション時間が実時間に追いつくのを待つ。
- do_stepメソッドの上書きはそのまま作成関数で上書きすればOK。
- ダミーFMU実験用ソースコードは以前のマルチFMUの時の物を流用。
- do_step周りの追加が主な修正部分。
- ソースコード開示。
- FMUModelCS2の一部をDummy_FMUModelCS2。
- do_stepをdo_dummyで上書き。
- 時刻同期ができてるかを確認できるよう一部printを入れている。
- ダミーFMU実験の動作確認。
- タイミングによっては若干ズレるがシミュレーション時間と実時間の同期はOK。
- シミュレーション波形確認。
- Ramp Up、Ramp Downの台形状の波形が出ており、期待通りの指令値になっている。
- 上記によりダミーFMUによる実験がし易くなる。
- 簡単なアルゴリズムであればPythonで実施
リアルタイム描画
- 前回までやってたシミュレーション時間と実時間の同期には課題がある。
- シミュレーション時間の方が長いと破綻。
- Python側でグラフ表示等すると破綻し易い。
- シミュレーション時間の方が長いと破綻。
- よって、別案。
- 前回まではシミュレーション時間に実時間を追いつかせる。
- 対して、新方式は実時間に対してシミュレーション時間を追いつかせる。
- 実験環境は「シミュレーションしながらリアルタイムで波形表示」。
- 上記以外にも以下を組み込む。
- スライダー等で入力を手動操作。
- sin波、のこぎり波などを入力。
- リアルタイム表示の一時停止。
- tkinterを使用する予定。
- Tk GUIツールキットをPythonから呼ぶライブラリ。
- 実験構成はGUIから決めていく。
- GUIでおおよそのインターフェースが確定しそうだから。
- GUIの概要図を描いた。
- 各種チェックボックス、Scale、plot画面。
- 上記の機能、目的を定義。
- 処理負荷が分かるようにする。
- pauseに関してはリアルタイム描画を抑制した際の負荷を見る為に設置。
- tkinterはPython標準ライブラリのためインストール済み。
- 動作確認。
- 「python -m tkinter」でウィンドウが出てくればとりあえずOK。
- メインウィンドウをrootとして定義し、そこに各ウィジットを生成&紐づけしていく。
- matplotlib関連もtkinter連携用モジュールが存在する。
- tkinterのimportはそのまんま。
- 「import tkinter」
- VisualStudioのリソースエディタのようなものは無い。
- メインウィンドウ生成方法解説。
- titleも指定可能。
- メインウィンドウの大きさと位置指定。
- 文字列で以下のような感じで指定。
- ‘960×540+960+0’
- 文字列で以下のような感じで指定。
- matplotlibをtkinterに埋め込む際には以下のモジュールが必要。
- matplotlib.pyplot。
- プロット用。
- FigureCanvasTkAgg。
- プロット画面埋め込み用。
- NavigationToolbar2Tk。
- メニューバー。
- matplotlib.pyplot。
- 埋め込む前に図(figure)を用意しておく。
- FigureCanvasTkAggを使用してtkinterへmatplotlibを埋め込み。
- pack(side = tkinter.RIGHT)でメインウィンドウの右端に接するよう配置。
- NavigationToolbar2Tkを使用してメニューバー配置。
- place(x=0, y=h/2-40)を使用して座標指定で配置。
- チェックボックスの配置。
- チェック状態を確認するための変数を用意し、ウィジット生成時に渡しておく。
- この変数を見ればチェック状態が分かる。
- チェック状態をPython側から変更することも可能。
- placeで座標指定して配置。
- チェック状態を確認するための変数を用意し、ウィジット生成時に渡しておく。
- 同様の処理を必要なチェックボックス分実施。
- GUI上でも設置されていることを確認。
- 最後のウィジットとしてScaleを配置。
- いままでのウィジットの中では最もパラメータが多い。
- 縦置き、横置きの指定ができる。
- commandという引数で値変更時のコールバックを設定できる。
- to、from_で値の範囲を指定できるが大きさが逆転しても良い。
- 0値の配置を逆にしたい場合は意図的に逆転させる。(公式もOK情報あり)
- Scaleの配置確認。
- 問題無く配置されているのを確認。
- Scaleの挙動確認。
- 値をprintしているのでそれを確認。
- Scaleの値が出力されているの挙動もOK。
- 値をprintしているのでそれを確認。
- tkinterはmainloopに入ると、戻ってこない。
- よって、タイマ処理を使ってうFMU関連、描画関連をうまくハンドリングする必要あり。
- まずはtkinterのタイマ処理から確認。
- タイマ処理に描画処理、FMU処理を組み込むためまずはタイマ。
- タイマ処理はafterメソッドに指定時間とタイマハンドラを渡すことで実施できる。
- しかし、一回コールバックするとそれで終了。
- よって、周期的に処理したい場合はタイマハンドラ内で再度afterメソッドを呼び出す必要あり。
- タイマ処理にmatplotlibの描画更新処理を入れてみる。
- 現時刻をベースに過去8秒分の時間軸を生成し、それをsin関数に入れてsin波生成。
- relimメソッドを使うと縦軸が自動調整される。
- pauseのチェックボックスを設置していたので、チェック状態を確認して更新するかしないかの判定に使う。
- いままでごちゃごちゃと追加してきたコードを再確認。
- tkinterによるリアルタイム描画の動作確認。
- sin波が流れるように描画されていることを確認。
- pauseで一時停止もできることを確認。
- FMU関連処理はまだ未実装だが、tkinterによるリアルタイム描画はできそう。
- FMU関連処理追加がそこそこいろいろあるので情報整理。
- 基本はいままでのFMU処理ではある。
- 追加でdequeが必要そう。
- シミュレーションデータ全てを保持すると、記憶する配列が膨大になる可能性が高い。
- よって、描画に必要なデータだけを格納するためにdequeを使用。
- dequeの使用方法を追々説明予定。
- FMU関連処理前準備を一気に説明。
- いままでやってきたものなので細かい説明は省略。
- deque関連の初期化。
- dequeはcollectionsライブラリの一部。
- maxlenで最大要素数を指定できる。
- 指定なしの場合は無限に入れられる。
- 実際には物理的な限界はあるだろうが・・・。
- 指定なしの場合は無限に入れられる。
- 必要なタイマハンドラは2つ
- FMU処理用とmatplotlib描画用。
- 描画用側の負荷が高いので、こっちは長めの周期で実施。
- FMU処理用タイマハンドラ内で時間関連の演算が入るため、タイマハンドラ外で下準備が必要。
- Masterの初回呼び出し時のオプションと2回目以降で変える必要がある。
- initializeオプション。
- FMU_handlerことFMUシミュレーション用処理のコードを提示。
- 外部の変数を使用するためにglobal定義している変数あり。
- 実時間の経過を元にFMUシミュレーション時間を決定している。
- 実時間にシミュレーション時間を追いつかせる方式。
- ただし、開始と終了のstepが重複したり欠落したりするので微小時間の調整が必要。
- plot_handlerことmatplotlibリアルタイム更新処理のコード提示。
- 以前のリアルタイム描画実験との差は以下。
- deque使用。
- plot時にnp.arrayに変換しているが無くてもOK。
- CPU負荷描画チェックボックス確認。
- 以前のリアルタイム描画実験との差は以下。
- dequeのように要素数が分からない場合は-1による末尾指定が便利。
- 現状のコードで動作確認をしてみた。
- FMUの波形表示OK。
- Scaleで指令値変更OK。
- まだ以下の課題が残っている。
- ソースコードがちょっとヤッツケ。
- 負荷関連の評価が出来ていない。
- 実際の負荷状態。
- 描画処理の影響有無の評価。
- なんとなく現状でも負荷の影響が見え隠れはしている。
- 現状のソースコード確認。
- 暗黙的なものも含めてグローバル変数が点在している。
- GUIイベントとかタイマハンドラで関数が分離しているので仕方ない面もある。
- 暗黙的なものも含めてグローバル変数が点在している。
- 折角なのでクラスとしてまとめてみようと試みる。
- 基本的なロジックは出来ているのでそれほど修正時間はかからない見込み。
- 実験用Pythonコードのクラス化。
- 基本、関数横断変数をメンバ変数にした。
- つまり、メンバ変数になっているものが暗黙的なグローバル変数だった。
- 「実はグローバル変数だったのかー!」ってのはあるある。
- 前回と同様の動きはしていそうなので、このコードをベースに実験を継続する。
- 特に負荷関連が残っている。
- 修正コードでとりあえず動作させてみた。
- 問題無く動作した。
- 負荷確認実施。
- カクついているが人間の操作のせいの可能性もある。
- sin波の自動入力で確認。
- やはりカクついている。
- よって、操作の問題ではない。
- やはりカクついている。
- CPU負荷を見て、さらにmatpotlibの波形拡大で詳細確認。
- FMU処理以外の処理負荷大きそう。
- 前回の負荷を安定化する方法を検討。
- 描画に処理時間を持っていかれているっぽからpauseで描画だけ停止していた。
- 案の定、負荷安定化。
- 描画に処理時間を持っていかれているっぽからpauseで描画だけ停止していた。
- 上記より、フェーズを分けた利用法が考えられる。
- デバッグフェーズは波形を見ながら。
- ある程度デバッグが完了したら波形無しの高精度状態で検証する。
- 描画を止める以外で負荷低減方法を検討してみた。
- スレッドの利用案が出たが以下の理由で不可。
- GIL(Global Interpreter Lock)仕様の影響で期待するスレッドのコンテキストスイッチにならない。
- よって、負荷はほぼ減らない。
- 下手したら増える可能性もある。
- というわけで対策としては見送り。
- スレッド以外で処理分散を検討。
- マルチプロセスでやれば分散できるかもしれない。
- マルチプロセスを実現するにはプロセス間通信が必要。
- Queue、Pipe、共有メモリとあるが、今回はPipeを使用してみる。
- ただし、通常の文字列送信方式ではなく、バイナリ方式。
- 文字列方式は型を維持できて便利だがオーバーヘッドが大きい。
- Queue、Pipe、共有メモリとあるが、今回はPipeを使用してみる。
- Pythonでマルチプロセスをする上での手順確認。
- Pipeを使ったプロセス間通信が面倒そう。
- 追加コードについては今回は触り程度。
- 要importのライブラリ。
- multiprocessingのProcessとPipe。
- Pipeの生成。
- 送受信の2つが取得できる。
- 引数自体で双方向、片方向が切り替わる
- 要importのライブラリ。
- Pythonでサブプロセスの生成と開始について説明。
- Process関数を呼ぶだけ。
- 引数にサブプロセス開始時に呼び出す関数とそれに対する引数を渡せる。
- 今回はPipeを渡しておく。
- Pipeによる送信について説明。
- send_bytesを呼ぶだけ。
- dequeのままでは渡せないのでdouble型のarrayにする。
- Pipeの受信処理のコードを提示。
- 受信自体は一関数だが、byte配列からdoubleに直すのに手間がかかってる感じ。
- Pipeの受信確認。
- 受信関数自体は受信が来るまで戻ってこないので、無条件では呼びたくない。
- よって、poll関数で受信有無を確認した後に実際の受信処理をする。
- 次回から実際のコードと動作確認。
- マルチプロセス化した実験コードを提示。
- 基本的な流れは一緒だが、Pipe関連のコードが増えた。
- deque毎にsend_bytesで送信。
- Scaleの入力やsin波生成指示を描画のメインプロセスからFMUのサブプロセスに送ってる。
- sin波をサブプロセス側にしないと、結局描画負荷でsin波が崩れる。
- マルチプロセス化実験コードを実際に動作させてみた。
- 一応負荷は下がったが、それでも50ms間の負荷がかかるポイントが散見される。
- 結果としてはPipeのプロセス間通信のオーバーヘッド。
- 他のプロセス間通信でもこのオーバーヘッドはそれほど変わらない。
- 今後の実験は通常のシングルプロセス版のコードをベースとする。
CAN連携
- さらなるHILSっぽさを求めて外部入力を検討。
- ECUのインターフェースを想定すると分かり易い。
- インターフェースは様々ではあるが、とりあえずCANであれば融通が利きそう。
- CANからADC、DAC、PWMへの変更はそれほど大変ではない。
- よって、今後はCANをインターフェースの前提として話を進める。
- CANをHILSのインターフェースにするには本来では専用のインターフェース装置が必要。
- しかし、今回はVector社のXL Driver Library付属のVirtual CAN Busを使用。(無償)
- 実験構成は指令値をCANで指定。
- 前回までのtkinterに対して追加する。
- 入力パターンが増えるイメージ。
- CAN経由で渡したい情報として指令値を仮定義。
- Ramp Up、Ramp Down、Stepなどの組み合わせ
- Python-CANの復習。
- インストールの話。
- pip使えばOK。
- importの話。
- canをimportすればOK。
- インストールの話。
- 以降はバス初期化、送受信などの話が続く予定。
- Python-CAN関連は基本的には過去記事参照でOK。
- バス初期化時に使用するデバイスやボーレートを設定。
- 回線が複数ある場合は、回線毎にバス初期化をする。
- そして戻り値のbusをもって通信先を識別する。
- CANのメッセージはcan.Messageで作成。
- CANID、標準or拡張フレームの指定ができる。
- Python-CANで指令値送信のコードを提示。
- CAN関連の前回までの復習の話のまんま。
- 利便性を上げるためにコマンドライン引数を取り込む機能追加。
- 送信周期変更と繰り返し処理切替を追加。
- 動作確認はBusMasterでモニタすることで実施予定。
- 実際のCANだと対抗機が必要だがVirtual CAN Busでは不要。
- Python-CANで送信確認。
- 送信は成功している。
- BusMasterで確認。
- 送信状況はモニタできている。
- 100ms周期より若干上振れしている。
- Sleepで周期を作っているため、どうしても上振れ方向になる。
- よって、今回は気にせず指令器としてはOKとする。
- Sleepで周期を作っているため、どうしても上振れ方向になる。
- CAN受信用にモード切替のUIが必要。
- チェックボックスで実施予定。
- CAN受信のimport、バス初期化はCAN送信側といっしょ。
- 実際の想定受信コードも記載。
- スレッドを使うことも可能だが、タイムアウトを0秒にするポーリング型を想定。
- とりあえず、負荷に影響を与えない&シンプルな実装にする。
- スレッドを使うことも可能だが、タイムアウトを0秒にするポーリング型を想定。
- GUI(tkinter)にチェックボックス追加。
- 「can rcv」というチェックボックス。
- チェック時に受信有効になる想定。
- CAN受信にチェックボックス判定追加。
- if文を追加したのみ。
- バス初期化、チェックボックス生成は__init__メソッドに追加予定。
- CAN受信はFMU処理のタイマハンドラ内に追加予定。
- CAN連携の受信側(FMU処理、グラフ描画)のコードを開示。
- __init__にバズ初期化とチェックボックス配置。
- FMU_handlerでCAN受信。
- ただし、タイムアウト0秒のポーリング方式。
- 一応起動することだけは確認済み。
- あとは指令器側のCAN送信との連携を試すのみ。
- 「指令器」と「FMU処理&グラフ描画処理」の結合実験実施。
- 共に問題無く動作。
- CANの送信周期が100msというのもあって、波形もキレイ。
- 上記の状況を動画で確認。
- 「HILSもどき」というおおよそ目的を達成した気もするが、他のアプローチも試したいのでこのシリーズはまだまだ続く。
XCP Basic
- 「HILSもどき」を利用した試したい事はXCPを利用することらしい。
- XCPはBypass関連の記事で一年以上前にやったことはある。
- BypassはECUの一部の機能をXCPを使って疑似化するもの。
- 今回やろうとしていることはECUの外側をXCPを使って疑似化するもの。
- 詳細は次回説明予定。
- ECUの外側の疑似化にXCPを使うってのはインターフェースをXCPにするってことだった。
- 以前やったBypassとはデータの流れは逆向きになっている。
- XCPで直接RAMの読み書きを行うことで物理的なインターフェースの制約を無視できる。
- 逆に物理的な制約に紐づいた検証はできないという欠点はある。
- XCPのシミュレーション環境が欲しい。
- 候補としてはVector社で無償で公開されているXCP basicがある。
- XCP basicはEULAとしてはVector社の責が無いことを前提に自由にして良い。
- サポートが必要な場合は有償。
- XCP ProfessionalやMICROSAR XCPというプロプライエタリ品もある。
- プロプライエタリ品との差別化のためXCP Basicには機能制限がある。
- XcpBasic.cに機能制限について記載されている。
- 特に大きいのがSTIM使用不可。
- STIM使用不可に関してはDOWNLOADで代替可能。
- ただし、スループットはどうしても落ちる。
- とりあえずの落としどころとしてやむを得ず。
- ただし、スループットはどうしても落ちる。
- XCP Basic動作までの方針を決めた。
- 数点、要否が確定できない作業があるが、実際に確認してみてから決める。
- 実験構成を提示。
- CANの制御はPython-CANを使用。
- 単純な送受信であればお手軽。
- 恒例のVirtual CAN Busを使用。
- 物理的な制約を一旦無視して論理的な正しさを求める場合はこれが一番楽。
- CANの制御はPython-CANを使用。
- XCP Basicを入手方法。
- Vector社のサイトから入手。
- インストール。
- 最初にEULAが出てくる。
- インストール先にはXCP Basicのソースコードが展開されるので好きなところでOK。
- ビルド環境は恒例のVisual Studio 2017 express。
- Virtual CAN Busのインストール。
- XCP Basicのフォルダ構成を確認。
- ドキュメント、EULA、Sample、XCP Basicハード非依存コードなどが配置されている。
- Sampleに各物理層のポーティングコードが入っている。
- XCPsimがPCシミュレーションを想定。
- Ethernet(TCP/UDP)かCAN(VN16xx)を物理層と想定。
- XCPsimがPCシミュレーションを想定。
- XCP BasicのVisual Studio 2015から2017へのアップグレードは特に何もなかった。
- ビルドを実施。
- warningは出ているが、sscanfをsscanf_sにする話。
- 起動オプションがある。
- XCPsim -hでヘルプを参照可能。
- デフォルトはCANでオプションでEthernetへの切り替えもできる。
- とりあえずXCP Basicのテスト用にメモリアクセスに絡まない以下のコマンドを送ってみる。
- CONNECT。
- GET_STATUS。
- SYNC。
- コマンドの送信はPythonの対話モードかJupyter Notebookで実施。
- まずはCONNECTコマンドを送信。
- 無事送信&受信できた。
- 他のコマンドも同様に試してみる。
- XCP BasicにGET_STATUSコマンドを送ってみた。
- 問題無く応答あり。
- 次にSYNCコマンドを送ってみた。
- 応答があったがエラー応答?
- 実は「コマンドプロセッサの同期。」を示す0x00というエラーコードで通常応答になる。
- 応答があったがエラー応答?
- メモリ非依存のコマンド確認は取れた状態。
- PCシミュレーションの場合、XCP Basicでメモリアクセス系コマンドを実施には課題がある。
- 各変数のアドレスが起動毎に変わる可能性あり。
- よって、以下のどちらかが必要。(後者を採用)
- 変数のアドレスをファイル出力して、XCPマスタからアクセス毎にファイル参照。
- 配列定義して配列先頭を0アドレスとする。
- xcp_cfg.hの修正は以下のdefine定義を追加するだけ。
- XCP_ENABLE_CALIBRATION。
- XCP_ENABLE_MEM_ACCESS_BY_APPL。
- XCP_ENABLE_CALIBRATIONはDOWNLOADとDOWNLOAD_MAXが有効になる。
- SHORT_DOWNLOAD等は使用不可。
- xcp_cfg.hについかしたXCP_ENABLE_MEM_ACCESS_BY_APPLのdefine定義について説明。
- XCP経由のメモリアクセスが直接アクセスからフック関数を経由する状態に切り替わる。
- ecu.cの修正。
- アクセス用の配列を定義。
- メモリアクセス用フック関数の定義と実装。
- XCP Basicがサポートしているメモリアクセス系コマンドを列挙。
- SET_MTA。
- DOWNLOAD。
- DOWNLOAD_MAX。
- SHORT_UPLOAD。
- UPLOAD。
- Python-CAN初期化とCONNECTコマンドを発行してからSET_MTAコマンド発行。
正常応答あり。
- DOWNLOADコマンドを実施。
- 正常応答あり。
- 実際に書き込まれたかはUPLOAD系コマンド実験時に確認予定。
- DOWNLOAD_MAXコマンド実施
- MTAは読み書き時にそのサイズ分参照アドレスが後方にズレる。
- C言語のポインタ的な仕様。
- MAX_CTO(今回の場合は8)-1分のサイズが無条件に書き込みサイズになる。
- MTAは読み書き時にそのサイズ分参照アドレスが後方にズレる。
- UPLOAD系コマンドの実験開始。
- UPLOADコマンドが基本形のコマンドでその派生形としてSHORT_UPLOADがあるが、SHORT_UPLOADのとある挙動を確認するためにSHORT_UPLOADから実施。
- SHORT_UPLOADでDOWNLOADで書き込んだ値が読めた。
- よって、両コマンド共に動作OKとなる。
- なんとSHORT_UPLOAD時にMTAも更新される仕様になっていた。
- よって、続けて読み出す場合はSET_MTA無しでUPLOADを実施すればOK。
- この仕様を利用してSHROT_UPLOAD→UPLOAD×n回とすると、効率的なメモリダンプが実現できる。
- よって、続けて読み出す場合はSET_MTA無しでUPLOADを実施すればOK。
- メモリアクセス系コマンドはこれで動作確認OKとなる。
- メモリアクセス系コマンド以外のメモリアクセスとしてDAQ(Data AcQuisition)がある。
- DAQは過去記事で数回にわたって説明しているのでそちらを参照。
- 必要なコマンドをそこそこあるので、一個ずつ試していく。
- 尚、XCP BasicはDynamic DAQが実装されている。
- Static DAQは未実装。
- 尚、XCP BasicはDynamic DAQが実装されている。
- XCPパケット送受信処理を関数化してみた。
- CAN送信は従来通り。
- CAN受信でいろいろ判定を追加
- タイムアウト追加。
- 1秒タイムアウト。
- CANID判定追加
- PID判定追加。
- 0xFE(エラー)か0xFF(正常)以外はレスポンスとして扱わない。
- レスポンスのタイミングでDAQパケットが来ても無視できる。
- タイムアウト追加。
- FREE_DAQコマンドを送信。
- ECU内部のDAQ list構造を真っ新にするコマンド。
- “とりあえず”一番最初に投げらえる。
- ALLOC_DAQコマンドを送信。
- 生成するDAQ listの数を指定するコマンド。
- XCP Basicの場合、DAQ list毎に6byte消費。
- ALLOC_ODTを送信してみた。
- ODT1個あたり4byteの管理領域のリソース割り当てがされる。
- 試しに5個ずつODTを生成してみた。
- 20個のODT生成時にエラー。
- エラー理由はメモリ不足。
- 1ODTあたり8byteの送信バッファも必要なのでそれも含めてメモリ不足。
- この点含めてリソース管理が必要。
- 20個のODT生成時にエラー。
- ALLOC_ODT_ENTRYは物理層の都合やODTのフォーマットによる制限がある。
- CANの場合だとデータフィールド8byteが制限。
- タイムスタンプ等が入ることで計測データを格納する範囲が少なくなる。
- ALLOC_ODT_ENTRYを複数回投げてみた。
- リソースが枯渇するまで生成は可能。
- DAQ listの構造的な準備はできたので、実際にODT_ENTRYを更新していく。
- 更新するODT_ENTRYを参照するにはSET_DAQ_PTRを使用する。
- 実際にODT_ENTRYを更新するにはWRITE_DAQを使用する。
- WRITE_DAQ実行後は参照するODT_ENTRYは自動的に一個後ろになる。
- SET_DAQ_LIST_MODEコマンドでDAQの計測データ送信ポリシーを設定できる。
- TIMESTAMP有無、使用するEvent channel、分周比など。
- Event channelと実際の周期はECU側の実装依存。
- 今回は1:10ms、2:100ms、3:1msとなっている。
- DAQの開始方法は大きく2パターン。
- DAQ list毎に個別開始。
- 開始可能状態にして複数のDAQ listを同時開始。
- 今回は後者で実施。
- START_STOP_DAQ_LISTコマンドで個別開始/停止と開始可能状態の設定できる。
- これでDAQの開始条件は揃った状態となる。
- START_STOP_SYNCHコマンドを投げて見た。
- 並行してcan.loggerでCAN回線モニタを実施。
- DAQパケットの計測ができた。
- タイムスタンプ有効設定をしていたため先頭の方にタイムスタンプが埋まっている。
- タイムスタンプの後ろから計測データが続く。
- 今回は0値が取得されている。
- 計測RAM値の変更方法はSET_MTAとDOWNLAOD。
- SET_MTAとDOWNLOADの電文確認。
- 折角なので連続で投げて見る。
- ただ、同じ値だと変化が分からない無いのでちょっと書き込みデータを変える。
- さらにDAQパケット送出前に書き換えてしまう可能性もあるのでsleep関数を挟む。
- 折角なので連続で投げて見る。
- 実際にSET_MTAとDOWNLOADを使ってRAM値書き換えを実施。
- RAM値書き換え前にDAQを起動しておき、RAM値書き換えとDAQパケットの状況をCAN回線モニタして確認。
- RAM値書き換えに合わせてDAQパケットの計測データが変化。
- おおよそXCP Basicの挙動はOKだが、まだちょっと問題がある。
- XCP Basic PCシミュレーションの問題点は送信周期。
- 前回のCAN回線ログのDAQパケットだけを抽出し、それのタイムスタンプと元に現状の送信周期を算出。
- 10[ms]周期で送信されるべきところが15[ms]になっていた。
- 一応、原因は分かってはいるので、そこらへんを次回確認予定。
- 10[ms]周期で送信されるべきところが15[ms]になっていた。
- DAQパケット送信周期の精度が悪い理由を確認。
- イベントチャンネルが15[ms]になってる。
- Sleep(10)で10[ms]周期を作ろうとしているが、実際は15[ms]になってる。
- この部分はPC依存な面はある。
- Sleep(10)で10[ms]周期を作ろうとしているが、実際は15[ms]になってる。
- イベントチャンネルが15[ms]になってる。
- 上記により小手先の回収ではどうにもならなそう。
- 一応対策も考えてるので次回確認予定。
- DAQパケット送信周期精度改善の方針を決めた。
- 時間精度を引き上げるためマルチメディアタイマを使用。
- 1[ms]精度の過去実績あり。
- 時間精度を引き上げるためマルチメディアタイマを使用。
- Sleep(10)を挟んだWhileループをマルチメディアタイマによる1[ms]コールバックに変更。
- これに伴い10[ms]周期だけでなく、イベントチャンネル3も1[ms]保証ができる。
- 再度DAQパケットを確認。
- XCPの一連のコマンド含めてCAN回線ログとして取得。
- DAQパケットだけ抽出し、タイムスタンプを確認。
- 狙い通り10[ms]周期の送信周期になっていた。
- ソースコード等はGithubに上げておいた。
- Python側はJupyterNotebook形式。
PyXCP
- Python-CANにXCPの上位プロトコルを載せたPythonLibraryが存在。
- その名はそのまんまでPyXCP。
- PyXCPのインストールはpip使えばOK。
- pip install pyxcp。
- PyXCPだけだと疎通確認も取れないからXCP BasicによるPCシミュレーション環境はかなり重要。
- PyXCPを利用する上で必要なimportを列挙。
- 設定ファイルがJSONファイルを想定しており、Python内の文字列をファイル
- 認識されるためにio.StringIOもimport。
- StringIOに引き渡すJSONを提示。
- 比較的自明なパラメータが多いが一部分かり難いものもあるので次回説明。
- JSON文字列を再度確認。
- それぞれのパラメータについて一気に説明。
- 使用できるCANインターフェースデバイスは多い。
- 有名どころは網羅されている。
- トランポート層にSxIを指定できる。
- SPI、SCI(UART)のことでPyXCPに於いてはCOMポートになる。
- と言ってもそれほど利用シーンは無い。
- コンフィグレーションパラメータ(JSON文字列)の作成と読み込みのコードを提示。
- readConfigurationにStringIOを渡しているが、拡張子がjsonのファイル名を設定しておく必要あり。
- コンフィグレーションを元にXCPマスターの生成。
- コンフィグレーションを渡すことでXCPマスターが生成される。
- 早速PyXCPでCONNECTとGET_STATUSのコマンドを投げて見た。
- 共に問題無く動作
- 該当メソッドの戻り値でレスポンスの詳細が取得できる。
- かなり見やすい構造になっている。
- GET_STATUSはレスポンス内容の性質上、DAQ起動中やCAL_PAGEのROM書き戻し完了待ちなどの確認使用されることが多い。
- SYNCHコマンドを投げた。
- 想定通りERR_CMD_SYNCHを受け取れた。
- GET_COMM_MODE_INFOコマンドを投げた。
- 今回使用しないがinterleavedMode、masterBlockModeの有無や関連パラメータが返ってくる。
- 上記以外に適用しているXCP仕様Versionも取得可能。
- メモリアクセス系コマンドということでSET_MTA、DOWNLOAD、UPLOADを実施。
- DOWNLOADの結果がUPLOADするまで分からないということと、一個ずつ試すがめんどいので一気に流した。
- UPLOAD→DOWNLOAD→UPLOADの流れでverifyすることで読み書きが正常に行われていることが確認できた。
- DAQ listの構築を一気に実施した。
- FREE_DAQ、ALLOC_DAQ、ALLOC_ODT、ALLOC_ODT_ENTRYを一気に実施。
- 以前XCP BasicでやったDAQ list構築に合わせた構成にしてある。
- PyXCPのメソッド単位で隠蔽されていることもあり、かなり楽ちん。
- WRITE_DAQの前にODT_ENTRYのデータ管理の便利な方法について説明。
- namedtupleと使う。
- tupleは異なるデータを一組に管理する手法や構造。
- namedtupleはtupleの各要素に明示的に名前を付けられるようにしたもの。
- 管理のし易さを見やすさの両方が得られる。
- ODT_ENTRYをnamedtupleを使ってデータ管理してみた。
- 今回は一個しかないのでほぼ効能はないが、ODT_ENTRYが増えてきた際には大きな効能が見込めそう。
- 実際にSET_DAQ_PTR、WRITE_DAQ実施。
- 問題なく動作。
- 毎回SET_DAQ_PTRを投げるようなコードになってるが、とりあえずOK。
- DAQ起動すべく以下のコマンドに相当するメソッドを実行。
- SET_DAQ_LIST_MODE。
- START_STOP_DAQ_LIST。
- START_STOP_SYNCH。
- 狙い通りDAQ起動はした。
- CAN回線上でDAQパケットが流れていることが確認できた。
- しかし、PyXCP上でDAQパケットを確認する術が不明。
- DAQパケットの取得方法の概要説明。
- transport層に相当するクラスでdaqQueueが定義されている。
- このdaqQueueに自動的にDAQパケットがキューイングされる仕組み。
- 上記仕組みはJSONコンフィグレーションのCAN_USE_DEFAULT_LISTENER
- trueでないと使えない点に注意。
- PyXCPでDAQパケットを受信しながらSET_MTA、DOWNLOADのコマンド送信を行う実験。
- XCP Basic側は問題なくできることは分かってるのでPyXCP側メインの実験。
- 一応PyXCP内コード的には大丈夫そう。
- 実験コードはDAPパケット受信ループ内で0.05秒周期でSET_MTA、DOWNLOAD発行。
- DAQパケットを受信しながらSET_MTA、DOWNLOADのコマンド送信してみた。
- 一応動いた。
- が、DAQパケットの吸い上げのリアルタイム性が若干悪い。
- 20~30[ms]程度の遅れがある。
- Pythonでやる以上、やむを得ない問題ではあるが、もう少し手が無いか考える必要はありそう。
- XCP BasicとPyXCPは微妙だった?ってことはない。
- 計測やちょっとしたキャリブレーションであれば問題無く使える。
- HILSもどきと相性が悪いだけ。
- オーバーヘッドを検討。
- 15msはSET_MTAとDOWNLOADの2回のコマンド発行分。
- ここを何とかすればもう少しマシになりそう。
- SET_MTAとDOWNLOAD以外のRAM書き換え方法はSTIM
- しかし、XCP BasicはSTIMは未対応。
- XCP Basicに対してSTIM拡張をするかXCP Basic以外のXCPスレーブIPを探すか。
- とりあえず後者の線でやってみる。
- 見つからなかったら諦めてSET_MTAとDOWNLOADで頑張る方向で。
AUTOSAR-XCP
- XCP Basicとは別のXCPスレーブIPを見つけてきた。
- その名はAUTOSAR-XCP。
- 以前やったCanTp、DCMと同じくAUTOSAR-BSWのXCP仕様に準拠したもの。
- ライセンスがLGPL。
- よって、条件によってはコード開示が必要になり、商用利用としては難しい場合がある。
- LGPLはライブラリ化すればライセンス汚染をある程度食い止められる。
- しかし、完全に食い止められるわけではなくどうしてもリバースエンジニアリングを許容する条件は付く。
- これは動的リンクであっても変わらない。
- AUTOSARのXCP実装、STIMの実装を学べるという利点があるので、使ってみる方針で行く。
- AUTOSAR-XCPの機能範囲について説明。
- 純粋にXCP BWSを実現しているのみ。
- よって、下位BSWのCanIfを実装する必要がある。
- CanIfは以前、診断通信関連BSWであるCanTpのシミュレーションをしたときに作成してる。
- 微調整はあるかもしれないが、これを使いまわす予定。
- AUTOSAR-XCP PCシミュレーションに向けてのロードマップを提示。
- 恒例の実験構成、環境、実験内容を提示。
- 実験構成の提示。
- こちらもおなじみのVirtual CAN Busを使用した構成。
- AUTOSAR-XCPのPCシミュレーション環境も恒例のVisual Studio 2017 express。
- AUTOSAR-XCPをGithubからCloneしてきてソースコードを確認。
- とりあえず、全部使う。
- AUTOSAR仕様として必要そうなソースとヘッダを洗い出し。
- TOPPERSプロジェクトのA-ComStackとATK2から頂戴する。
- 足りないかもしれないが、まずはこれでビルドを通してみる。
- AUTOSAR-XCPをビルドする上で排他同期等でWindowsAPIが必要となる。
- しかしwindows.hが他の定義を競合することがある。
- よってstub.cでラップ関数を定義して避けている。
- マルチメディアタイマで1msコールバックを生成。
- ECUの実装が1ms周期を起点に処理されることが多く、それを模擬してる。
- CanIfとかAUTOSAR-XCPから呼び出される関数群の辻褄合わせをmain.cで実施。
- 送信関数と送信完了割り込み、受信割り込み、エラー割り込み。
- 排他制御関数。
- エラー通知関数。
- main関数で初期化処理関連を実施。
- これでやっとビルドが通った状態となる。
- AUTOSAR-XCPのコンフィグレーションはREADME.txtに説明あり。
- Xcp_Cfg.hとXcp_Cfg.cを作成する必要あり。
- Xcp_Cfg.hを作成。
- README.txtに記載が無かった項目として以下がある。
- XCP_PROTOCOL。
- XCP_STANDALONE。
- BYTE_ORDER。
- README.txtに記載が無かった項目として以下がある。
- Xcp_Cfg.cの作成したコードを確認。
- DAQ、STIMで使うイベントチャンネルの定義がほとんど。
- Online Calibrationで使用されるSegmentについても記載するパラメータがある。
- アクセスするメモリ空間を疑似的に切り替える概念を実現。
- 今回は使用しないので、そういう概念があるのを認識程度に留める。
- AUTOSAR-XCPの動作確認開始。
- XCPコマンドはPyXCPを使って実施。
- XCP Basic、PyXCPで散々やったところなので巻き気味で推進。
- 一気にCONNECT、GET_STATUS、SYNCH、GET_COMM_MODE_INFOを実施。
- 当然ながら問題無く動作。
- CAN回線ログも一緒に確認。
- AUTOSAR-XCPのメモリアクセス系コマンドの動作確認を実施。
- 今回も一気にSET_MTA、DOWNLOAD、UPLOADを確認。
- 最後UPLAODでverifyして想定される値が書き込まれていたのでOK。
- 念のためCAN回線ログも確認。
- 次回からDAQ関連に突入。
- AUTOSAR-XCPのDAQ設定系コマンドの動作確認。
- FREE_DAQ、ALLOC_DAQ、ALLOC_ODT、ALLOC_ODT_ENTRYを一気に確認。
- ODT_ENTRYへの書き込み実施。
- SET_DAQ_PTR、WRITE_DAQを確認。
- この段階のDAQ listはDAQかSTIMかの利用方法の指定はしていない。
- AUTOSAR-XCPのDAQ listモード設定コマンドの動作確認実施。
- SET_DAQ_LIST_MODEを実施。
- STIM側はTimeStampFieldは不要なので削除指定。
- さらにRAM値への更新速度を上げるため1ms周期。
- START_STOP_DAQ_LISTを実施。
- SET_DAQ_LIST_MODEを実施。
- ついにAUTOSAR-XCPでSTIMを実現。
- SET_MTA,DOWNLOADで実現していたコードを改修。
- 若干、遅れがあるように見えたが、CAN回線ログと比較した感じだと、全に正しい結果となっている。
- 思った以上の成果と言える。
- フクさんが一点気になることがあるらしい。
- これは次回説明予定。
- AUTOSAR-XCPのSTIMの気になる点がある。
- STIMパケットに対してRESパケットが発行されている点。
- 仕様書上は、RESパケット応答に関する振る舞いは明記されていなかった。
- 恐らくデファクトスタンダード的な仕様が紛れている。
- 上記は何が正しいというのは恐らくない。
- こういうものがあるということに気を付ける。
仮想ECU/PID制御
- 仮想ECUを作るべくAUTOSAR-XCPに制御器を組み込む必要がある。
- PID制御器は過去記事で作ったものを流用予定。
- 制御周期はとりあえず、10msあたりにしておく。
- PID制御器の動作確認はXCP経由で入出力を制御することで実施する予定。
- PID制御器自体のソースコードは以前、SILS等で確認済み。
- PID制御器のコードを用意した。
- 過去に使用したものを今回向けに修正したもの。
- ecu_t10ms_job関数で10ms周期で処理。
- ecu_init関数で初期化処理。
- 変数の初期化を実施。
- コンパイルが通ることのみ確認済み。
- 動作確認にPyXCPを使用するが具体手なコードは次回以降に説明。
- PID制御器の動作確認方法としてPyXCPを使う。
- HILSもどきから使用することを想定してクラス化。
- クラス化は目的次第で隠蔽する度合いが変化する。
- つまり何をサボりたいかが動機になり易い。
- エントリーポイントとして呼び出すかライブラリで呼び出すかで振る舞いを変えると便利。
- 単体テストをする場合はエントリーポイント。
- PID制御器動作確認用Pythonコードを開示。
- コンフィグレーション、XCPマスター生成、DAQ起動、計測、書き換えを隠蔽している。
- エントリポイントとして起動した際は簡単なXCP通信をしてXCPスレーブの挙動が見れる。
- ライブラリとして呼び出すことも可能。
- こんな感じのちょっとずづの積み上げが割と重要。
- 仮想ECU側のPID制御器の入出力する信号の種類を確認。
- 実際にXCPのDAQ、STIMを実施して制御状態を計測。
- 想定通りの挙動になっていた。
- CAN回線モニタを確認。
- DAQとSTIMの目標値、実値のレイアウトを合わせていたので、縦に慣れべることで挙動が推測できる。
- HILSもどきの前にDAQパケットを取得して波形表示する機能が必要。
- 仮想ECU側の内部変数を表示することが目的。
- DAQパケットはECU側の計測値なので、内部変数と解釈しても差し支えない。
- 上記機能はDAQリスナと命名。
- DAQリスナの実験構成を図示した。
- 横からCANを覗き見るだけのシンプルな機能。
- DAQリスナの実験構成を図示した。
- DAQリスナーのPythonコードを開示。
- 基本的にはPython-canで受信。
- DAQパケットならば取り込む。
- 上記を元にmatplotlibで描画。
- 波形表示はリアルタイムではあるが負荷低減のため0.2秒周期。
- ここらへんの描画負荷の事情はHILSもどきの時と一緒
- DAQリスナーの動作確認実施。
- 問題無く動作した。
- やや表示範囲が有ってないが次に作る「HILSもどき」向けの設定になってる。
- 「HILSもどき」改め「仮想HILS」と命名変更。
- 仮想HILS作成に向けてのロードマップ確認。
- 下位レイヤの動作が先に保証済みになっていると計画が立てやすい。
仮想HILS
- 再びPyFMIを使うということでPyFMI関連の復習。
- 過去記事のリンクを貼っておいた。
- PIDのFMUをDummy_FMUModel化実施。
- FMUModelCS2でロードしていたものをDummy_FMUModelCS2でロードするに変えるだけ。
- 当然、これに伴う変更もあるが。
- FMUModelCS2でロードしていたものをDummy_FMUModelCS2でロードするに変えるだけ。
- DummyFMU化したPID制御の辻褄合わせを実施。
- Dummy_FMUModelCS2のdo_stepメソッドをオーバーライドすればOK。
- do_stepをdp_pidとして実装。
- do_pid関数内部でPID.fmuの変わりに出力を決定する仕掛け。
- このように入出力の辻褄合わせをすればOK。
- XCP関連の処理を追加。
- 基本xcp_canクラスを呼び出すだけ。
- DAQ受信とSTIM送信はFMU処理が実装してあるFMU_handler内で呼び出す。
- DAQ受信はFMU処理直前。
- STIM送信はFMU処理直後。
- これにより、仮想ECU側の変数と仮想HILS側のシグナルの同期が取れる。
- ついに仮想HILSのPythonコード完成。
- 基本的には前回までの修正内容を盛り込んだだけ。
- 指令値取得用のCANバス初期化の位置を先頭から実処理の前に移動。
- XCP関連のCANフレームがFIFOに詰まれてしまうため、それを避ける目的で移動。
- 使用する前にFIFOクリアしてもOK。
- 構成がややこしいことになってきたので仮想ECUと仮想HILSの実験構成を再確認。
- ネットワーク構成と論理的構成を確認。
- 論理的構成はModelicaモデルベースでシンプルだが、ネットワーク構成は各信号をCANやXCPに変えてるため複雑化。
- このようにノードを分割しておくと部分的に実物に差し替えるなどがし易くなる。
- ついに仮想ECUと仮想HILSの連携動作。
- とりあえず動いたんで録画してYoutubeに上げた。
- 想定よりもキレイだはあるが、やはりカクついている。
- カクついている原因は変数の精度も含まれているかもしれない。
- 変数を32bit長にすることで精度を上げられるがODTを増やす必要がある。
- CAN-FDを使うと・・・。
XCPonCANFD
- XCPonCANとしての課題を明確化。
- CANのデータフィールド8byteの仕様がODTの限界値を決めていた。
- よって、データフィールドが長くなれば解決と言える。
- CANのデータフィールドの上限が8byteに対し、CAN-FDは64byte。
- よって、4byte、3変数が載っても十分な長さ。
- XCPonCAN-FDに対応すべく現状の仮想ECU、仮想HILS構成を再確認。
- CANのところをCAN-FD化。
- XCP経由で変数のやり取りをしている部分を32bit化。
- 改造方針と同時に動作確認手段も模索する必要あり。
- python-can、PyXCPを駆使すればなんとかなりそうという当たりだけ付けた。
- AUTOSAR-XCPのCAN-FD対応に向けての方針を決める。
- CanIf相当の部分とXCPのパケット長のところを修正する必要あり。
- といってもCanIf相当の部分は実はすでにCAN-FD対応済み
- CAN-FD対応の際はDLCの仕様の特殊さに注意する必要がある。
- 8以下の時と8を超えた時で雰囲気が変わる。
- AUTOSAR-XCPのパケット長変更を実施。
- MAX_CTOとMAX_DTOを8から64に変更すればOK。
- CTO、DTOについては過去記事で復習。
- 念のためCANFD_SUPPORTで#ifdefで切り分けられるようにしておく。
- 仮想ECU側のPID制御の入出力定義はポインタで実施。
- 元々が符号付き16bit変数へのポインタだったのでこれを符号付き32bit長変数に差し替え。
- 上記に伴い、参照先のアドレス境界を16bitから32bitに切替。
- PID制御器の入力はfloat64で、参照変数が16bitか32bitかは気にしなくてもOKなコード。
- 指令器の修正範囲を確認。
- CANがCAN-FDに変えることから逆算して特定。
- バス初期化とメッセージ構築の部分。
- CANがCAN-FDに変えることから逆算して特定。
- バス初期化はCAN-FD有効化とDataRateの設定。
- メッセージ構築はCAN-FD化とbitrate_switch有効。
- bitrate_switch無効でもOKだがbitrateは切り替わらない。
- xcp_canクラスのCAN-FDに関係する部分を確認。
- 要はPyXCPをCAN-FD対応にする部分。
- コンフィグレーションのJSON記述を修正すればOKっぽい。
- ソースコードからのリバースエンジニアリングによる調査結果。
- 要はPyXCPをCAN-FD対応にする部分。
- 本当合ってるか不安なのでJupyterNoteBookで動作確認を先に実施予定。
- PyXCPのCAN-FD対応はコンフィグレーションのJSON記述部分だけではあるが、動作確認用に修正する部分もある。
- UPLOAD、DOWNLOADのサイズ。
- DAQ listのODTのサイズ。
- 上記の方針に合わてコード修正実施。
- DAQ listはODT_ENTRYの数を増やした。
- PyXCPのCAN-FD対応の動作確認の結果を確認。
- 一見するとうまく動いているように見える。
- が、どうやらPyXCP側からの送信フレームがCAN-FDになっていない。
- 少なくともCAN-FDフレームの受信はできている。
- PyXCPもVersionがあるので、Version別の挙動を確認してみる。
- PyXCPのVersion別CAN-FD対応状況を確認。
- かなり、いろいろ変化している。
- SERIALパラメータがあると、CAN-FDフレーム送信でエラーが発生する。
- よって、現時点では0.16.5のVersionを使用する。
- CAN-FDフレーム送信可能、DLC可変。
- PyXCP Version0.16.5にてCAN-FDの動作確認再開。
- まずはコンフィグレーション用JSON記述を修正。
- SERIALパラメータを削除。
- SERIALパラメータはデバイスの製品シリアルを指定するもの。
- CAN-FD対応有無を判断しているようで、この判定でCAN-FD不可側になってるように見える。
- PyXCP Version0.16.5にてCAN-FDの動作確認。
- UPLOAD、DOWNLOAD、DAQ、STIMを確認。
- 基本的にOK。
- PyXCP側からのCAN-FDはBitrate_switchが無効になっているが、現状のPyXCPの仕様上やむを得ない。
- この結果を元にxcp_canクラスを改造予定。
- xcp_canクラス改造箇所を整理。
- 予想以上に多いが、仮想HILSにXCPの都合を見せないための部分なので修正内容が集中し易い。
- コンフィグレーション用のJSON記述の修正。
- 事前のPyXCP CAN-FD実験を元に修正。
- SERIALを消して、FDとDATA_BITRATEと追加。
- 事前のPyXCP CAN-FD実験を元に修正。
- DAQ listのODT_ETNRYのデータ長変更(16bit→32bit)。
- namedtupleを使用していたので修正は楽。
- DAQの受信レイアウト調整。
- int.from_bytesを使用してるので16bitから32bitへの変更は楽。
- DAQ受信データの物理値変換(1/65536→浮動小数点)。
- STIMの送信レイアウト調整した。
- int.to_bytesを使用指定していたので一撃で修正完了。
- STIM送信時のrawデータ変換(浮動小数点→1/65536)を実施。
- xcp_canのテストコードとしては送信データをリスト管理しているだけなので、そのリスト修正するのみ。
- 仮想HILS修正時に要注意。
- xcp_canのテストコードとしては送信データをリスト管理しているだけなので、そのリスト修正するのみ。
- xcp_canクラス改めxcp_canfdクラスを作成。
- xcp_canfdクラスのソースコードを開示。
- 前回までの修正分を盛り込んだのみ。
- コンフィグレーション、ODT_ENTRY構成、STIMレイアウト、DAQレイアウト、STIM送信用データ。
- 前回までの修正分を盛り込んだのみ。
- 次回は、これを実際に動作させてみる。
- xcp_canfdクラス動作確認を実施。
- 問題無く動作。
- 念のためxcp_canの時の動作確認結果とも比較。
- 同じ動きをしていることが確認できる。
- CAN-FD回線モニタも実施し、CAN-FDフレームのレベルでも確認。
- こちらも想定通りの動作をしていることを確認。
- PID制御器もおおよそ狙い通り動いてそう。
- DAQリスナーのCAN-FD対応の要否ついて説明。
- CANのみ対応のインターフェースはCAN-FDフレームを検知すると「異常フレーム」と認識しエラーフレームをもってフレーム破壊を行う。
- よって、CAN-FDフレームが流れるネットワークにCANのみ対応インターフェースは接続禁止。
- CANのみ対応のインターフェースはCAN-FDフレームを検知すると「異常フレーム」と認識しエラーフレームをもってフレーム破壊を行う。
- DAQリスナーの修正は一撃。
- DAQリスナーCAN-FD対応版の動作確認を実施。
- can.loggerのCAN-FDモードを並走さえて回線モニタを実施。
- 問題無くCAN-FDフレームが送出されていることを確認。
- Bitrate_switchも有効になっている。
- 問題無くCAN-FDフレームが送出されていることを確認。
- can.loggerのCAN-FDモードを並走さえて回線モニタを実施。
- python-canの範疇では問題無しと判断できる。
- 仮想HILSのCAN-FD対応方針整理。
- 数としてはそこそこあるが、一個一個は1行修正のレベル。
- importしているxcp_canをxcp_canfdに変更。
- 上記のクラス変更に伴い、XCPインスタンスの生成部分変更。
- importのところでエイリアスを使うのもあり。
- 指令値受取用バスの初期化変更。
- バス初期化の引数をFD用に変更するのみ。
- XCP DAQ受信部のレイアウト変更対応とLSB変更対応
- xcp_canfdクラス作成時のテストコードと同じ対応。
- XCP STIM送信部のレイアウト変更対応とLSB変更対応
- レイアウトはxcp_canfdクラス内で決めているのでLSB対応のみ。
- 仮想HILS側の修正後のコード開示。
- 前回までの修正範囲と内容を反映したのみ。
- tkinter、matplotlibのコードもあり、肥大化しているが、基本一直線のコード。
- タイマハンドラで周期的に呼ばれるくらい。
- いろいろな要素が絡んでるので、次回は全体構成の再確認。
- XCPonCANFD対応に於ける当初想定していた仮想HILS、仮想ECUの全体構成と実際の全体構成。
- 基本的には想定通りの修正。
- PyXCPのCAN-FD対応が難航したのが想定外ってくらい。
- 基本的には想定通りの修正。
- 論理構成としては変わらず。つまり基本的な動作は変わらないはず。
- 変数のサイズと精度が変わっているのでそれの効能を期待。
- 仮想HILSと仮想ECUのXCPonCANFD対応の動作確認は結果としては失敗に終わった。
- 変数の精度向上はあまり性能向上にはつながらなかった。
- しかしXCPonCANFDを直に見るのも珍しい体験なので、これはこれで将来に生かすって発想が大事。
- 失敗したからこそ意地でも糧になるものを拾うべし。
CANoeでFMU/FMI
- 仮想HILSと仮想ECUの精度が上がらなかった原因を予測。
- ほぼ間違いなく応答性が原因。
- Pythonではこれ以上の応答性は得られそうもない。
- ほぼ間違いなく応答性が原因。
- Pythonに変わって仮想HILS側をVector社のCANoeにしたらどうかという意見あり。
- 本物のHILSと比べ、コスパも良さそう。
- CANoeで仮想HILSの実現が可能かを検討する間に現行の仮想HILSの機能を列挙した。
- FMU import&実行。
- たぶんOK。でも要確認
- 各種信号のグラフ表示&CAN受信。
- 間違い無くできる。
- XCPマスタ
- 本当のXCPをするなら追加ライセンスが必要。
- しかし、CAPLを駆使すれば今回の目的は達成できそう。
- CANoeの仮想HILS化への実験ロードマップ提示。
- 大雑把にはFMU importとXCPマスタの2つ。
- FMU importの実験をやってからXCPマスタの実験の流れ。
- 最初はXCPを使用せずにCANoeのシミュレーションバスを使用したFMU間連携をさせてみる。
- CANoeでFMU import&動作実験の全体構成提示。
- 恒例のネットワーク構成と論理構成。
- FMU間の各信号の接続はCAN経由で行う。
- 実は以前SimulinkDLLをCANoeで駆動させた時と近似の構成。
- SimulinkDLLの代わりにFMUになっただけ。
- dbcファイルについて簡単に説明。
- CANメッセージとそれに載せるシグナルだけでなく、ネットワークノードの定義もできる。
- ネットワークノードを定義しておくと、CANoeのインポートウィザードでノードの自動生成をしてくれる。
- CANメッセージとそれに載せるシグナルだけでなく、ネットワークノードの定義もできる。
- dbcファイルを作成開始。
- CANeb++エディターを使用。
- プロトコルの設定まで実施。
- CANeb++エディターを使用。
- CANdb++エディターで各種定義を実施。
- シグナル、メッセージ、ノードの順番で定義していく。
- メッセージの周期時間は送信周期を示す。
- 単位は[ms]。
- 属性の「GenMsgCycleTime」パラメータを修正することで変更可能。
- CANoe.IL機能を使用する時に生きてくるパラメータ。
- dbcファイルが出来上がったのでCANoeのデータベースインポートウィザードを使ってみた。
- dbcを読み込むことでdbc内で定義されているネットワークノードをCANoeに割り当てることが可能。
- 生成されたノードはdbcで定義されたCANメッセージを同じく定義された送信周期で送信可能。
- CANoe.IL機能の一端。
- CANoe.IL機能でシグナルを簡単に叩けるようにはなったが、FMUとの接続が無いとCAPLを実装してもイマイチ。
- というわけでFMUインポートから実施。
- FMUインポートの手順確認。
- 最終的にはCANoeシステム変数と紐づいて、そのシステム変数を読み書きすることで結果としてFMUのInput、Outputにアクセスできる。
- FMUの中に指令器もあったが、CANoeから指令値を変えられた方が便利なのでシグナルジェネレータ機能を使用する方針となる。
- dbcファイルで定義したシグナルに対して自由は波形を載せることが可能な機能。
- シグナルジェネレータで台形波を作成しTargetシグナルとして出力できるように設定。
- さまざまは波形を設定可能。
- FMUとCANシグナルの紐づけのためついにCAPLに手を付け始める。
- CAPLではイベントハンドラを使うことが多い。
- イベントはおおよそ以下。
- シミュレーション開始。
- タイマ。
- CAN受信。
- シグナル更新。
- イベントはおおよそ以下。
- ネットワークノード毎にCAPLを設定できるので、ノードの役割を意識する必要がある。
- 仮想HILS向けCAPLコード公開。
- イベントハンドラは関数先頭に「on」が付く。
- その後ろに以下が続く。
- start:シミュレーション開始。
- timer:タイマ。
- signal:シグナル更新。
- その後ろに以下が続く。
- CAPLのコツはシンプルに書く。
- 複雑な処理はライブラリに逃がすなどがコツ。
- ついにCANoe上でFMUを動作させる実験を開始・・・したが・・・。
- 謎エラー発生。
- 32bitアプリケーションから呼ばれるFMUの場合はwin32向けFMUである必要がある。
- しかし、今回使用しているCANoeは64bitアプリケーションのため辻褄が合わない。
- なぜこのようなことになっているか要調査。
- しかし、今回使用しているCANoeは64bitアプリケーションのため辻褄が合わない。
- CANoeが32bitFMUを要求する理由の調査結果。
- CANoe内に2つのアプリケーションが動作しているもよう。
- 描画担当で64bitプロセス。
- 計測/演算担当の32bitプロセス。
- CANoe内に2つのアプリケーションが動作しているもよう。
- FMUは計測/演算担当のプロセス側が読み込んでるっぽい。
- CANoeの内部構成についていろいろ考察したので次回説明予定。
- CANoe内の描画プロセスと計測/演算プロセスの存在をタスクマネージャで確認。
- 計測/演算プロセスことランタイムカーネルが32bitプロセス。
- ランタイムカーネル側のプロセスIDを元にTCP/IPのListenポートを確認。
- netstatで該当プロセスにListenポートがあることを確認。
- CANoe内のTCP/IPによるプロセス間通信の様子を確認。
- WiresharkでBSD-loopbackをキャプチャ。
- netstatで確認したポートをキャプチャ。
- CANoeの設計思想予想。
- ユーザインターフェースとランタイムカーネルは別PC、別プラットフォームでも動作する思想。
- 32bit FMU作成しCANoeに組み込んだ。
- CANoeでFMU駆動の実験。
- かなりキレイな波形が取れた。
- FMU、CAN送信共に1ms駆動させているため。
- Pythonの時は100ms駆動相当になっていた。
- かなりキレイな波形が取れた。
- このあとはCAPLでXCPを実現するがpython-canでやったことと同じことをやればOK。
CANoe 仮想ECU連携
- 残り作業はXCPだけではない。
- 構造が複雑なので一旦整理。
- Virtual CAN BusとCANoeシミュレーションバスは直結していると思って良い。
- 構造が複雑なので一旦整理。
- CANoe仮想HILSと仮想ECU連携のロードマップ提示。
- 指令器disable。
- テストノード追加。
- XCP関連セットアップ。
- DAQ/STIM関連。
- CANoe上の指令器をdisable。
- シミュレーション設定ウィンドウのノード上でdisable操作が可能。
- CANOe上にテストノード追加。
- シミュレーション設定ウィンドウのバス上で追加操作可能。
- 今回はCAPLテストモジュールを使用。
- .NET、XMLのテストモジュールは気が向いたら調べる。
- 今回はCAPLテストモジュールを使用。
- シミュレーション設定ウィンドウのバス上で追加操作可能。
- CANoeでXCP関連の処理を追加する際の方針を確認。
- 複数のノードにXCP関連処理を記載するので連携が必要。
- ノード間連携はCANoeシステム変数を使うのが最も手っ取り早い。
- CANoeシステム変数の追加方法を確認。
- CAPLからシステム変数にアクセスする際は変数名の目に「@」をつける。
- CANoeシステム変数の追加方法を確認。
- CANoeテストノードに記載するXCPセットアップ関連のCAPLコード開示。
- ネットワークノードはイベントドリブン型、テストノードはシーケンシャル型とやや性格が違う。
- といっても、テストノードでもCAN送信/受信イベントは受け取れるので気にするほどの差ではない。
- ネットワークノード(Controller)に追加するXCP DAQ/STIM関連のCAPLコード開示。
- CAPLのCAN送信/受信の機能を使って、無理やりXCPを実現。
- on message 2でDAQ受信を実現、stimでSTIM送信を実現。
- 同時にシグナル単位の単位変換も行っている。
- AUTOSAR-XCP側こと仮想ECUもちょっとだけ修正が必要。
- 修正しなくても動くがCANoe側が1ms駆動なので仮想ECU側も1ms駆動にしたい。
- 修正は以下。
- PID制御器の処理周期を1ms化。
- PID制御器の演算用の想定処理周期パラメータを1ms化。
- AUTOSAR-XCPこと仮想ECU側の修正コード開示。
- PID制御器の処理周期修正。
- ecu_t10ms_job()関数からecu_t1ms_job()関数へ移動。
- PID制御器か内の微分、積分の演算で使用しているΔtを修正。
- #define dTを0.001に変更。
- CANoe仮想HILS、AUTOSAR-XCP仮想ECUの実験構成を再確認
- 上記の動作確認実施。
- かなりキレイな波形が取れている。
- CANoe環境であれば1msオーダーの応答性が確保可能。
- Python環境、CANoe環境を検証プロセスに含めると確保難な設備利用の回数を減らす効果が期待できる。
ASAM MDF
- Python環境下の仮想HILSで収録データが無いことが問題に。
- さらに収録データフォーマットはMDFが望ましいらしい。
- MDFはASAMで規定されてる標準仕様。
- ただしVersion4がASAM仕様で、それより以前のVersionは非ASAM。
- Version3,Version4のどちらかについて解説していく方向にしたい。
- MDFの説明はVersion4のみとする。
- ASAM仕様として公開されてるので妥当な判断。
- MDFのデータ構造について説明。
- 基本はASAM公式サイトのMDFで説明されている。
- それだけだととっかかりが無さ過ぎるので、簡単に説明する。
- MDF内部はHD,DG,CG,CN,DTがツリー構造を取っている。
- 今回はMDFを利用することを目的としているのでMDFの細かい仕様は把握する必要はない。
- ただ、ポリシーや存在するデータなどは把握しておいた方が良い。
- MDF DT内のレコードについて説明。
- レコードという単位で格納されている。
- このレコードがCGと紐づいた情報。
- よって、CGの数だけレコードが混在することになる。
- MDFのDGに含まれるCGの数によってunsorted MDFとsorted MDFに分けられる。
- unsorted MDFが複数CG、sorted MDFが単一のCG。
- Version3は無条件でunsorted MDF相当になる。
- Vector MDF ValidatorでMDFの内部構造を参照できる。
- MDFの有用性についての話に突入。
- 計測生値以外の情報も格納されている。
- 物理値変換式。
- 単位。
- 異なるファイルとの同期を取り易い構造になっている。
- 複数のDGを読み取る機能があれば、異なるMDF間でも同様の処理になるため、結果的に同期が取れるビューワになることが多い。
- 動画への参照も可能。
- MDF仕様単体以外の有用性としてASAM ODSとの連携がある。
- ASAM ODSはテスト自動化にあたり、表現の曖昧性に課題とした仕様。
- ざっくり言うと「テスト管理のパラメータの標準化」。
- ASAM ODSの中のMEASURMENTS領域とDIMENSION&UNIT領域がMDFと関係する。
- ASAM OSDが育ってきた文化について説明。
- 車両のNVH(Noise、Vibration、Harshness)評価を想定したもの。
- ベンダー的にはあまりMDFは使用しない。
- ASAM MDFが育ってきた文化について説明。
- ベンダーが積極的にMDF利用。
- 標準化の認識が日本人的には強烈なギャップあり。
- ODSとMDFの具体的な問題に突入。
- 計測結果の再現性ポリシーが違う。
- MDFはECUに生値を入れたい動機がある。
- 計測データのレートの取り方が違う。
- ODSは一定サンプリング、MDFは可変サンプリング。
- 計測データの格納状態が違う。
- ODSはチャンネル別、MDFはCG単位のレコード。
- ODSとMDFの計測データの格納状態の違いに起因する問題。
- MDFのレコードを走査する仕様はODS側にある。
- unsorted MDFの場合、ODSのレコード走査仕様は使えない。
- よって、自動車業界としてはsorted MDF推奨の流れ。
- さらにMDF仕様がversion UPしてODS都合の仕様になる可能性もある。
Python AsamMdf
- PythonパッケージにAsamMdfというものがあり、これを使えば簡単にMDFを作成できそう。
- Viewerも付属している。
- 今後の方針を打ち出した。
- 基本機能確認
- インストール。
- View実験。
- MDF生成。
- 他のツール(CANape)で作成したMDFを読めるか実験。
- 仮想HILS組み込み。
- AsamMdfの基本機能を確認。
- 公式サイトで説明はされている。
- 基本機能と未対応機能を列挙。
- 今回使用する範囲の機能は問題無し。
- 念のためMDF生成実験の時に使用する機能をもうらできるようなデータ構造を考えておく必要はある。
- 複数のDG/CG。
- CGに複数のCN。
- 比較的特殊な物理値変換式。
- AsamMdfのインストール方法は簡単なパターンだとpipとcondaの2種類。
- pipの場合、付属Viewerが必要な場合はasammdf[gui]を指定する必要がある。
- AsamMdf付属のViewerはPython環境直下のscriptフォルダにインストールされる。
- OS管理側は特に汚さない。
- AsamMdf付属Viewerで読み込ませてみるのはASAM MDF仕様と同梱されてるサンプルMDF。
- 仕様を取得できない場合は、ASAM公式サイトから入手することも可能。
- 実際にMDFを読み込ませてみた。
- 波形表示だけでなく、詳細データ表示も可能。
- サンプルMDFはCANape、INCAが出力したもの。
- 生成するMDFの方針としては複数のDG、特殊な物理値変換を入れる。
- 上記を元にMDF構成を決めた。
- DG2つ、CN3つ&それぞれに物理値変換式を含む。
- AsamMdfによるMDF生成手順を確認。
- 最初にシグナルを作っておいてその後にMDFのデータ構造を作って行って最後にファイル保存。
- 今回からPythonによるMDF生成を開始。
- まずはimport
- asammdfモジュールのMDFとSignal。
- 疑似データ生成用にnumpyも。
- numpyでタイムスタンプ生成。
- シグナルを生成。
- 信号、タイムスタンプを引数にして生成。
- これ以外のオプションもあるがconversionが魔境。
- asammdfのSignalのオプション引数conversionは物理変換式。
- 大きく8種類存在。
- no conversion。
- 無変換。
- linear。
- 線形変換。
- algebraic。
- 代数変換。
- 文字列で式を表現。
- sin、cos、log、exp等の算術関数が使用可能。
- MDFのrationalの変換式は制限付き有理関数。
- 有理関数はxの多項式が分母分子に来るもの。
- MDFの場合は分母分子が2次までの有理関数を想定している。
- rationalには有理関数の6個の係数を指定するだけでOK。
- P1~P6をKeyとして、それぞれの係数をValueと置く。
- MDF conversion仕様のtabularについて説明。
- 一言で言うとテーブル変換。
- tabularには線形補間に関連する仕様がある。
- with interpolation仕様。
- 指定した点の間を線形補間。
- without interpolation仕様。
- 線形補間はしないが中点を境に変換範囲を推定。
- with interpolation仕様。
- MDF conversion仕様 value to textについて説明。
- 利用シーンとしては状態名の表示。
- シフトなどが代表的。
- 利用シーンとしては状態名の表示。
- AsamMdfでのvalue to textの設定方法について説明。
- 辞書型に対してval_、text_のテーブルを設定。
- 指定外の数値だとdefault caseになる。
- MDF conversion仕様 tabular with rangeについて説明。
- tabular仕様とにているが、入力側をレンジ指定できる。
- tabular with range仕様ではdefault caseの値設定が必須。
- レンジ外の場合はこの値が採用される。
- この部分がtabular仕様の振る舞いとの大きな差
- レンジ外の場合はこの値が採用される。
- MDF conversion仕様 value range to textを説明。
- value to textの入力側が範囲指定できる。
- value to textとtabular with rangeを合わせたような仕様。
- tabular with rangeの出力側のphys_がtext_になった感じ。
- MDFの各種物理変換仕様をやったので、折角なのでそれらもMDF生成実験に組み込む。
- 具体的には以下の変換パターンを構成に追加。
- tabular。
- tabular with interpolation。
- value to text。
- tabular with range。
- value range to text。
- シグナル生成の10msサンプリングと100msサンプリングの部分のコード作成。
- 10msサンプリングの方に複雑さが寄ってる状態。
- 各種物理値変換式のパターンを入れ込んでいるため。
- 100msサンプリングはDataGroupを複数にするために入れている。
- 10msサンプリングの方に複雑さが寄ってる状態。
- 前回までだとシグナルが存在しているだけでMDFのデータ構造にはなっていない。
- 今回はDataGroupに各種シグナルを登録することでMDFのデータ構造と同等の形となる。
- DataGroupの下位にChannelGroupがあるが、AsamMdfとしてはSorted想定のため複数登録はできなそう。
- MDFの全体構造を作るにはMDFクラスのインスタンス(MDFモジュール)が必要。
- MDFモジュールにDataGroupを登録することでMDFとしてのデータ構造が完成する。
- データ構造が完成しているMDFモジュールのsaveメソッドを呼び出すことで保存可能。
- 上書きオプションがあるのでお好みで利用。
- MDF validatorで作成したMDFの構造を確認。
- DataGroup、ChannelGroupは想定通りの構成。
- 物理変換式もMDFに埋まっているので、同じく確認。
- linear、algebraicを確認。
- パラメータや式文字列が埋まっていることも確認
- linear、algebraicを確認。
- 今回もMDF Validatorで物理変換式を確認。
- tabular without interpolationとtabular with interpolationはcc_typeが違うだけで保持している情報は一緒。
- value to textはtabularのようなテーブル情報と変換先の文字列を格納している。
- MDFの残りの物理値変換式をMDF validatorで確認
- tabular with range。
- Lower、Upper、valueで1セットになるよに情報が埋まっている。
- value range to text。
- tabular with rangeの文字列変換型。
- 文字列も情報として埋まっている。
AsamMdf付属Viewer
- AsamMdf付属Viewerで作成したMDFを確認していく。
- まずはMDFを開くところ。
- それっぽく表示されるのは確認。
- まずはMDFを開くところ。
- Linear変換を細かく確認。
- 指定通りの線形変換がされている。
- 同一のChannelを複数表示する方法に加えて片方だけ生値表示する方法がある。
- 次回説明予定。
- 「同一Channelの複数表示」と「Channelの生値表示」のやり方を説明。
- 基本触りながら覚えて行った方が良い。
- その他plot周りの基本操作を説明。
- 波形の拡大縮小、移動、スケール変更などの基本的操作。
- 上記の操作方法が分かっていればそれほど困ることは無い。
- 波形の拡大縮小、移動、スケール変更などの基本的操作。
- AsamMdf付属Viewerで信号確認再開。
- tabular without interpolationを確認。
- tabular with interpolationを確認。
- 補間処理はViewer側で行っているため浮動小数点の演算誤差問題が絡む場合がある。
- 実際は気にするレベルではない。
- AsamMdf付属Viewerで各信号を確認。
- value to text確認。
- plotでは確認できないので、Tabular表示で確認。
- tabular with range確認。
- 狙い通り階段上になっている。
- さらに、範囲外はdefault指定した-1になっている。
- value range to textを抱えた信号をAsamMdf付属Viewerで確認。
- この後はCANapeで同様に各信号を確認していく予定。
- CANapeで確認したことが無い物理値変換もある。
- AsamMdf付属ViewerとPyQT5のVersion不整合が発生。
- Version落として整合。
MDF ViewerとしてのCANape
- MDFを異なるツールで読み込めるのは標準仕様の真骨頂と言える。
- CANapeへMDF内の信号取り込み方法を説明。
- MDFを開いた段階で選択する方法とエクスプローラの測定からのドラッグ&ドロップの方法がある。
- 物理値、生値の切り替え方法を説明。
- グラフィックウィンドウの設定から可能。
- Linearの物理変換を確認。
- 想定通り、y=2x-0.5になっている。
- algebraicの物理変換を確認。
- あまり使わないパターンなのでなんか新鮮。
- CANapeのグラフィックウィンドウにはオートスケール機能がある。
- 「最適に合わせる」を選ぶか「F」キーを押す。
- tabular without interpolationをCANapeで確認。
- 実はECUではあまり見かけないタイプの物理変換。
- tabular with interpolationをCANapeで確認。
- これもECUではあまり見かけないタイプの物理変換だが、センサ値に対して使われることは多い。
- tabular with rangeの物理変換をCANapeで確認。
- 階段状に変換されているのに加え、指定範囲外の値はdefault値になっていることを確認。
- 文字列へ変換するタイプの物理変換用にグラフィックウィンドウでの文字列表示のやり方をレクチャー。
- 次回からの文字列変換タイプにはこれを実施する。
- value to textの物理変換をCANapeで確認。
- value to textの表現がAsamMdf付属Viewerと異なることが発覚。
- MDF仕様としてはファイルフォーマットの規定だけなので表現方法の標準仕様は存在しない。
- 欧州ではツールが先に存在し、それらの共通項をASAM仕様にまとめただけと推測される。
- value range to textの物理変換をCANapeで確認。
- 値の設定上、value to textと同じ感じになる。
- なんだかんだでCANapeの方がAsamMdf付属Viewerより高度なことができる。
- 自動レポート機能もあったり。
- 次回から仮想HILSへAsamMdfを組み込む作業となる。
仮想HILSでMDF
- 仮想HILS改修計画として以下を提示。
- 仮想HILS側にAsamMdfを組み込んでMDF出力機能の追加を示している。
- 少なくとも仮想HILSのみの修正になる。
- 任意のタイミングでMDFを生成するためGUI側の修正も必要。
- 収録データは一時ファイルにした方が良いが今回はメモリ上に蓄える。
- MDF生成をするためのimportを説明。
- 以前のMDF生成実験の時と一緒。
- ボタンGUI追加方法説明。
- 以前のチェックボックス、スケールの時と似たような感じ。
- スケールの時と同じくイベントハンドラを設定可能。
- ボタン押下時のイベントに紐づいて呼び出されるメソッドを設定可能。
- Pythonのリストの宣言の仕方について簡単に説明。
- 空っぽのリストも宣言できる。
- データ保持用のストレージの候補を列挙。
- 上記に合わせて空っぽのリストを事前に宣言。
- 本来であれば一時ファイルの方が望ましいので、必要であればファイルアクセスに差し替える必要がある。
- ボタン押下時にやることを確認。
- MDFインスタンス生成。
- 各種シグナル生成。
- 各種シグナルをリストにまとめる。
- MDFインスタンスに統合
- MDFを生成。
- 各種シグナルとMDFインスタンスを再初期化。
- 上記を元にコードを書いてみた。
- 実際には動かしてみないとわからないが流れはOK。
- 計測データの取得場所はFMU処理をしているFMU_handler内が妥当。
- 描画用のデータもここで取ってる。
- 計測データ対象はFMUの戻りのデータ。
- 仮想HILSが描画しているデータと合わせておくと評価し易い。
- 追加コードは描画用データのdequeとほぼ一緒。
- リスト、dequeのメソッド名が共通化している恩恵。
- 仮想HILSの改修済みコードを開示。
- XCP等の通信周りへの影響は無し。
- GUIがちゃんと配置されてることだけ確認。
- ボタンGUIを増やしたので、スケールの表示位置の微調整を入れた。
- 仮想HILSにMDF生成を組み込んだものの動作確認実施
- リアルタイム波形は30秒の範囲なので、比較用といしてその範囲で計測してMDF生成
- リアルタイム波形をMDFを比較。
- AsamMdf付属Viewer、CANapeと比較したところOK。
- 一応、今回が本シリーズ最終回(たぶん)
コメント