ジョブフレームワークライブラリ使用方法#

インストール#

  1. 以下のGithubリポジトリをクローンしてください。

    Beckhoff-JP/PLC_JobManagementFramework

  2. TwinCAT XAEでプロジェクトを開き、TwinCATプロジェクトからライブラリを作る手順に従ってライブラリファイルJobManagement.libraryを保存してください。

  3. ライブラリを使用する任意のプロジェクトを作成してください。そのあと、ライブラリを読み込んで使うに従ってライブラリをインストール、追加を行います。

  4. ライブラリを追加したら、ライブラリパラメータを調整します。ReferenceからライブラリマネージャからJobManagementFrameworkを選び、GVLs以下のParamFutureLibを選択します。Library Parametersタブに現れる下記パラメータを適切な値に設定します。

    パラメータ名称

    デフォルト値

    説明

    MAX_TASK_NUM

    20

    逐次・並列ジョブを実行するために複数のジョブを格納するコンテナオブジェクトに格納できるジョブ数を指定します。

    MAX_STRING_LENGTH

    255

    ジョブのひな型となるFutureオブジェクトに設定する名称の最大文字数を指定します。

要求分析#

要求分析により、機械が行う処理内容から、共通の振る舞いを抽出します。抽象化した共通の振る舞いごとにInterfaceFutureインターフェースの実装ファンクションブロックを定義します。このインターフェースを実装したファンクションブロックのひな型として、本フレームワークが提供するライブラリ内で基底クラスとなるFB_AbstructFutureが用意されています。不要なメソッドやプロパティは実装しなくても済むように、最低限の実装がされていますので、このファンクションブロックを継承して、ユーザファンクションブロックを実装します。

詳細は次節へ進んでください。

処理

処理内容

抽象化した機能仕様

1

3秒間点滅し、そのあと消灯して終了

指定のBOOL型変数を任意の時間ON-OFFを繰り返し、その後消灯して終了。

2

外部信号の立ち上がりを20回カウントする

指定のBOOL型変数の立ち上がりをカウントして、規定値になると終了する

Futureファンクションブロックの実装#

