Skip to content

Latest commit

 

History

History
621 lines (501 loc) · 25.6 KB

File metadata and controls

621 lines (501 loc) · 25.6 KB

Supabase REST API client for NX

これは、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のテーブルに以下のようにレコードを作成します。 このテーブルは装置の生産状態を保持します。

SupabaseのTable Editorで表示した生産モニタテーブル

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

作業は以下の手順で行います。

  1. Supabaseのセットアップ
  2. anonキーの確認
  3. サンプルプロジェクトを使用環境に合わせる
  4. サンプルプロジェクトのAPIクライアントサービスの設定を変更
  5. コントローラのセキュアソケット設定にTLSセッションを登録
  6. コントローラにサンプルプロジェクトを転送

1. Supabaseのセットアップ

リポジトリのセットアップSQLをSupabaseのSQL Editorで実行します。 セットアップSQLをSQL EditorにコピーしたらDECLAREステートメントのinitial_service_keyを適当な値に変更します。 HTTPリクエストのヘッダーとしてhttp_header_keyinitial_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を確認してください。


2. anonキーの確認

SupabaseのSettings/API Keysでanonキーを確認して控えます。

Supabaseでのanonキーの確認


3. サンプルプロジェクトを使用環境に合わせる

サンプルプロジェクトを使用環境に合わせます。以下の変更が必要です。

  • コントローラの型式
    使用するコントローラの型式に変更します。
  • コントローラのネットワーク設定
    使用環境でインターネット接続可能な設定とします。DNSは特別な理由が無ければ、"1.1.1.1"のようなパブリックDNSを使用します。

4. サンプルプロジェクトのAPIクライアントサービスの設定を変更

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クライアントサービスに含むものではありませんが、追加のアクセス制限として各プログラムで使用しています。


5. コントローラのセキュアソケット設定にTLSセッションを登録

コントローラに接続、プログラムモードに変更してセキュアソケット設定に4で指定したNoと同じNoのセッションを登録します。 変更していなければ、IDが0と1のセッションを作成します。以下のように作業します。

セキュアソケット設定でのTLSセッションの登録 セキュアソケット設定でのTLSセッションの登録


6. コントローラにサンプルプロジェクトを転送

コントローラにサンプルプロジェクトを転送し、運転モードに切り替えます。 ネットワークエラーが発生していないか確認します。 ネットワークエラーが発生している場合、エラー原因を取り除きます。

エラーが発生した場合、以下の可能性があります。

  • TLSセッションIDとTLSセッション名の不一致      4で指定したTLSセッション名の番号と5で指定したTLSセッションIDが一致していることを確認します。
  • ドメインの不一致 4で指定した登録しドメインが一致していることを確認します。
  • APIキーの不一致 4で指定したAPIキーがSupabaseのAPIキーに一致ていることを確認します。
  • インターネットに接続できないか、名前解決ができない
       Supabaseドメインまでのルートをtracertで確認します。
  • Supabaseで障害が発生している   Supabase Statusを確認します。

Sysmacプロジェクトについて

各Sysmacプロジェクト、ライブラリについては以下の通りです。

SupabaseClientServceLib.smc2

APIクライアントサービス開発用のSyamacプロジェクトです。 APIクライアントサービスの実装を確認することができます。

SupabaseClientServiceLib.slr

APIクライアントサービスのライブラリです。 APIクライアントサービスのライブラリは、依存ライブラリを含んでいません。 ライブラリを使用する場合、lib\の依存ライブラリも合わせて参照する必要があります。

使用手順

既存プロジェクトでAPIクライアントサービスを使用する手順は以下です。ライブラリ操作を伴うため、必ず既存プロジェクトのコピーを作成します。

  1. プロジェクトでAPIクライアントサービスのライブラリと依存ライブラリを参照
    リポジトリのlib/が依存ライブラリです。
  2. サービス用変数をグローバル変数に登録 gSupabaseClientServiceSingleton : SupabaseClientServiceSingletonContextを定義します。
  3. サービスランナー(SupabaseClientSingletonService FBを実行するプログラム)の作成
    サンプルプロジェクトを参考にしてください。
  4. サービスランナーをタスクに追加
    サービスランナーは、API呼び出し処理を実行するので、適切なタスク時間のタスクに登録します。プライマリタスクは不適です。
  5. ビルドしてエラーが無いことを確認
    ライブラリの不足やグローバル変数定義に誤りがあればエラーが出ます。
  6. メモリ使用状況を確認
    メモリ使用状況に変更が反映されているか確認します。

