高度な機能

カスタムフラグメント

Things Cloud APIを使用すると、データを自由に構築できます。Apama EPLでは、paramsにエントリを追加することで行われ、これらはdictionary<string, any>型となります。com.apama.cumulocityパッケージの各Things Cloudイベント(Alarm、Event、Measurement、Operationなど)にはparamsフィールドがあり、これはフラグメントまたは任意のフィールドに変換されます。したがって、イベントを受信するとき、コードはparamsフィールドのエントリを検索する必要があります。イベントを送信するときは、イベントタイプを定義するか、またはdictionary <string、any>タイプを使用します。イベントを受信する場合、EPLの型は dictionary <any、any>になります。EPLは強く型付けされているため、フラグメントのないイベントを作成する場合は、new dictionary<string, any>を使用する必要があることに注意してください。ディクショナリーリテラルとインラインでエントリを提供している場合、EPLは最初のキーバリューペアの型に基づいて型を決定します。したがって、 dictionary<string, any>の場合、<any>キャスト演算子を使用して、最初の値を anyタイプにキャストします:

send Event(..., new dictionary<string,any>) to Event.SEND_CHANNEL;
send Event(..., {"fragment":<any>"value"}) to Event.SEND_CHANNEL;

MeasurementValue型は、Measurement型のメジャーメント用に提供されています。MeasurementValueには、valueフィールドとunitフィールド、および他のフラグメント用のparams があります。

例 1:

send Measurement("", "c8y_TemperatureMeasurement", "12345", currentTime, {
	"c8y_TemperatureMeasurement":{
		"T1":MeasurementValue(1.0, "C", new dictionary<string,any>),
		"T2":MeasurementValue(2.0, "C", new dictionary<string,any>),
		"T3":MeasurementValue(3.0, "C", new dictionary<string,any>),
		"T4":MeasurementValue(4.0, "C", new dictionary<string,any>),
		"T5":MeasurementValue(5.0, "C", new dictionary<string,any>)
	}
},
new dictionary<string,any>) to Measurement.SEND_CHANNEL;

これにより、次のJSON構造が作成されます:

{
	"type": "c8y_TemperatureMeasurement",
	"time": "...",
	"source": {
		"id": "12345"
	},
	"c8y_TemperatureMeasurement": {
		"T1": {
			"value": 1,
			"unit": "C"
		},
		"T2": {
			"value": 2,
			"unit": "C"
		},
		"T3": {
			"value": 3,
			"unit": "C"
		},
		"T4": {
			"value": 4,
			"unit": "C"
		},
		"T5": {
			"value": 5,
			"unit": "C"
		},
	}
}

メジャーメントフラグメント

メジャーメントは、個々のメジャーメントフラグメントに分割できます。これは、メジャーメントに存在するフラグメントとシリーズごとに実行できます。メジャーメントフラグメント詳細については、コンセプトガイドThings Cloudのドメインモデルを参照してください。

メジャーメントフラグメントまたはシリーズに基づくフィルタリングが必要な場合は、Measurementイベントをリッスンしてmeasurementsディクショナリー内を調べる代わりに、com.apama.cumulocity.MeasurementFragment型のイベントをリッスンします。詳細については、Apamaドキュメントの Using measurement fragments を参照してください。

リスナー

受信するイベントのみがステートメントのトリガーとなるわけではありません。次のセクションではリスナーを組み合わせる方法について説明します。詳細については、Apamaドキュメント内の Defining Event Listenersをご覧ください。

フィルター

フィルターを使用すると、他のトリガーの組み合わせまたはシーケンスでトリガーさせることができます。例えば、次のようなトリガーがあるとします:

on all Event() as e { ... }

パターンにフィルターを追加することができます:

on all Event(type = "c8y_EntranceEvent") as e { ... }

複数のイベントをリッスンできます:

on Event() as e and Alarm() as a { ... }

これは、イベントとアラームのイベント受信時にトリガーされます。 それぞれの最初のイベントがキャプチャされます。

シーケンスでトリガーさせることもできます:

on all (Event() as e -> Alarm() as a) { ... }

これは、「アラームが後に続くイベント」のペアごとにトリガーされます。イベントを受信すると、それ以降のイベントのリッスンを停止し、代わりにアラームのリッスンを開始します。アラームが受信されると、イベントのリッスンが再び開始されます。

タイマー

時間に基づいてリスナーをトリガーすることもできます。例えば、5分(300秒)ごとに起動するなど、特定の間隔でトリガーすることができます:

on all wait(300.0) { ... }

または、Unix cron スケジュラーと同様の機能を使用して、1日の特定の時間にリスナーを起動できます:

