コンテンツにスキップ

5.1.3 Pythonアクションフレームワーク

本項では、Pythonによるカスタムコンポーネントについて、アクションフレームワークを使った実装方法を説明します。

5.1.3.1 Pythonカスタムコンポーネント用アクションフレームワーク#

Pythonカスタムコンポーネント用アクションフレームワークは、より簡易的にPythonカスタムコンポーネントを実装するためのフレームワークです。

通常のカスタムコンポーネントの実装では、mainメソッドの中でループ処理を記述することで、どんなロジックでも実現することができました。 その反面、コンポーネントのライフサイクルについて熟知する必要があり、処理実行のタイミング制御やエラーハンドリングにはある程度のプログラミングの技術が必要でした。

アクションフレームワークを用いる場合は、ループ処理を記述する必要がないため、コンポーネントのライフサイクルを考慮したロジックを含む必要がなく、実際にコンポーネントが目的とするロジックの実装に注力することができるようになります。

5.1.3.2 前提知識#

カスタムコンポーネントを開発する上での前提知識として、Python言語のチュートリアルの内容は大方把握できていること、併せてクラス定義、メソッド定義、クラスの継承、デコレータ等について理解していることを想定しています。 入門レベルの情報については、下記の Python公式ドキュメントを参照してください。

Python公式ドキュメント

また、上記に加えて、アクションフレームワークを使用する場合は下記の知識も必要となります。

5.1.3.3 アクションフレームワーク利用コンポーネントのサンプルプログラム#

アクションフレームワークを使用したカスタムコンポーネントの実装方法説明のため、シンプルな仕様のカスタムコンポーネントをサンプルとして提供しています。

サンプルカスタムコンポーネントのダウンロード

5.1.3.3.1 サンプルプログラムの内容#

カスタムコンポーネントのサンプルとして本章では下記のサンプルを説明します。

  • countup.py

    出力ポートに対して以下の2つのカラムを作成し、データを出力し続けるコンポーネントです。 zipファイル内のsource/python/synapsesample/countup.pyに配置されています。

    カラム名 データ型 内容
    fixcount INT32 5秒に1回、整数値が登録されます。登録される値は7ずつ増加していきます
    (パラメータで指定された任意のカラム名) (パラメータ指定された整数型or浮動小数点型) パラメータで指定された秒数ごとに、整数値が登録されます
  • readinput.py

    パラメータで指定した名称のカラムの値を入力ポートから受け取り、その受け取った値の積算値を毎回出力するコンポーネントです。

  • multicountup.py

    countup.pyのパラメータ指定するカラムと同様に、指定された周期で整数を登録するコンポーネントです。 ただしcountup.pyとは違い、パラメータで複数のカラムを指定することができ、それぞれ独立した周期で値を出力し続けます。

  • csv.py

    入力ポートからデータを受け取り、そのデータをCSVファイルとして書き出し、ファイル型カラムに出力するコンポーネントです。

5.1.3.3.2 実行#

このサンプルは1つのSCCDEパッケージになっています。生成したsccpkgファイルも同梱しています。 パッケージをSynapseへ登録する手順は、SynapseへのSCCPKGファイルの登録、および、削除を参照してください。

5.1.3.4 サンプルの実装詳細#

以降では、サンプルプログラムのソースコードを例に、アクションフレームワーク利用カスタムコンポーネントの実装内容を説明していきます。

5.1.3.4.1 カスタムコンポーネントクラス定義#

アクションフレームワークによるカスタムコンポーネントは、下記の通りSynapseActionComponentBaseクラスを継承した新しいSynapseComponentクラスを定義することでそのコンポーネントベースの名前や識別情報、実行時の処理を定義することができます。 このクラスには@synapse_action_componentのデコレータをつけてuuid, nameを指定してください。

アクションフレームワークによるコンポーネントクラス定義例

from speedbeesynapse.component.actionfw import (
    ActionContext,
    FrozenJsonType,
    TriggerContext,
    synapse_action,
    synapse_action_component,
)

@synapse_action_component(uuid='00000000-0000-0000-0000-000000000000', name='componentname')
class SynapseComponent(SynapseActionComponentBase):
    def setup(self, param: FrozenJsonType) -> None:
        :

    @synapse_action()
    def countup_fixed(self, context: ActionContext, t_context: TriggerContext):
        :

    @synapse_action()
    def countup_by_parameter(self, context: ActionContext, t_context: TriggerContext):
        :

@synapse_action_componentに指定できる引数は以下のとおりです。

引数名 定義情報 説明
uuid UUID コンポーネントを識別するためのUUIDです。ランダムなIDを生成してここに設定してください。
name コンポーネント名 コンポーネントの名前を設定します。画面ではこの名前が表示されますのでなるべく他のコンポーネントと被らず、分かりやすい名前にしてください。

UUIDについては他のコンポーネントと重複しないよう、必ずランダムなIDを生成してください。 他のコンポーネントと同じUUIDにしてしまうと、どちらのコンポーネントも使えなくなることがあります。

クラス内には、任意のメソッドを定義可能です。 メソッドに@synapse_actionのデコレータを適用することで、そのメソッドがフレームワーク側から自動で実行されるようになります。

5.1.3.4.2 SynapseComponentクラスのライフサイクル#

スクリプト内で定義したSynapseComponentクラスが、1つのコンポーネントベースとなります。 Pythonスクリプトファイル内で、このクラスをインスタンス化するような処理は必要ありません。 このクラスは、コンポーネントインスタンスが実行されたときに自動でインスタンス化され、実行を停止すると自動で破棄されます。 アクションフレームワークを使わない、通常のPythonカスタムコンポーネントの場合とはインスタンス化するタイミングが異なることにご注意ください。

5.1.3.4.3 setupメソッド#

特別なメソッドとして、setupメソッドを定義できます。 このメソッドはコンポーネントが実行を開始する前に自動でコールされるメソッドで、引数にコンポーネントのパラメータを受け取ることができます。 何らかのクラス変数等を初期化する必要がある場合は、このメソッド内に実装してください。

また、このメソッドでは、yield文を使用することにより、コンポーネント停止時の処理も記述することができます。 yield以降の処理はコンポーネント停止後に実行されますので、何らかのオブジェクトの開放など、後処理が必要な場合はこれを利用してください。

setupでのyield利用例

@synapse_action_component(uuid='00000000-0000-0000-0000-000000000000', name='componentname')
class SynapseComponent(SynapseActionComponentBase):
    def setup(self, param: FrozenJsonType) -> None:
        # コンポーネント開始時に実行される処理
        yield
        # コンポーネント停止前に実行される処理

5.1.3.4.4 固定周期でのデータ登録#

下記のソースコードは、一定周期でアクションを実行する最もシンプルなアクション定義の例です。

固定周期のデータ登録例

@synapse_action_component(uuid='830c1965-8362-4019-8937-d80a34fc3fab', name='[ActionFW] Countup')
class SynapseComponent(SynapseActionComponentBase):
    :
    @synapse_action.trigger_by_time(5.0)
    @synapse_action.output('fixcount', DataType.INT32)
    @synapse_action()
    def countup_fixed(self, context: ActionContext, t_context: TriggerContext):
        self.fix_counter += 7
        return self.fix_counter

この例の場合、メソッドcountup_fixedが5秒周期で自動的にコールされて、その戻り値がINT32型のカラムfixcountに登録されます。 カラムの定義や実行周期は、メソッドに適用しているデコレータによって制御されます。

  • @synapse_action()

    適用されたメソッドが、このクラスのアクションであることを明示し、アクションフレームワークがこのメソッドを自動実行するようになります。 メソッドの引数は例の通り、self, ActionContext, TriggerContextの3つにする必要があります。

  • @synapse_action.output()

    引数にカラム名、カラムの型を指定することで、適用されたアクションの戻り値を登録するためのカラムを用意します。 このデコレータを使用しない場合、アクションの戻り値は無視されます。

  • @synapse_action.trigger_by_time()

    アクションを実行する周期(秒単位)を指定することができます。

この指定方法の場合、デコレータの引数には全て固定値が指定されているため、コンポーネントのパラメータ設定によらず、常に同じ動作を実行し続けることになります。

5.1.3.4.5 JSONパラメータで制御されたデータ登録#

下記のソースコードはパラメータでアクションの実行を制御する例です。

JSONパラメータによるアクション設定例

from speedbeesynapse.component.actionfw import synapse_json_path as sjp
:
@synapse_action_component(uuid='830c1965-8362-4019-8937-d80a34fc3fab', name='[ActionFW] Countup')
class SynapseComponent(SynapseActionComponentBase):
    :
    @synapse_action.trigger_by_time(sjp('$.interval'))
    @synapse_action.output(sjp('$.column_name'), sjp('$.column_type', default=DataType.INT32))
    @synapse_action()
    def countup_by_parameter(self, context: ActionContext, t_context: TriggerContext):
        self.vcounter += context.param.get('diff', 1)
        return self.vcounter