使用環境が整ったらAPIクライアントサービスを使用するプログラム、Supabaseプロジェクトを作成し、サービスランナーに必要な情報を記述します。 大きなプロジェクトに対してこれらの操作を行うと時間がかかります。 可能であれば小さなプロジェクトで必要な機能を開発し、動作テストまで済ませてから必要とするプロジェクトに統合することを検討してください。

SupabaseClientServiceLibExample.smc2

APIクライアントサービスのサンプルプロジェクトです。 APIクライアントサービスの動作を確認することができます。 必要とするライブラリは全てバンドルしています。

Fetch POU

Fetch POUは、APIクライアントサービスによるSupabase REST API呼び出しを簡潔にするための一連のヘルパーPOUです。 各操作はAPI呼び出し単位であるFetchに関連する要素を集約したSupabaseRestApiFetchContext構造体を介して行います。 Fetchを使用したAPI呼び出し手続きは以下です。

  1. Fetchを生成する
  2. 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 --> [*]
    }
Loading

Query POU

クエリは、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 : スキーマ指定

スキーマの指定は、SPQ_SCHEMAで指定します。以下のように使用します。

SupabaseQuery_init(iQuery);
SPQ_SCHEMA('myschema', iQuery);

SPQ_FORM : テーブル指定(From)

テーブルの指定は、SPQ_FROMで指定します。以下のように使用します。

SupabaseQuery_init(iQuery);
SPQ_FROM('mytable', iQuery);

SPQ_SELECT : レコード取得(Select)

Selectの指定は、データベースを指定した後にSPQ_SELECTで指定します。以下のように使用します。

SupabaseQuery_init(iQuery);
SPQ_FROM('client_test', iQuery);
SPQ_SELECT('*', iQuery);
SPQ_FILTER('limit', '3', iQuery);

SPQ_INSERT : 単一レコード作成(Insert)

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_setHeaderContent-Typeを置換し、CSV形式とすることもできます。


SPQ_BULK_INSERT : 複数レコード作成(Bulk Insert)

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');

SPQ_UPDATE : レコード更新(Update)

Updateの指定は、データベースを指定した後にSPQ_UPDATEで指定します。以下のように使用します。

SupabaseQuery_init(iQuery);
SPQ_FROM('client_test', iQuery);
SPQ_UPDATE(iPayload, 0, iPayloadSize, iQuery);
SPQ_FILTER('id', 'lt.3', iQuery);

SPQ_UPSERT : レコード更新(Upsert)

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);

SPQ_SINGLE_UPSERT : 単一レコード更新(Upsert)

単一レコードの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_setHeaderContent-Typeを置換し、CSV形式とすることもできます。 単一レコードのUpsertは、PostgRESTのPUTによるUpsertです。


SPQ_FILTER : フィルタ指定

フィルタは、SPQ_FILTERで指定します。以下のように使用します。

SPQ_FILTER('id', 'gt.3', iQuery);

オペレータのバリデーションのような検査、オペランドのSQLとしてのエスケープのようなサニタイズは行いません。


SQP_PARAM : URLクエリパラメータ指定

URLクエリパラメータは、SPQ_PARAMで指定します。以下のように使用します。

SPQ_PARAM('key', 'value', iQuery);

SPQ_RPC : PostgreSQL関数呼び出し(RPC)

PostgreSQLの関数は、SPQ_RPCで指定します。以下のように使用します。

SupabaseQuery_init(iQuery);
SPQ_RPC('helloworld', iPayload, 0, 0, iQuery);

SPQ_FUNCTION : Edge functions呼び出し

Edge functionsは、SPQ_FUNCTIONで指定します。以下のように使用します。

SupabaseQuery_init(iQuery);
SPQ_FUNCTION('hello-world', iPayload, 0, iPayloadSize, iQuery);

SPQ_GRAPHQL : GraphQLの指定

GraphQLは、SPQ_GraphQLで指定します。以下のように使用します。

SupabaseQuery_init(iQuery);
SPQ_GRAPHQL(iPayload, 0, iPayloadSize, iQuery);

SupabaseQuery_addPreference : プリファレンスの設定

PostgRESTでは、意図したレスポンスを得るためにPreferヘッダーによる制御が必要になる場合があります。 以下のようにプリファレンスを設定することができます。

SupabaseQuery_addPreference(
    Context:=iQuery,
    Preference:='timezone=Aisa/Tokyo');

プリファレンスは、複数設定することができますが、追加しかできません。 置換はできません。


SupabaseQuery_setHeader : HTTPヘッダーの設定

何らかの理由で独自のHTTPヘッダーが必要になることがあります。 以下のようにHTTPヘッダーを設定することができます。

SupabaseQuery_setHeader(
    Context:=Query,
    Key:='x-service-key',
    Value:='mykey');

HTTPヘッダーは追加しかできません。 同一キーの値の置換はできます。