// timer:at(minutes, hours, daysOfMonth, month, daysOfWeek, (optional) seconds)
// minutes: 0-59
// hours: 0-23
// daysOfMonth: 1-31
// month: 1-12
// daysOfWeek: 0 (Sunday) - 6 (Saturday)
// seconds: 0-59

on all at(*, *, *, *, *) {} // trigger every minute

on all at(*/10, *, *, *, *) {} // trigger every 10 minutes
on all at(0, 1, *, *, [1,3,5]) {} // trigger at 1am every monday, wednesday and friday
on all at(0, */2, (1-7), *, *) {} // trigger every 2 hours on every day in the first week of every month

タイマーパターンを他のパターンと組み合わせることもできます。たとえば、別のイベントの後、特定の時間内にイベントがあったかどうかを確認できます:

on Event() -> wait(600.0) and not Alarm() { ... }

これは、イベントがあり、10分(600秒)以内にアラームがない場合にトリガーされます。not で指定されるイベントが発生した場合、リスナーが終了することに注意してください。

テナントオプションを使用すると、on all at タイマーに使用するタイムゾーンを設定できます。テナントオプションを設定するには、カテゴリにmicroservice.runtimeを、 キーにtimezoneを指定します。 例:

{
    "category" : "microservice.runtime",
    "key" : "timezone",
    "value" : "Europe/Warsaw"
}

また、Apamaドキュメントのサポートされているタイムゾーンも参照してください。

備考
このテナントオプションは、マイクロサービスの起動時に限って読み込まれます。テナントオプションが変更された場合、マイクロサービスは次のマイクロサービス登録時にこれを読み取ります。

ストリーム - ウィンドウ

ストリームを使用すると、イベントのウィンドウを複数操作できます。ストリームはonの代わりにfromキーワードを使用し、操作するウィンドウを定義し、集計を使用してそのウィンドウから出力を選択します。ウィンドウは2つの方法で制限できます:

  1. 一定期間のウィンドウ - withinキーワードを使用します。

    from m in all Measurement(type="c8y_TemperatureMeasurement") within 3600.0 select avg(m.measurements	["c8y_TemperatureMeasurement"]["T"].value) as avgValue { }
    
  2. 一定量のイベントを持つウィンドウ - retain キーワードを使用します。

    from m in all Measurement(type="c8y_TemperatureMeasurement") retain 100 select avg(m.measurements["c8y_TemperatureMeasurement"]["T"].value) as avgValue { }
    

ストリーム - 定期的な出力

ストリームは、every 指定子を使用して、評価する頻度を制御することもできます。

// will output the last measurement arrived every 1 minute
from m in all Measurement(type="c8y_TemperatureMeasurement") within 60.0 every 60.0 select last(m.measurements["c8y_TemperatureMeasurement"]["T"].value) as lastValue { }

// will output the first of every 20 measurements arriving
from m in all Measurement(type="c8y_TemperatureMeasurement") retain 20 every 20 select first(m.measurements["c8y_TemperatureMeasurement"]["T"].value) as firstValue { }

// will output the average of all 20 measurements after the 20th arrived
from m in all Measurement(type="c8y_TemperatureMeasurement") retain 20 every 20 select avg(m.measurements["c8y_TemperatureMeasurement"]["T"].value) as avgValue { }

Apama ドキュメントの built-in aggregate functions をご覧ください。

独自のイベント型の作成

事前定義されたイベント型に加えて、独自のイベント型を定義できます。これらは、同じモジュールの他の部分をトリガーするイベントの発生パターンを検出するのに役立ちます。

event MyEvent {
	Measurement m1;
	Measurement m2;
}

...

on Measurement() as m1 -> Measurement() as m2 {
	route MyEvent(m1, m2);
}
備考
Things Cloudは各モジュールを独自のネームスペースにデプロイするため、あるモジュールのイベント定義を他のモジュールで使用することはできません。これにより、モジュール間の依存を防いでいます。

独自アクションの作成

通常、次の例に示すように、(Javaの関数によく似た)アクションを使用してモニターを構成します。

指定された重大度を上げる:

action upgradeSeverity(string old) returns string {
	if old = "WARNING" { return "MINOR"; }
	if old = "MINOR"   { return "MAJOR"; }
	if old = "MAJOR"   { return "CRITICAL"; }
	return old;
}

2つの地理座標間の距離を計算する:

action distance(float lat1, float lon1, float lat2, float lon2) returns float {
	float R := 6371000.0;
	float toRad := float.PI / 180.0;
	float lat1Rad := lat1 * toRad;
	float lat2Rad := lat2 * toRad;
	float deltaLatRad := (lat2-lat1) * toRad;
	float deltaLonRad := (lat2-lat1) * toRad;
	float a := (deltaLatRad/2.0).sin().pow(2.0) * lat1Rad.cos() * lat2Rad.cos() * (deltaLonRad/2.0).sin().pow(2.0);
	float c := 2.0 * a.sqrt().atan2((1.0-a).sqrt());
	return R * c;
}