機能仕様毎に基本の制御モデルをファンクションブロックを作成します。FB_AbstructFutureを継承したファンクションブロックを作成してください。(図 2.5

../_images/2024-07-06-18-25-53.png

図 2.5 FB_AbstructFutureを継承したファンクションブロックの作成#

追加したファンクションブロックを選択し、コンテキストメニューからAdd>MethodまたはPropertyを選択します。オーバライドする場合Name欄の選択タブから基底ファンクションブロックのメソッドが一覧されますので、選択してください。(図 2.6

../_images/2024-06-26-18-59-57.png

図 2.6 メソッド・プロパティの定義(オーバライド含む)#

メソッドとプロパティの詳細は、表 2.2表 2.3を参照してください。どのメソッドも、実装時は次のルールに基づく必要があります。

入出力変数は空にしておく

入出力変数はインターフェースにより定められていますので、加えることはできません。ファンクションブロックの入出力変数や、ファンクションブロック独自のプロパティを使って、外部とのデータアクセスを行ってください。

終了条件としてメソッドの戻り値をTRUEにする

FB_Executorファンクションブロックはinit(),execute(),quit(),abort()各メソッドを実行したあと、戻り値がTRUEとなる事でプログラムが進行します。かならず戻り値を設定してください。

METHOD execute : BOOL

<<execute 処理>>
IF <<execute 処理終了条件>> THEN
  execute := TRUE;
END_IF

ファンクションブロック内の変数や処理の初期化や中断の処理が不要な場合でも、以下の通り空のinit(),quit(),abort()を実装する必要があります。

METHOD init : BOOL
<<init 処理>>
IF <<init 処理終了条件>> THEN
  init := TRUE;
END_IF
METHOD quit : BOOL
<<quit 処理>>
IF <<quit 処理終了条件>> THEN
    quit := TRUE;
END_IF
METHOD abort : BOOL
<<abort 処理>>
IF <<abort 処理終了条件>> THEN
  abort := TRUE;
END_IF

以上を踏まえて、例としてanalyze_requirement_table_job_designに示す処理1のFutureを定義した例が次の通りです。

Tip

この実装事例は、フレームワークのTwinCATプロジェクトのPOUs>model>activities以下にあります。ご参考ください。

処理1

FB_ControllerType_1

処理2

FB_ControllerType_2

own_outputown_parameterという名前は、InterfaceFutureで規定されたもの以外であることを示しています。実際は目的に合った分かりやすいメソッドやプロパティ名としてください。

own_parameterは点滅動作を行う時間をTIME型で設定し、own_outputはBOOL型変数のREFERENCEを設定しています。

init()でTON変数をリセット、own_outputのデバイスをFALSEにするなど初期化を行っています。また、execute()では0.5秒間隔でown_outputのデバイスのON/OFF反転を繰り返しています。

quit()では、THIS^.init()を実行することでタイマおよびown_outputの初期化を実施しています。

FUNCTION_BLOCK FB_ControllerType_1 EXTENDS FB_AbstructFuture
VAR
  _own_parameter: TIME;
  _set_time: TIME;
  process_timer: TON;
  blink_timer: TON;
  _result :UDINT;
  _own_output: REFERENCE TO BOOL;
END_VAR

METHOD abort : BOOL

  IF process_timer.IN THEN
    _set_time := process_timer.PT - process_timer.ET;
  END_IF
  process_timer(IN := FALSE);
  _own_output := FALSE;
  abort := TRUE;

METHOD execute : BOOL

  blink_timer(IN := NOT blink_timer.Q, PT := T#0.5S);
  IF _own_output AND blink_timer.Q THEN
    _own_output := FALSE;
  ELSIF blink_timer.Q THEN
    _own_output := TRUE;
  END_IF
  process_timer(IN := TRUE,PT := _set_time);
  execute := process_timer.Q;

METHOD init : BOOL

  _own_output := FALSE;
  _set_time := _own_parameter;
  process_timer(IN := FALSE);
  blink_timer(IN := FALSE);
  init := TRUE;

PROPERTY own_output : REFERENCE TO BOOL

  Set:
    _own_output REF= own_output;

PROPERTY own_parameter : TIME

  Get:
    own_parameter := _own_parameter;
  Set:
    _own_parameter := own_parameter;

METHOD quit : BOOL

  THIS^.init();
  quit := TRUE;
終了時にエラーが発生した場合は nErrorID プロパティを通して0以外の値を通知します

init(),execute(),quit()の終了時のエラー状態は、nErrorIDプロパティにて通知してください。FB_Executorで実行した際、これらの処理が完了するとこの終了コードがFB_Executor.nErrorIDプロパティで取り出せます。また、0以外の値となった場合は、E_FutureExecutionState.abort状態となり処理中断状態となります。中断時に実行する処理内容は、abort()に定義してください。

ジョブの生成と実行#

メインプログラムなどのプログラムでは、定義したFutureファンクションブロックのインスタンスと、FB_Executorファンクションブロックのインスタンス化が必要です。

まずは単一のジョブの実行方法についてご説明します。

VAR
  fbTask1         : FB_ControllerType_1;
  output  AT %Q*  : BOOL; // 点滅させたいBOOL変数
   executor        : FB_Executor;
  start           : BOOL; // HMIなどのスタートスイッチ
  _state          : UDINT;
END_VAR

これらのインスタンスに対して、次のプログラムを記述し、ジョブを生成とその実行を行います。これにより、start変数をTRUEにするたびに10秒間点滅を行うジョブが実行されます。

CASE _state OF
  0:
    _state := 1;
  1: // JOBの組み立て
    fbTask1.own_output REF= output; // IOの受け渡し
    fbTask1.own_parameter := T#10S;   // 点滅時間の設定
    fbTask1.future_name := 'Blinker'; // Future名を設定
     executor.future := fbTask1; // ExecutorにFutureをセット
     executor.init(); // 初期化
    _state := 2;
  2: // 実行

    IF  executor.execute() THEN // 実行と終了監視
      _state := 0;
    END_IF

    // Start条件。wait_for_processかabortからのリトライの何れかで再開
    IF start AND  executor.ready THEN
       executor.start();
      start := FALSE;
    END_IF

END_CASE

ジョブコンテナによる実行#

通常は複数のジョブを組み合わせて実行する必要があります。次の適切なジョブコンテナをイスタンス化して、add_future()メソッドにてジョブを登録します。

FB_BatchJobContainer

逐次実行する静的なジョブコンテナ

FB_ParallelJobContainer

並列実行する静的なジョブコンテナ

FB_QueueJobContainer

逐次実行する動的なジョブコンテナ

FB_ParallelQueueJobContainer

並列実行する動的なジョブコンテナ

ここでは、10秒毎に異なるランプを順次点滅させる処理をFB_BatchJobContainerを使って実現する例をご紹介します。

VAR
  fbTasks    : ARRAY [1..4] OF FB_ControllerType_1;
  outputs  AT %Q*  : ARRAY [1..4] OF BOOL; // 順番に点滅させたいBOOL変数
  fbJobContainer : FB_BatchJobContainer;
  ads_reporter : bajp_jobmgmt.FB_ADSLOG_reporter; // 備え付けのイベントレポート
   executor   : FB_Executor;
  start      : BOOL; // HMIなどのスタートスイッチ
  _state     : UDINT;
  i :UINT;  
END_VAR

さきほどの例と違うのは、複数のIO(アクチュエータ)とそれに応じたFutureオブジェクトを配列で用意し、FB_BatchJobContainerのインスタンスを1つ宣言している点です。

CASE _state OF
  0:
    _state := 1;
  1: // JOBの組み立て
    FOR i := 1 TO 4 DO
      fbTasks[i].own_output REF= outputs[i]; // IOの受け渡し
      fbTasks[i].own_parameter := T#10S;   // 点滅時間の設定
      fbTasks[i].future_name := CONCAT('Blinker ', TO_STRING(i)); // Future名を設定
      fbJobContainer.add_future(fbTasks[i]); // 逐次実行ジョブを追加
    END_FOR

     executor.future := fbJobContainer; // ExecutorにFutureをセット
     executor.job_event_reporter := ads_reporter; // ADSLOGSTR出力のイベントハンドラをセット
     executor.init(); // 初期化
    _state := 2;
  2: // 実行

    IF  executor.execute() THEN // 実行と終了監視
      _state := 3;
    END_IF

    // Start条件。wait_for_processかabortからのリトライの何れかで再開
    IF start AND  executor.ready THEN
       executor.start();
      start := FALSE;
    END_IF
  3:
    // `reset()`にて、executorオブジェクトにぶら下がっている全てのジョブを削除
    IF  executor.reset() THEN
      _state := 0;
    END_IF

END_CASE

ジョブコンテナでは、複数のJOBをadd_futureで登録し、そのコンテナオブジェクトを1つのジョブとして扱うことができます。この例では、配列で定義した出力変数とFutureオブジェクトをインスタンス化します。これらをFB_BatchJobContaineradd_futureメソッドにて順に登録しています。このジョブを、executorオブジェクトのfutureプロパティでセットして実行すると、登録した子ジョブを逐次実行することができます。上記実装例では、outputs[1]outputs[4]の間で、10秒間ごとに順次点滅する出力が移動します。

また、イベントハンドラの適用節で詳しく説明しているとおり、job_event_reporterプロパティには、本フレームワーク付属のADSLOGSTRを使ったジョブの状態遷移イベントをメッセージウィンドウに出力するイベントハンドラを登録していますので、XAEのメッセージウィンドウに、PLCのシステム時刻(100ns精度)でのジョブの名称やIDとその開始、終了イベントが記録されます。

QueueJobの場合

上記で示す実装例は静的ジョブコンテナでした。_state=1で構築したジョブを_state=2で実行したあと、_state=3reset()を行うことでいったん全て消滅させた上で、再度_state=0へ戻りジョブを組み立て直しています。

FB_QueueJobContainerおよび、FB_ParallelQueueJobContainerを用いる場合は、終了したら自動的にキューから消滅しますのでreset()を行う必要はありません。add_future()を行うとすぐさまジョブを実行し、全てのジョブが完了したら自動的にコンテナ上はジョブが無くなった状態となり、終了します。

ただし、動的ジョブコンテナの場合でも、全てのジョブが実行完了になると実行状態の完了シグナルであるexecute()のTRUEを返します。これにより、そのドライブオブジェクトであるFB_Executorではfinish状態まで進んでしまいますので、もう一度最初からexecute()しなおす必要があります。

ジョブコンテナの中身が空になっても実行状態を維持するには、ジョブコンテナオブジェクトのオプションを使ってcontinuous_mode := TRUEを指定してください。これにより空になっても実行状態を維持できます。

このモードでは特に終了待ちを行う必要はありませんので、num_of_futureの状況を監視して実行中のジョブが無いか確認の上、自発的にシーケンスを終了させてください。

処理中断とリセット#

InterfaceFuture.init(),InterfaceFuture.execute(),InterfaceFuture.quit()それぞれの終了時に、nErrIDが0でない場合、エラーとみなし自動的にabort状態へ遷移し、InterfaceFuture.abort()が実行されます。状況に応じてInterfaceFuture.abort()の処理を実行したくない場合は、InterfaceFuture.abort()内の定義で、nErrIDの値に応じて処理を切り替えてください。

InterfaceFuture.abort()が正常に終了すると、FB_Executor.readyプロパティがTRUEになります。

そのあと異常になる要因を対処し、再開する場合、FB_Executor.start()を呼び出すと、中断前に行っていた処理を再実行します。

処理を中止する場合

中断後、完全に処理を中止する場合、FB_Executor.execute()の実行をやめ、FB_Executor.init()を行ってください。処理状態が初期化され、次回FB_Executor.execute()を実行したら最初からやりなおします。

外部から処理中断を行うこともできます

FB_Executor.abort()を外部から呼び出すと、nErrIDに関わらずabort状態に遷移します。そのあとの再実行、中止方法は同様です。

Tip

サンプルコードではInterfaceFuture.abort()処理内にて、タイマ値の現在地を保存した上でタイマの計測停止を行います。また、次回処理再開時にはその残時間を改めてTONに設定しています。

このように、処理単位をFutureファンクションブロック化することで、インスタンスごとに安全な処理中断・再開処理が可能になります。

イベントハンドラの適用#

FB_Executorには、job_event_reporterというプロパティがあり、InterfaceJobEventReporter型で実装した状態遷移毎に実行されるイベントハンドラを登録することができます。

InterfaceJobEventReporterreportメソッドのみを持つインターフェースで、FB_Executorで状態遷移が発生すると、都度このメソッドがコールされます。詳細は、InterfaceJobEventReporterをご覧ください。

reportメソッドには、さまざまなイベント記録機構を実装可能です。たとえばTF6730 IoT CommunicatorのMQTTのパブリッシュを実装すればAzureやAWSのIoTサービスに接続し、装置のジョブ動作状態をモニタ可能ですし、TF6420を用いれば各種データベースサーバにイベントを記録することができます。

ここでは、本フレームワークに内包している、ADSLOGSTRに出力する実装例をご紹介します。開発環境のメッセージウィンドウに文字列でスクロール表示します。

FUNCTION_BLOCK FB_ADSLOG_reporter IMPLEMENTS InterfaceJobEventReporter

METHOD report : BOOL
  VAR_INPUT
    old_state	: E_FutureExecutionState;
    new_state	: E_FutureExecutionState;
    record_time	: Tc2_Utilities.T_FILETIME64;
    executor	: REFERENCE TO FB_Executor;
  END_VAR
  VAR_INST
    fb_timezone_info : FB_GetTimeZoneInformation := (bExecute := TRUE);
  END_VAR
  VAR
    last_state : STRING;
    current_state : STRING;
  END_VAR

  last_state := TO_STRING(old_state);
  current_state := TO_STRING(new_state);
  fb_timezone_info();
  text := FILETIME64_TO_ISO8601(fileTime := record_time, nBias := DINT_TO_INT(fb_timezone_info.tzInfo.bias), bUTC := TRUE,nPrecision := 6);
  text := CONCAT(text, ':');
  text := CONCAT(text, last_state);
  text := CONCAT(text, '->');
  text := CONCAT(text, TO_STRING(current_state));
  text := CONCAT(text, ':');
  text := CONCAT(text, executor.future.future_name);
  text := CONCAT(text, ':');
  text := CONCAT(text, executor.id);

  ADSLOGSTR(msgCtrlMask := ADSLOG_MSGTYPE_LOG, msgFmtStr := text, strArg := '');

  report := TRUE;

このファンクションブロックをインスタンス化し、FB_Executorjob_event_reporterプロパティにセットします。

PROGRAM MAIN
VAR
  :
  ads_reporter : bajp_jobmgmt.FB_ADSLOG_reporter; // 備え付けのイベントレポート
   executor   : FB_Executor;
  :
END_VAR

:
   executor.job_event_reporter := ads_reporter; // ADSLOGSTR出力のイベントハンドラをセット

このジョブの実行中、リアルタイムにADSLOGSTRでメッセージが出力されます。このように、今回はADSLOGSTRでしたが、このメソッドの実装方法次第ではデータベースやMQTTなどのデータウェアハウスに格納すれば、簡単に、特定のfuture_nameの、特定のjob_idの処理開始、終了の範囲データを抽出することが可能です。

電流、モータトルク、圧力、温度など様々な時系列連続データがありますが、これらの値とは別に、制御モードを示すデータを統一的なフォーマットで収集することは、製造業のリソース4M(Man, Machine, Material, Method)という定性データを示すカテゴリカルデータの生成根拠となりえます。データ収集時点で苦労されているのはこの点だと思いますので、この課題を解決できる本フレームワークの導入は、データ活用を飛躍的に向上させることにつながるでしょう。

Message 2024/07/08 22:32:47 757 ms | 'PlcTask' (350): 2024-07-08T22:32:47.757000+09:00:finish->wait_for_process:BATCH JOB:
Message 2024/07/08 22:32:47 707 ms | 'PlcTask' (350): 2024-07-08T22:32:47.707000+09:00:quit->finish:Blinker 4:/4
Message 2024/07/08 22:32:47 707 ms | 'PlcTask' (350): 2024-07-08T22:32:47.707000+09:00:quit->finish:BATCH JOB:
Message 2024/07/08 22:32:47 697 ms | 'PlcTask' (350): 2024-07-08T22:32:47.697000+09:00:process->quit:Blinker 4:/4
Message 2024/07/08 22:32:47 697 ms | 'PlcTask' (350): 2024-07-08T22:32:47.697000+09:00:process->quit:BATCH JOB:
Message 2024/07/08 22:32:37 687 ms | 'PlcTask' (350): 2024-07-08T22:32:37.687000+09:00:wait_for_process->process:Blinker 4:/4
Message 2024/07/08 22:32:37 677 ms | 'PlcTask' (350): 2024-07-08T22:32:37.677000+09:00:quit->finish:Blinker 3:/3
Message 2024/07/08 22:32:37 667 ms | 'PlcTask' (350): 2024-07-08T22:32:37.667000+09:00:process->quit:Blinker 3:/3
Message 2024/07/08 22:32:27 657 ms | 'PlcTask' (350): 2024-07-08T22:32:27.657000+09:00:wait_for_process->process:Blinker 3:/3
Message 2024/07/08 22:32:27 647 ms | 'PlcTask' (350): 2024-07-08T22:32:27.647000+09:00:quit->finish:Blinker 2:/2
Message 2024/07/08 22:32:27 637 ms | 'PlcTask' (350): 2024-07-08T22:32:27.637000+09:00:process->quit:Blinker 2:/2
Message 2024/07/08 22:32:17 627 ms | 'PlcTask' (350): 2024-07-08T22:32:17.627000+09:00:wait_for_process->process:Blinker 2:/2
Message 2024/07/08 22:32:17 617 ms | 'PlcTask' (350): 2024-07-08T22:32:17.617000+09:00:quit->finish:Blinker 1:/1
Message 2024/07/08 22:32:17 607 ms | 'PlcTask' (350): 2024-07-08T22:32:17.607000+09:00:process->quit:Blinker 1:/1
Message 2024/07/08 22:32:07 597 ms | 'PlcTask' (350): 2024-07-08T22:32:07.597000+09:00:wait_for_process->process:Blinker 1:/1
Message 2024/07/08 22:32:07 587 ms | 'PlcTask' (350): 2024-07-08T22:32:07.587000+09:00:idle->wait_for_process:Blinker 4:/4
Message 2024/07/08 22:32:07 587 ms | 'PlcTask' (350): 2024-07-08T22:32:07.587000+09:00:idle->wait_for_process:Blinker 3:/3
Message 2024/07/08 22:32:07 587 ms | 'PlcTask' (350): 2024-07-08T22:32:07.587000+09:00:idle->wait_for_process:Blinker 2:/2
Message 2024/07/08 22:32:07 587 ms | 'PlcTask' (350): 2024-07-08T22:32:07.587000+09:00:idle->wait_for_process:Blinker 1:/1
Message 2024/07/08 22:32:07 577 ms | 'PlcTask' (350): 2024-07-08T22:32:07.577000+09:00:wait_for_process->process:BATCH JOB:
Message 2024/07/08 22:31:42 217 ms | 'PlcTask' (350): 2024-07-08T13:31:42.217000+00:00:idle->wait_for_process:BATCH JOB: