😸

CODESYS Example Projectを調べてみた #5 (Element Collections Library)

2024/12/06に公開

Element Collections Examples

Connpassの勉強会のまとめ記事です。

今回は、Elements Collections Examplesを調査します。このExampleでは、リスト(List)、キュー(Queue)、スタック(Stack)、ハッシュテーブル(HashTable)、ツリーノード(TreeNode)など、データ構造の利用方法を学ぶことができます。

セットアップ

プロジェクトファイルは、以下のように9つのApplicationで構成されています。

Devices

プログラムを実行するには、最初に以下の準備を行います:

  1. デバイスのアップデート: プロジェクト内で使用しているデバイスを最新バージョンに更新します。
  2. 不足ライブラリの追加: 必要なライブラリを追加します。

Applicationの切り替えは、ビルドアイコンの横にあるドロップダウンメニューから行えます。各Applicationを選択してビルドし、ダウンロードを実行してください。1つのApplicationでログインしている状態で別のApplicationに切り替えても、ログイン状態は維持されます。

プログラムの内容

Element Collections Examples.projectには、以下の9つのApplicationが含まれています。数が多いので、今回はNo.1, No.2, No.4に焦点を当てます。

  1. DynamicListExample
  2. Element Example
  3. OnlineChangeSafeLinkedListExample
  4. SimpleHashTableExample
  5. SimpleListExample
  6. SimpleQueueExample
  7. SimpleSortedListExample
  8. SimpleStackExample
  9. SimpleTreeNodeExample

1. DynamicListExample

このExampleでは、COLライブラリのListFactoryIntElementFactoryIListなどを使用して、動的なリスト操作を学びます。

COLライブラリとは?

COLはElementCollectionsライブラリのNamespaceで、リストやキューなどのデータ型を提供するFunction Blockを含んでいます。ライブラリの詳細はCODESYS Online Helpから確認できます。

動的リストの生成

以下のコードは、動的リストを生成する例です。データ型に対応したFactoryとインターフェース型の変数を用意して、FactoryのCreateメソッドでインスタンスを生成し、インターフェース型変数に持たせるというのが他のExampleでも共通する操作の流れです。

PLC_PRG
VAR
    ・・・
    ListFactory : COL.ListFactory;      // リスト生成用Factory
    itfList : COL.IList;               // リスト操作用インターフェース
    ・・・
END_VAR

// 動的リストの生成
itfList := ListFactory.CreateDynamicList(
    udiInitialSize := 10,  // リストの初期サイズ
    uiGrowFactor := 2      // リストが満杯になった場合の増加倍率
);
  • udiInitialSize: リストの初期容量。適切に設定することでメモリアロケーションの回数を削減します。
  • uiGrowFactor: 容量が不足したときの成長係数。適切な値を選択することで、メモリ使用量とパフォーマンスのバランスを調整します。

リスト内の要素を巡回

リストの内容を巡回するには、COL.IListインターフェースとCOL.ListIterator(イテレーター)を使用します。イテレーションの基本手順は以下の通りです:

  1. イテレーターの生成
    ElementIteratorメソッドを使用してリストのイテレーターを取得します。このイテレーターは、リストの現在の状態に基づいて操作を行います。

  2. 次の要素の確認 (HasNext)
    HasNextメソッドを使用して、リスト内にさらに巡回できる要素があるかを確認します。

  3. 次の要素の取得 (Next)
    Nextメソッドを使用して、現在の位置から次の要素を取得します。

以下はコード例です。

PLC_PRG
VAR
    ・・・
    iterator : COL.ListIterator;      // イテレーター
    itfElement : COL.IElement;       // リスト内の要素
    itfIntElement : COL.IIntElement; // INT型要素
    ・・・
END_VAR

// イテレーターの初期化
itfList.ElementIterator(itfIterator := iterator);

// 要素の巡回と処理
    ・・・
	IF iterator.HasNext() THEN
		iterator.Next(itfElement => itfElement); //IElemnet型の変数にイテレーターの次の要素を受け取る。
        // itfElementを用いて要素に関連する処理
    END_IF

要素の追加と解放

リストに要素を追加し、エラーが発生した場合はインスタンスを解放します。

AddProductToShow(METH)
METHOD AddProductToShow
VAR_INPUT
	iProductID : INT;
END_VAR
VAR
	itfElement : COL.IElement;
	eError : COL.COLLECTION_ERROR;
	itfInstance : FBF.IInstance;