変数

モジュールで変数を定義できます。

string myEmailText := "Hello World";
sequence<string> supportedOperationsList := ["c8y_Restart", "c8y_Relay"];

モニタースコープ変数を定義する場合(つまり、モニター内であるが、モニターのどのアクション内にも存在しない場合)、リスナーでのイベント共同割り当ての際、asの代わりにコロン(:)を使用すると、リスナーで使用できます。 次の例では、10秒ごとに最新のイベントを記録します:

monitor MyMonitor {
    // monitor scope:
    Event e;
    action onload() {
        monitor.subscribe(Measurement.SUBSCRIBE_CHANNEL);
        on all Event():e {}
        on all wait(10.0) {
            log e.toString();
        }
    }
}

リスナーが開始されると、すべてのローカル変数のコピーが取得されます。 次の例では、他のイベントが間に挟まれても、10秒遅れて各イベントを記録します。

monitor MyMonitor {
    // monitor scope:
    Event e;
    action onload() {
      monitor.subscribe(Measurement.SUBSCRIBE_CHANNEL);
      on all Event():e {
            on all wait(10.0) {
                log e.toString();
            }
        }
    }
}

モニターインスタンスとコンテキストの生成

1つのモニターで複数のデバイスを処理することは可能ですが(たとえば、ストリームでgroup bypartition byを使用したり、他の状態のデバイスIDにキー入力されたディクショナリーを維持したり)異なるデバイスの処理を複数のモニターインスタンスに分割すると便利なケースがあります。

新しいモニターインスタンスは、 spawnステートメントを使用して作成できます。これにより、モニターのモニタースコープ変数のコピーが取得され、新しいモニターインスタンスで名前付きアクションが実行されます。新しいモニターにリスナーはコピーされません。また、新しいモニターインスタンスを生成するコンテキストを指定することもできます。異なるコンテキストを同時に実行でき、また、異なるモニターを相互に分離することもできます。コンテキストを構築するとき、コンテキストを識別する名前、およびコンテキストがパブリックであるかどうかを制御するブール値を指定します。つまり、デフォルトでThings Cloudのイベントを受信します(デフォルトチャネルに送信されます)。

このパターンは、多くの場合、unmatchedキーワードとともに使用され、そのコンテキスト内の他のリスナーと一致しないイベントを識別します。各モニターに個別のコンテキストを使用することにより、対象の振る舞いはそのモニターのスコープに限定されます。例えば:

monitor PerDeviceMeasurementTracker {
	action onload() {
		spawn factory to context("PerDeviceMeasurementTracker", true);
	}
	action factory() {
		monitor.subscribe(Measurement.SUBSCRIBE_CHANNEL);
		on all unmatched Measurement() as m {
			spawn perDevice(m);
		}
	}

	dictionary<string, Measurement> latestMeasurementByType; // measurements for this device

	action perDevice(Measurement m) {
		processMeasurement(m);
		on all Measurement(source = m.source) as m {
			processMeasurement(m);
		}
	}
	action processMeasurement(Measurement m) {
		latestMeasurementByType[m.type] := m;
	}
}

ストリーミング分析アプリケーションのアクセス制御

デフォルトでは、ストリーミング分析アプリケーションからAnalytics BuilderおよびEPL Appsページにアクセスできます。 管理者は、異なるテナントまたは異なるユーザーに対してどのページを表示するかを制御したり、ホーム画面のカードの文言を変更したりすることができます(ストリーミング分析アプリケーションのホーム画面のカスタマイズも参照してください)。

どのページが利用可能かは、実行されているApama-ctrlマイクロサービスの種類によっても異なります。

テナント内に feature-disable-analyticsbuilder もしくは feature-disable-eplapps という名前の「機能アプリケーション」が存在する場合、テナント全体に対して該当の機能が無効になります。 これはテナント内で登録することも、親テナントからサブテナントに登録することもできます(親テナントから機能へのアクセスを制限する場合、サブテナントの管理者はこのアプリケーションの登録を解除できません)。 テナント内でこのような「機能アプリケーション」を作成するには、/application/applicationsにPOSTリクエストを送信します(アプリケーションを作成する権限を持つ管理者が実施)。

Analytics Builderを無効にする場合:

