これは、OMRON社のNXコントローラ向けのSupabase client(APIクライアントサービス)ライブラリです。 このライブラリは、Database、Edge functions及びGraphQLのSupabase REST API呼び出し機能を提供します。 第三者向けのプロダクトレベルの品質ではありませんが、内部的な使用は可能です。 "NXからSupabaseを使う"に幾らか付加的な情報があります。
ユーザーはSupabase REST APIを、以下のようなコードで呼び出します。 fetch、resolved、rejectedという単語で予想がつくと思います。
100:
// クエリの生成。
SupabaseQuery_init(iQuery);
SPQ_FROM('production_monitor', iQuery);
SPQ_BULK_INSERT(iRows, 0, iRowSize, 'text/csv', iQuery);
// Fetchの生成。
SupabaseFetch_new(
Context:=iFetchContext,
EndpointName:=SUPABASE_ENDPOINT_NAME,
Query:=iQuery);
Inc(iState);
101:
CASE Supabase_fetch(iFetchContext) OF
ATS_RESOLVED:
SupabaseFetch_getStatusCode(
Context:=iFetchContext,
StatusCode=>iStatusCode);
iState := iReturnState;
ATS_REJECTED:
SupabaseFetch_getStatusCode(
Context:=iFetchContext,
StatusCode=>iStatusCode);
SupabaseFetch_getResponseBodyAsStr(
Context:=iFetchContext,
Body=>iRespBody);
SupabaseFetch_getError(
Context:=iFetchContext,
Error=>iError,
ErrorID=>iErrorID,
ErrorIDEx=>iErrorIDEx);
iState := iReturnState;
END_CASE;
上記のコードはAPI呼び出し要求を行うだけです。 別途、API呼び出し処理を行うPOUをあらかじめ実行しておく必要があります。 以下のようなAPI呼び出し処理を行うPOUを含むプログラムを実行します。 POUにシークレット情報を配置するのであれば、別途パスワード保護をします。
CASE iState OF
// STATE_INIT
0:
// 設定初期化。
InitSupabaseClientServiceSettings(
// 排他制御キー。
LockKey:=17);
// Supabaseエンドポイントの登録。
RegisterSupabaseEndpoint(
// エンドポイントを識別する名称。
Name:='MachineInfo',
// Supabaseのドメイン。
Domain:='YOUR_PROJECT_ID.supabase.co',
// Supabaseのanonキー。
ApiKey:='YOUR_ANON_KEY');
// 無制約TLSセッションの登録。
RegisterUnrestrictedTlsSession(
TlsSessionName:='TLSSession0');
RegisterUnrestrictedTlsSession(
TlsSessionName:='TLSSession1');
Inc(iState);
1:
IF iCtrlReload THEN
ReloadSupabaseClientService();
iCtrlReload := FALSE;
ELSE
EnableSupabaseClientService();
iService.Enable := TRUE;
END_IF;
iState := STATE_ACTIVE;
// STATE_ACTIVE
10:
// オンラインによる手動操作用。
IF iCtrlEnable THEN
EnableSupabaseClientService();
iCtrlEnable := FALSE;
ELSIF iCtrlDisable THEN
DisableSupabaseClientService();
iCtrlDisable := FALSE;
ELSIF iCtrlReload THEN
iState := STATE_INIT;
END_IF;
END_CASE;
iService();
サンプルプロジェクトを編集してコントローラで実行すると、Supabaseのテーブルに以下のようにレコードを作成します。 このテーブルは装置の生産状態を保持します。
APIクライアントサービスには、以下の制約があります。
- HTTPプロキシ環境では使用できない
APIクライアントサービスは、以下のSupabase REST APIを使用できます。
- Database
- Edge functions
- GraphQL
このプロジェクトの使用には、以下の環境が必要です。
| コントローラ | NX1またはNX5 |
| Sysmac Studio | 最新版を推奨。 |
| ネットワーク | インターネット接続が可能であること。 |
このプロジェクトは、以下の環境で構築しました。
| コントローラ | NX102-9000 Ver 1.64 |
| Sysmac Studio | Ver.1.62 |
サンプルプロジェクトの実行には、Supabase、サンプルプロジェクト、コントローラのそれぞれについて作業が必要です。 Supabaseは予めアカウントを作成し、OrganizationとProjectを作成しておきます。
サンプルプロジェクトはDatabase操作を対象としたバックエンドだけです。 フロントエンドはありません。 サンプルプロジェクトは、以下のテーブルを操作します。 RLSを有効にして簡潔なサービスキーによるアクセス制限を行います。
- production_monitor : 生産モニタ 生産情報を保持します。
- production_task : 生産タスク 生産タスクを保持します。
サンプルプロジェクトの使用には、最終的に以下の情報が必要です。
- Supabaseのドメイン
- Supabaseのanonキー
- 使用コントローラの型式
- コントローラをインターネット接続するための設定
- コントローラのセキュアソケットのセッションNo
作業は以下の手順で行います。
- Supabaseのセットアップ
- anonキーの確認
- サンプルプロジェクトを使用環境に合わせる
- サンプルプロジェクトのAPIクライアントサービスの設定を変更
- コントローラのセキュアソケット設定にTLSセッションを登録
- コントローラにサンプルプロジェクトを転送
リポジトリのセットアップSQLをSupabaseのSQL Editorで実行します。
セットアップSQLをSQL EditorにコピーしたらDECLAREステートメントのinitial_service_keyを適当な値に変更します。
HTTPリクエストのヘッダーとしてhttp_header_keyとinitial_service_keyの値のペアを設定することでテーブルへのCRUD操作を行えるようになります。
サービスキーによるアクセス制限です。
DECLARE
http_header_key TEXT := 'x-service-key';
initial_service_key TEXT := 'mykey';
service_keys_table TEXT := 'service_keys';
production_monitor_table TEXT := 'production_monitor';
production_task_table TEXT := 'production_task';上記であれば、x-service-key: mykeyをHTTPヘッダーに設定することで、操作が行えるようになります。
セットアップSQLは、関連要素のクリーンアップを行うので問題が生じたら再実行することで初期状態にすることができます。
詳細はセットアップSQLを確認してください。
SupabaseのSettings/API Keysでanonキーを確認して控えます。
サンプルプロジェクトを使用環境に合わせます。以下の変更が必要です。
- コントローラの型式
使用するコントローラの型式に変更します。 - コントローラのネットワーク設定
使用環境でインターネット接続可能な設定とします。DNSは特別な理由が無ければ、"1.1.1.1"のようなパブリックDNSを使用します。
POU/プログラム/SupabaseClientServiceRunnerを編集します。
各RegisterSupabaseEndpointの引数をSupabaseの環境に合わせます。
Supabaseのドメイン、APIキー(anonキー)を変更します。
YOUR_SUPABASE_DOMAINをSupabaseのプロジェクトIDを含むドメインで、YOUR_ANON_KEY を2で控えたanonキーで置き換えます。
// Supabaseエンドポイントの登録。
RegisterSupabaseEndpoint(
// エンドポイントを識別する名称。
Name:='MachineInfo',
// Supabaseのドメイン。
Domain:='YOUR_SUPABASE_DOMAIN',
// Supabaseのanonキー。
ApiKey:='YOUR_ANON_KEY');
TLSセッションは、変更の理由が無ければ以下のままにします。
RegisterUnrestrictedTlsSession(
TlsSessionName:='TLSSession0');
RegisterUnrestrictedTlsSession(
TlsSessionName:='TLSSession1');
次に、POU/ファンクション/SetServiceCredentialToSupabaseQueryを編集します。サービスキーを1で設定した値にします。変更していなければ、以下のままにします。
SupabaseQuery_setHeader(
Context:=Query,
Key:='x-service-key',
Value:='mykey');
このPOUは、APIクライアントサービスに含むものではありませんが、追加のアクセス制限として各プログラムで使用しています。
コントローラに接続、プログラムモードに変更してセキュアソケット設定に4で指定したNoと同じNoのセッションを登録します。 変更していなければ、IDが0と1のセッションを作成します。以下のように作業します。
コントローラにサンプルプロジェクトを転送し、運転モードに切り替えます。 ネットワークエラーが発生していないか確認します。 ネットワークエラーが発生している場合、エラー原因を取り除きます。
エラーが発生した場合、以下の可能性があります。
- TLSセッションIDとTLSセッション名の不一致 4で指定したTLSセッション名の番号と5で指定したTLSセッションIDが一致していることを確認します。
- ドメインの不一致 4で指定した登録しドメインが一致していることを確認します。
- APIキーの不一致 4で指定したAPIキーがSupabaseのAPIキーに一致ていることを確認します。
- インターネットに接続できないか、名前解決ができない
Supabaseドメインまでのルートをtracertで確認します。 - Supabaseで障害が発生している Supabase Statusを確認します。
各Sysmacプロジェクト、ライブラリについては以下の通りです。
APIクライアントサービス開発用のSyamacプロジェクトです。 APIクライアントサービスの実装を確認することができます。
APIクライアントサービスのライブラリです。 APIクライアントサービスのライブラリは、依存ライブラリを含んでいません。 ライブラリを使用する場合、lib\の依存ライブラリも合わせて参照する必要があります。
既存プロジェクトでAPIクライアントサービスを使用する手順は以下です。ライブラリ操作を伴うため、必ず既存プロジェクトのコピーを作成します。
- プロジェクトでAPIクライアントサービスのライブラリと依存ライブラリを参照
リポジトリのlib/が依存ライブラリです。 - サービス用変数をグローバル変数に登録
gSupabaseClientServiceSingleton : SupabaseClientServiceSingletonContextを定義します。 - サービスランナー(SupabaseClientSingletonService FBを実行するプログラム)の作成
サンプルプロジェクトを参考にしてください。 - サービスランナーをタスクに追加
サービスランナーは、API呼び出し処理を実行するので、適切なタスク時間のタスクに登録します。プライマリタスクは不適です。 - ビルドしてエラーが無いことを確認
ライブラリの不足やグローバル変数定義に誤りがあればエラーが出ます。 - メモリ使用状況を確認
メモリ使用状況に変更が反映されているか確認します。
使用環境が整ったらAPIクライアントサービスを使用するプログラム、Supabaseプロジェクトを作成し、サービスランナーに必要な情報を記述します。 大きなプロジェクトに対してこれらの操作を行うと時間がかかります。 可能であれば小さなプロジェクトで必要な機能を開発し、動作テストまで済ませてから必要とするプロジェクトに統合することを検討してください。
APIクライアントサービスのサンプルプロジェクトです。 APIクライアントサービスの動作を確認することができます。 必要とするライブラリは全てバンドルしています。
Fetch POUは、APIクライアントサービスによるSupabase REST API呼び出しを簡潔にするための一連のヘルパーPOUです。 各操作はAPI呼び出し単位であるFetchに関連する要素を集約したSupabaseRestApiFetchContext構造体を介して行います。 Fetchを使用したAPI呼び出し手続きは以下です。
- Fetchを生成する
- Fetchを実行・監視する
この手続きの概形は以下のようになります。
NEW_FETCH:
GenerateSupabaseQuery(
Query:=iQuery,
...);
Supabase_newFetch(
Context:=iFetchContext,
EndpointName:=SUPABASE_ENDPOINT_NAME,
Query:=iQuery,
...);
iState := DO_FETCH;
DO_FETCH:
CASE Supabase_fetch(iFetchContext) OF
ATS_RESOLVED:
iState := DONE;
ATS_REJECTED:
SupabaseFetch_getError(
Context:=iFetchContext,
Error=>iError,
ErrorID=>iErrorID,
ErrorIDEx=>iErrorIDEx);
iState := DONE;
END_CASE;
NEW_FETCH節でAPI呼び出しのクエリを生成し、それを引数としてFetchを生成します。 Fetchを生成したら処理をDO_FETCH節に移し、Fetchを毎サイクル監視して成否が返ってきたら完了します。
Fetchの生成は、APIクライアントサービスのリソースを消費しません。 そのため、フォールバックとして障害時にFetchを蓄積し、復旧したら実行するということもできます。 但し、APIクライアントサービスのリロードを行うとそれ以前に生成したFetchは実行しても失敗します。
生成したFetchはそれを実行・監視するPOUに渡して処理を実行します。 実行・監視POUは以下です。
-
Supabase_fetch
Fetchの状態を戻り値で列挙値(ATS_PENDING : 処理中、ATS_RESOLVED : 成功、ATS_REJECTED : 失敗)として返します。 CASE文と合わせて使用します。 -
Supabase_fetchSignals
Fetchの状態をBOOL値(Pending : 処理中、Resolved : 成功、Rejected : 失敗)として出力します。 LDで使用します。
ユーザーは、これらの実行・制御POUが成否を返すまで実行します。 API呼び出し処理はAPIクライアンサービス内の処理となるため、実行・制御POUは各サイクル、あるいは、レスポンスを処理可能なサイクルで1回だけ実行します。 単一サイクルで複数回実行しても問題ありませんが、処理時間が短縮されるわけではありません。
実行・監視POUが成否を返すとき、内部的にレスポンスの複製を行います。 そのため、他の処理でタスク時間が厳しい場合や、複数のFetchを同時使用してそれらが同時に完了した場合、タスクタイムオーバーとなる可能性があります。 Fetchの監視は毎サイクル必要なわけではありません。レスポンスの処理が可能なサイクルで実行すれば十分です。
一度でも実行・監視POUで処理を実行したFetchは、必ず成否を確認する必要があります。 実行・監視POUは成否を返す際に、APIクライアントサービスのタスクを解放します。 そのため、成否を確認しない限りタスクリソースは占有されたままとなります。 任意に実行するPOUでのFetchの使用には注意が必要です。 また、Fetchは実行・監視POUが成否を返した時点で再実行可能な状態になります。 再実行の意図が無い場合、以後はそのFetchを実行・監視POUで実行しないようにします。
成否が確定したFetchはその内容を取得して処理することになります。 以下のPOUでFetchから必要とする値を取得します。
- SupabaseFetch_getError
Fetchのエラー情報を取得します。 - SupabaseFetch_getStatusCode
Fetchのレスポンスステータスコードを取得します。 - SupabaseFetch_getResponseHeader
Fetchのレスポンスヘッダーを取得します。 - SupabaseFetch_getResponseBody
Fetchのレスポンスボディをバイト列として取得します。 - SupabaseFetch_getResponseBodyAsStr
Fetchのレスポンスボディを文字列として取得します。
最後にFetchの状態遷移です。 Fetchの状態は、手続きに従って以下のように遷移します。
stateDiagram-v2
[*] --> UNDEFINED : Supabase_newFetch()
UNDEFINED --> run : Supabase_fetch()
state run {
state settled <<choice>>
[*] --> PENDING : The task created in the service
PENDING --> settled : The task done
settled --> RESOLVED : success
settled --> REJECTED : failure
RESOLVED --> [*]
REJECTED --> [*]
}
クエリは、Fetchの生成に必要な対象テーブルや操作を集約する構造体(SupabaseQuery)です。 クエリの構築には、以下のQuery POUを使用します。
| POU | 機能 |
|---|---|
| SPQ_SCHEMA | スキーマを指定します。 |
| SPQ_FROM | テーブルを指定します。 |
| SPQ_SELECT | レコード取得を指定します。 |
| SPQ_INSERT | 単一レコード作成を指定します。 |
| SPQ_BULK_INSERT | 複数レコード作成を指定します。 |
| SPQ_UPDATE | レコード更新を指定します。 |
| SPQ_UPSERT | Upsertによるレコード更新を指定します。 |
| SPQ_SINGLE_UPSERT | Upsertによる単一レコード更新を指定します。 |
| SPQ_DELETE | レコード削除を指定します。 |
| SPQ_FILTER | フィルタを指定します。 |
| SPQ_PARAM | 任意のURLクエリパラメータを指定します。 |
| SPQ_RPC | PostgreSQL関数呼び出しを指定します。 |
| SPQ_FUNCTION | Edge functions呼び出しを指定します。 |
| SPQ_GRAPHQL | GraphQLを指定します。 |
| SupabaseQuery_addPreference | 任意のプリファレンスを追加します。 |
| SupabaseQuery_setHeader | 任意のHTTPヘッダーを設定します。 |
スキーマの指定は、SPQ_SCHEMAで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_SCHEMA('myschema', iQuery);
テーブルの指定は、SPQ_FROMで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FROM('mytable', iQuery);
Selectの指定は、データベースを指定した後にSPQ_SELECTで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FROM('client_test', iQuery);
SPQ_SELECT('*', iQuery);
SPQ_FILTER('limit', '3', iQuery);
Insertの指定は、データベースを指定した後にSPQ_INSERTで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FROM('client_test', iQuery);
SPQ_INSERT(iPayload, 0, iPayloadSize, iQuery);
// レスポンスとしてレコードを取得したい場合
SupabaseQuery_addPreference(
Context:=iQuery,
Preference:='return=representation');
ペイロードはJSON形式であることを前提にしていますが、CSV形式のペイロードを渡した後、SupabaseQuery_setHeaderでContent-Typeを置換し、CSV形式とすることもできます。
Bulk Insertの指定は、データベースを指定した後にSPQ_BULK_INSERTで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FROM('client_bulk_insert_test', iQuery);
SPQ_BULK_INSERT(iPayload, 0, iPayloadSize, 'application/json', iQuery);
// レスポンスとしてレコードを取得したい場合
SupabaseQuery_addPreference(
Context:=iQuery,
Preference:='return=representation');
ペイロードがCSV形式である場合は、以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FROM('client_bulk_insert_test', iQuery);
SPQ_BULK_INSERT(iPayload, 0, iPayloadSize, 'text/csv', iQuery);
// レスポンスとしてレコードを取得したい場合
SupabaseQuery_addPreference(
Context:=iQuery,
Preference:='return=representation');
Updateの指定は、データベースを指定した後にSPQ_UPDATEで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FROM('client_test', iQuery);
SPQ_UPDATE(iPayload, 0, iPayloadSize, iQuery);
SPQ_FILTER('id', 'lt.3', iQuery);
Upsertの指定は、データベースを指定した後にSPQ_UPSERTで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FROM('client_upsert_test', iQuery);
SPQ_UPSERT(iPayload, 0, iPayloadSize, 'application/json', iQuery);
ペイロードがCSV形式である場合は、以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FROM('client_upsert_test', iQuery);
SPQ_UPSERT(iPayload, 0, iPayloadSize, 'text/csv', iQuery);
単一レコードのUpsertの指定は、データベースを指定した後にSPQ_SINGLE_UPSERTで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FROM('client_upsert_test', iQuery);
SPQ_SINGLE_UPSERT(iPayload, 0, iPayloadSize, 'sku', 'MS0', iQuery);
ペイロードはJSON形式であることを前提にしていますが、CSV形式のペイロードを渡した後、SupabaseQuery_setHeaderでContent-Typeを置換し、CSV形式とすることもできます。
単一レコードのUpsertは、PostgRESTのPUTによるUpsertです。
フィルタは、SPQ_FILTERで指定します。以下のように使用します。
SPQ_FILTER('id', 'gt.3', iQuery);
オペレータのバリデーションのような検査、オペランドのSQLとしてのエスケープのようなサニタイズは行いません。
URLクエリパラメータは、SPQ_PARAMで指定します。以下のように使用します。
SPQ_PARAM('key', 'value', iQuery);
PostgreSQLの関数は、SPQ_RPCで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_RPC('helloworld', iPayload, 0, 0, iQuery);
Edge functionsは、SPQ_FUNCTIONで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_FUNCTION('hello-world', iPayload, 0, iPayloadSize, iQuery);
GraphQLは、SPQ_GraphQLで指定します。以下のように使用します。
SupabaseQuery_init(iQuery);
SPQ_GRAPHQL(iPayload, 0, iPayloadSize, iQuery);
PostgRESTでは、意図したレスポンスを得るためにPreferヘッダーによる制御が必要になる場合があります。 以下のようにプリファレンスを設定することができます。
SupabaseQuery_addPreference(
Context:=iQuery,
Preference:='timezone=Aisa/Tokyo');
プリファレンスは、複数設定することができますが、追加しかできません。 置換はできません。
何らかの理由で独自のHTTPヘッダーが必要になることがあります。 以下のようにHTTPヘッダーを設定することができます。
SupabaseQuery_setHeader(
Context:=Query,
Key:='x-service-key',
Value:='mykey');
HTTPヘッダーは追加しかできません。 同一キーの値の置換はできます。