上記の例は、固定周期でのデータ登録とよく似ていますが、デコレータに渡す引数やクラス変数の加算値に違いがあります。

sjpは、synapse_json_pathの別名です。可読性を上げるためimport時に名前を置き換えています。 sjpの引数には、JSONPathの文字列を渡すことができます。 これは、コンポーネントの引数としてユーザーが指定したデータのJSONデータの特定のプロパティを示すものです。 例として、sjp('$.interval')という指定は、下記のJSONオブジェクトのintervalの値、2.5を示しています。

{
  "interval": 2.5,
  "diff": 10,
  "column_name": "value",
  "column_type": "float"
}

そのため、このコンポーネントのアクションcountup_by_parameterは、2.5秒周期で実行されるようになります。 その他、@synapse_action.outputもカラム名、データ型の指定にsjpを用いることで、ユーザーがパラメータ設定画面で入力した内容を反映することができます。

また、context.paramという指定により、コード上から直接パラメータのJSONデータにアクセスすることも可能です。 context.param.get('diff', 1)を指定すれば、JSONデータ上のdiffの値、10を取得できます。(未定義の場合は、getの第二引数の1が使用されます)

5.1.3.4.6 入力ポートからのデータ受信#

下記のソースコードは、入力ポートから特定のカラムのデータを受け取ったときにアクションを実行する実装例です。

入力ポートからのデータ受信例

@synapse_action_component(uuid='ee6dbe76-fe12-4b2c-a00e-97a35627ffa3', name='[ActionFW] ReadInput')
class SynapseComponent(SynapseActionComponentBase):
    :
    @synapse_action.trigger_by_data(component_name=sjp('$.input_trigger_component_name'), data_name=sjp('$.input_trigger_data_name'))
    @synapse_action.output('sum', DataType.FLOAT)
    @synapse_action()
    def read_summation(self, _context: ActionContext, t_context: TriggerContext):
        self.input_data_sum += t_context.data.value
        return self.input_data_sum

@synapse_action.trigger_by_data()デコレータの適用により、このアクションは入力ポートにつながった特定のコンポーネントの特定のカラムからのデータをトリガにしてコールされるようになります。

トリガとするデータのコンポーネント名、カラム名は本デコレータの引数component_name, data_nameによって指定できます。 上記の実装例では、前述のsjpを使用して、コンポーネントのパラメータから実際のコンポーネント名、カラム名を取得しています。 sjpを使わずに直接文字列を指定することも可能ですが、その場合は常にその指定した文字列に一致するコンポーネント名、カラム名のデータしか扱えません。

アクションが実行された際、実際にトリガとなったデータはメソッドの第三引数t_context: TriggerContextに格納されています。

5.1.3.4.7 アクション実行の並行実行#

これまでに解説したアクションの利用方法では、1つのアクションごとに1つの設定しか処理できません。 ですが実際には標準コレクタのPLCコレクタで複数個のレジスタの読み込みを設定できるように、 同じアクションでもユーザーの設定により複数の処理シーケンスを実行したい場合があります。

下記のソースコードは、1つのアクション定義で、パラメータの配列をもとにして複数の処理シーケンスの実行を可能にします。

複数アクションの並列実行例

@synapse_action_component(uuid='88c150c8-0fc4-489b-b928-aac791cded91', name='[ActionFW] MultiCountup')
class SynapseComponent(SynapseActionComponentBase):
    :
    @synapse_action.trigger_by_time(sjp('$.interval'))
    @synapse_action.output(sjp('$.column_name'), sjp('$.column_type', default=DataType.INT32))
    @synapse_action(sjp('$.countup_list[*]'), user_data=0)
    def countup_by_parameter(self, context: ActionContext, t_context: TriggerContext):
        context.user_data += context.param.get('diff', 1)
        return context.user_data

これまでの実装との違いは、@synapse_action()デコレータの引数です。 この第一引数にsjp('$.countup_list[*]')を指定することにより、パラメータのJSONデータにあるcountup_listの配列の各要素について、 このアクションを実行することを宣言できます。 この指定はJSONPathの仕様に従いますので、この方法以外にもパターンにマッチさせれば様々な方法で複数のアクションシーケンスを実行できます。

@synapse_action()デコレータでJSONPathを指定した場合、その指定にマッチした個々のオブジェクトを基準にして、@synapse_action.trigger_by_time()@synapse_action.output()のJSONPath指定が解釈されます。