END_VAR

// 要素の生成と追加
itfElement := IntElementFactory.Create(iValue := iProductID);
eError := itfList.AddElement(itfElement := itfElement);

IF eError <> COL.COLLECTION_ERROR.NO_ERROR THEN
	__QUERYINTERFACE(itfElement, itfInstance);
	IF itfInstance <> 0 THEN
		itfInstance.Dispose();
	END_IF
END_IF

2. Element Example

このExampleでは、独自のデータ型MyDataを定義し、それをIMyElementインターフェースを通して管理します。また、動的生成を可能にするためにMyElementFactoryを使用します。

実装の流れ

  1. MyDataの定義
    データ型MyDataFBF.InstanceDataを基に定義します。これにより、リストやハッシュテーブルなどのコンテナで管理できる要素型を作成します。

  2. インターフェースの定義
    COL.IElementを拡張したIMyElementインターフェースを定義します。このインターフェースでは、カスタム要素に特化したメソッドを宣言します。

  3. インターフェースの実装
    MyElementというFunction BlockでIMyElementを実装します。これにより、カスタムデータ型に対する具体的な操作が可能になります。

  4. Factoryの定義
    動的生成を可能にするためにMyElementFactoryを定義します。

インターフェースの定義

以下は、IMyElementインターフェースの定義です。COL.IElementを拡張し、2つのプロパティTestIntValueTestIntValue2を追加しています。

IMyElement
INTERFACE IMyElement EXTENDS COL.IElement

PROPERTY TestIntValue : INT
PROPERTY TestIntValue2 : INT

インターフェースの実装

以下は、MyElementというFunction BlockでIMyElementを実装する例です。このFunction Blockに対しては、Elementを操作するためのメソッドがいくつか定義されています。

MyElement(FB)
{attribute 'no_explicit_call' := 'Explicit calls not allowed.'}
FUNCTION_BLOCK MyElement EXTENDS FBF.InstanceBase IMPLEMENTS IMyElement
VAR_INPUT CONSTANT
	iInt1 : INT;
	iInt2 : INT;
END_VAR

メソッドの例としてElementEqualsを見ると以下のような実装になっています。

MyElement.ElementEquals(METH)
METHOD ElementEquals : BOOL
VAR_INPUT
	(* The element to compare*)
	itfElement	: COL.IElement;
END_VAR
VAR
	itfIntElement : IMyElement;
	xResult : BOOL;
END_VAR

xResult := __QUERYINTERFACE(itfElement, itfIntElement);
IF xResult THEN
	ElementEquals := TestIntValue = itfIntElement.TestIntValue AND TestIntValue2 = itfIntElement.TestIntValue2;	
ELSE
	ElementEquals := FALSE;
END_IF 

Factoryの定義

以下は、MyElementFactoryを定義し、MyElementのインスタンスを動的に生成する例です。Factoryは、必要に応じてMyElementのインスタンスを生成し、メモリ管理を自動化します。

MyElementFactory(FB)
{attribute 'enable_dynamic_creation'}
FUNCTION_BLOCK MyElementFactory EXTENDS FBF.FactoryBase
VAR
END_VAR
SUPER^();

プログラムでの利用

以下は、PLC_PRGMyElementFactoryを利用してリストに要素を追加する例です。動的に生成したElementとStaticに生成したElementをListに追加しています。異なるデータ型をListに追加できるため、PythonのListのように利用できます。

PLC_PRG
PROGRAM PLC_PRG
VAR
	IntElementFactory : COL.IntElementFactory;
	itfIntElementOnHeapMemory : COL.IIntElement := IntElementFactory.Create(iValue := 100);
	intElementStatic : COL.IntElement := (iValue := 101);
	MyElementFactory : MyElementFactory;
	itfMyElement : IMyElement := MyElementFactory.Create(iValue1 := 299, iValue2 := 733);
	myElement : MyElement := ( iInt1:= 776, iInt2 := -23);
	aElements : ARRAY[0..9] OF COL.IElement;
	list : COL.List := (paElements := ADR(aElements), udiMaxElements := 10);
	xFirst : BOOL := TRUE;
END_VAR

// リストに要素を追加
IF xFirst THEN
	// Add elements to the list.
	list.AddElement(itfElement := itfIntElementOnHeapMemory);
	list.AddElement(itfElement := intElementStatic);
	list.AddElement(itfElement := itfMyElement);
	list.AddElement(itfElement := myElement);
	xFirst := FALSE;