{
   "name":"feature-disable-analyticsbuilder",
   "contextPath": "feature-disable-analyticsbuilder",
   "type":"HOSTED",
   "resourcesUrl":"/",
   "manifest": {
       "noAppSwitcher": true
   },
   "key":"feature-disable-analyticsbuilder-key"
}

EPL Appsを無効にする場合:

{
   "name":"feature-disable-eplapps",
   "contextPath": "feature-disable-eplapps",
   "type":"HOSTED",
   "resourcesUrl":"/",
   "manifest": {
       "noAppSwitcher": true
   },
   "key":"feature-disable-eplapps-key"
}

curl コマンドを使用して POST リクエストを送信することもできます。

Analytics Builderを無効にする場合:

curl --user username -X POST -H 'Content-Type: application/json' -d '{"name":"feature-disable-analyticsbuilder", "contextPath": "feature-disable-analyticsbuilder", "type":"HOSTED", "resourcesUrl":"/","manifest": {"noAppSwitcher": true},"key":"feature-disable-analyticsbuilder-key"}' -k https://{{hostname}}/application/applications/

EPL Appsを無効にする場合:

curl --user username -X POST -H 'Content-Type: application/json' -d '{"name":"feature-disable-eplapps", "contextPath": "feature-disable-eplapps", "type":"HOSTED", "resourcesUrl":"/", "manifest": {"noAppSwitcher": true},"key":"feature-disable-eplapps-key"}' -k https://{{hostname}}/application/applications/

デフォルトでは、すべてのユーザが同じページ一覧を見ることができます(上記の制限に従う)。 また、ROLE_ANALYTICSBUILDER_READ または ROLE_EPLAPPS_READ の権限を持つユーザーのみにページの閲覧を制限することもできます。 これらの権限は、ユーザーに直接割り当てる、もしくはグループを介して割り当てることができます(ユーザーガイド管理 > 権限の管理 も参照してください)。 これを有効にするには、テナントオプションのカテゴリ streaminganalytics を設定し、applicationAccess キーを “role” に設定します (Things Cloud OpenAPI仕様のTenant APIも参照)。

curlコマンドの利用例:

curl --user username -X POST -H 'Content-Type: application/json' -d '{"category": "streaminganalytics", "key": "applicationAccess", "value": "role"}' -k https://mytenant/tenant/options

上記利用例のユーザー名は、「オプション管理」のADMIN権限を持つユーザー名に置き換える必要があります。

これは、ストリーミング分析アプリケーションのカードとページの可視性にのみ影響することに注意してください。 サポートされるRESTサービスは、「CEP 管理」の読み取りとおよび管理者権限のみを必要とします。

ストリーミング分析アプリケーションのホーム画面のカスタマイズ

ストリーミング分析アプリケーションのホーム画面に表示されるカードには、テナントごと、言語ごとにカスタマイズできるテキストとリンクが含まれています。これを行うには、URL /service/cep/apamacorrelator/EN/documentation.jsonからカスタマイズしたい言語のdocumentation.jsonファイルをダウンロードします(Things Cloudテナントのユーザで認証する必要があります)。URL内の “EN” をダウンロードしたいファイルの言語コードに置き換えてください。

documentation.jsonファイルには、ストリーミング分析アプリケーション全体のドキュメントリンクのURLと、ホーム画面に表示されるテキストが含まれています。これは、お客様の要件に合わせて変更することができます。

必要な変更をすべて行ったら、変更したコピーを含めた以下ファイル群をZIPファイルにパッケージします:

cumulocity.jsonファイルには、以下の内容が含まれます:

{ 
   "contextPath": "streaminganalytics-customization", 
   "availability": "MARKET", 
   "type": "HOSTED", 
   "name": "streaminganalytics-customization", 
   "key": "streaminganalytics-customization-key", 
   "noAppSwitcher": true 
}

次に、管理アプリケーションを使用してZIPファイルをアップロードします。手順は、エコシステム > アプリケーション に進み、アプリケーションを追加 をクリックし、Webアプリケーションをアップロード を選択します。 詳細については、ユーザーガイド管理 > アプリケーションの管理を参照してください。

変更を有効にするには、ブラウザのキャッシュクリアが必要な場合があります。

必要に応じて、1つのZIPファイルに複数の言語を含めることができます。また、必要に応じて親テナントからサブテナントに登録できます。

プラットフォームのアップグレード時は、documentation.jsonのソースファイルに変更がないか確認することをお勧めします。 このファイルの新しい項目は、デフォルト値でピックアップされます。

備考
Things Cloudの各テナントには、アプリケーションにあらかじめ streaminganalytics-customization が登録されています。 上記手順を実施することで、既存の設定内容を上書きすることが可能です。 (デフォルトで登録されている streaminganalytics-customization を削除することはできません。)