例として、下記のJSONデータがパラメータとして設定された場合を説明します。

コンポーネントパラメータ例

{
"countup_list": [{
    "interval": 1,
    "diff": 1,
    "column_name": "value1",
    "column_type": "int32"
}, {
    "interval": 2,
    "diff": 2,
    "column_name": "value2",
    "column_type": "double"
}, {
    "interval": 3,
    "diff": 3,
    "column_name": "value3",
    "column_type": "int64"
}]
}

上記のJSONデータではcountup_list配列が3つの要素を持つため、同じアクションが下記の3つの設定で並列に実行されます。

  • 1つ目の要素により、value1という名前のint32型のカラムが作成され、1秒に1回、1ずつ増加する値が登録されます
  • 2つ目の要素により、value2という名前のdouble型のカラムが作成され、2秒に1回、2ずつ増加する値が登録されます
  • 3つ目の要素により、value3という名前のint64型のカラムが作成され、3秒に1回、3ずつ増加する値が登録されます

それぞれの設定のアクションで保持するデータは、メソッドの第二引数contextにあるuser_dataに保持しています。

5.1.3.4.8 ファイル型カラムへの書き込み#

Experimental

本機能については現在実験段階の機能となっています。 今後のバージョンアップにより仕様変更となる可能性があることにご注意ください。

下記のソースコードは、入力ポートからデータを受け取り、それをファイル型のカラムに書き出すアクションの実装例です。

ファイル型データ出力例

@synapse_action_component(uuid='5d8b980e-f091-437b-b455-fb3c26097ed6', name='[ActionFW] CSV')
class SynapseComponent(SynapseActionComponentBase):
    def setup(self, param: FrozenJsonType) -> None:
        self.record_list = []
        self.threshold = param.get('record_threshold', 10)
        self.data_count = 0

    @synapse_action.trigger_by_record()
    @synapse_action.enable(True)
    @synapse_action.output('outfile', DataType.FILE)
    @synapse_action()
    def receive(self, a_context: ActionContext, t_context: TriggerContext) -> None | tuple[TextIOWrapper, dict]:
        if t_context.record is None:
            return None

        self.record_list.append(t_context.record)

        if len(self.record_list) < self.threshold:
            return None

        with a_context.open_newfile(mode='wt', encoding='utf-8') as fo:
            self.write_records(fo)

        meta = {'media_type': 'text/csv'}
        return fo, meta

ファイル型カラムにデータを出力する場合、@synapse_action.output()の第二引数で、DataType.FILEを指定する必要があります。 これにより、アクション処理中にa_context.open_newfile()メソッドを使って出力するファイルのファイルオブジェクトを開くことができます。

このファイルオブジェクトは通常のPythonプログラミングにおけるファイルオブジェクトと同様に扱うことができますので、 write()メソッドでデータを出力し、close()してからそのオブジェクトをアクションの戻り値としてください。

ファイルオブジェクトとあわせてファイルのメタデータを返却することも可能です。 メタデータにてmedia_typeを指定すればSynapseのWEB画面上での表示時にメディアタイプに応じて表示内容が変化します。

5.1.3.5 アクションフレームワークAPIリファレンス#

本項で紹介した関数以外にも、多数のAPI関数が用意されています。 詳細は付録の「カスタムコンポーネントアクションフレームワーク(Python)リファレンス」を参照してください。

5.1.3.6 制限事項・注意事項#

対応バージョン

本章で説明するアクションフレームワークは、Synapseのバージョン4.12.0より正式に対応しています。 それ以前のバージョンでもアクションフレームワークを認識するものの、現在のバージョンとは互換性がありません。 4.12.0以前のバージョンで利用する場合、何らかのエラーとなる可能性が高いため、ご注意ください。

実行性能

アクションフレームワークは通常のPython実装のカスタムコンポーネント上に定義されたフレームワークです。 アクションの実行制御はPython実装のため、高速なデータ処理には性能が追いつかない可能性があります。 Synapseを実行する環境や実行内容にも影響されますが、アクションの実行間隔としては0.5秒以上の周期を目安としてください。 それ未満の周期のアクションは想定する間隔でアクションが実行されないなどの問題が発生する可能性があります。

依存モジュール

アクションフレームワークでは、Pythonのサードパーティモジュールjsonpath-ng を使用しています。 このモジュールはデフォルト状態ではSynapseにインストールされていないため、手動でのインストールが必要です。 もしくは、アクションフレームワークを利用するカスタムコンポーネントの依存モジュールとして、scc-info.jsonに定義してください。