END_IF

4. SimpleHashTableExample

このExampleでは、ハッシュテーブルを作成し、Key-Valueペアを管理します。FactoryでHashTableのインスタンスを生成し、インターフェース(ここでは、IMap)に設定し、IMap型の変数でKeyValueの追加やKeyでの検索を行います。

ハッシュテーブルの生成と操作

以下は、ハッシュテーブルの生成とKey-Valueペアの追加、検索の例です。KeyやValueで使う値はElementを生成するFactoryからインスタンスを生成して使用します。

PLC_PRG
PROGRAM PLC_PRG
VAR
	HashTableFactory : COL.HashTableFactory; 
	itfMap : COL.IMap := HashTableFactory.Create(udiMaxElements := 100);
	StringElementFactory : COL.StringElementFactory;
	
	state : ActionState := ActionState.IDLE;
	sKey : STRING := 'Enter a unique key';
	sValue : STRING := 'Enter a value';
	sKeyFind : STRING;
	itfStringKey : COL.IStringElement;
	itfIInstance : FBF.IInstance;
	sValueFound : STRING;
	eError : COL.COLLECTION_ERROR;
	aStringValues : ARRAY[0..99] OF STRING;
	iterator : COL.ListIterator;
	itfElement : COL.IElement;
	itfStringElement : COL.IStringElement;
	i : INT;
	xResult: BOOL;
END_VAR

CASE state OF
	ActionState.IDLE:
		// Idle
	ActionState.ADD_ELEMENT:
		// Add key value pair
		eError := itfMap.AddKeyValuePair(itfKey := StringElementFactory.Create(sValue := sKey), itfValue := StringElementFactory.Create(sValue := sValue));
		IF eError = COL.COLLECTION_ERROR.NO_ERROR THEN
			state := ActionState.UPDATE_VISU;
		ELSE
			state := ActionState.IDLE;
		END_IF;
		
	ActionState.FIND_ELEMENT_BY_KEY:
		// Finds a value by key
		itfStringKey := StringElementFactory.Create(sValue := sKeyFind);
		eError := itfMap.GetElementByKey(itfKey := itfStringKey, itfValue => itfElement);
		xResult := __QUERYINTERFACE(itfStringKey, itfIInstance);
		IF xResult THEN
			// Remove the temporary key
			itfIInstance.Dispose();
		END_IF
		
		// Set the found value
		sValueFound := '';
		xResult := __QUERYINTERFACE(itfElement, itfStringElement);
		IF xResult THEN
			sValueFound := itfStringElement.StringValue;
		END_IF
		IF eError = COL.COLLECTION_ERROR.NO_ERROR THEN
			state := ActionState.UPDATE_VISU;
		ELSE
			state := ActionState.IDLE;
		END_IF;
		
	ActionState.UPDATE_VISU:
		// Clear visu array
		FOR i := 0 TO 9 DO
			aStringValues[i] := '';
		END_FOR
		eError := itfMap.Keys(itfIterator := iterator);
		i := 0;
		WHILE iterator.HasNext() DO
			iterator.Next(itfElement => itfElement);
			// Cast IElement to IStringElement
			__QUERYINTERFACE(itfElement, itfStringElement);
			aStringValues[i] := itfStringElement.StringValue;
			i := i + 1;
		END_WHILE
		state := ActionState.IDLE;
END_CASE

学びと気づき

  • 動的データ構造の使い方:リストやハッシュテーブルなどを動的に生成して管理する方法を習得。
  • インターフェースの活用:汎用性を持たせるプログラミング設計の重要性を理解。
  • リソース管理の重要性Dispose()を適切に使用してリソースを解放する必要性を再確認。
  • 属性値の設定: {attribute 'enable_dynamic_creation'}など属性値の設定はあまり調べられていないので今後の課題。

まとめ

COLライブラリを使用する際の一般的な手順は以下の通りです:

  1. ファクトリによる生成

    • コンテナ(リストやハッシュテーブル)や要素をFactoryで生成。
  2. インターフェースを利用した操作

    • コンテナや要素を操作するための抽象化されたインターフェースを使用。
  3. イテレーターで要素を巡回

    • ListIteratorを用いてリストやハッシュテーブルの要素を順に処理。
  4. __QUERYINTERFACE()によるデータ型の確認

    • __QUERYINTERFACEで、データ型のElementがIIntElementなど、より具体的なデータ型のインターフェースを持っているか確認して処理を行う。

Discussion