• NEQTO リージョンAPIを使って、コマンドラインからスクリプトの操作をする方法

本記事のポイント

IoT組込みエンジンNEQTOでスクリプトを使ったアプリケーション開発を行う際、スクリプトは自分の慣れたエディタで書きたい、とお考えになる方もいらっしゃると思います。

今回は、NEQTO APIを用いて、NEQTO Consoleを通さずにスクリプトの更新からノードに再ロードまでを行う方法をご紹介します。



1. はじめに

NEQTOでスクリプトを使用したアプリケーションを作成する際、コードを書く、バージョンを管理する、スクリプトをデバイスにロードする、といった作業が発生します。これらの作業は、NEQTO Console(以下、Console)を使用するとすべてブラウザー上で完結することができます。

しかし、「コードを書く時は、使い慣れた自分のエディタで書きたい」「gitでも管理したい」と考え、「でもローカルで開発したコードを一々Consoleのスクリプト欄にコピー&ペーストするのは面倒…」とお悩みの方もいらっしゃるのではないでしょうか。

そこで今回は、NEQTO APIを用いて、コマンドラインからスクリプトの操作を実行するためのコードをご紹介します。

2. NEQTO APIについて

Consoleで扱っているAPIはNEQTO APIとして公開しています。

NEQTO APIは現在以下の4つがあります。

  • 認証API
  • グローバルAPI
  • APリージョンAPI
  • NAリージョンAPI

認証API は、NEQTO APIの使用に必要な認証情報を取得するために利用、グローバルAPIは、デバイス、ライセンス、アカウントの管理に利用されます。

リージョンAPIはそれ以外の機能に利用され、スクリプトやノードの管理などが含まれています。物理デバイスに最も近いリージョンが推奨されていますので、 今回はAPリージョンAPI (以下API) を利用します。

詳しくはこちらのドキュメントをご覧ください。

3. 完成したPythonスクリプトのご紹介

はじめに、完成したPythonスクリプトをご紹介します。コマンドラインから実行名を指定すると、対象のAPIが実行されます。

これによって、コマンドラインでスクリプトの実装からアップデート、デバイスへのロードまでを完結することができます。言語はPythonで書きましたが、他の言語でも問題ありません。

% python app.py -h            
usage: app.py [-h] {list,read,code-read,upload,download,update,history,revert,node-reload} ...

positional arguments:
  {list,read,code-read,upload,download,update,history,revert,node-reload}
                        sub-command help
    list                スクリプトのリスト
    read                スクリプト情報を取得
    code-read           スクリプトコードを取得
    upload              スクリプト(file)をアップロード
    download            スクリプトのダウンロード
    update              スクリプトのアップデート
    history             スクリプトのバージョン履歴
    revert              スクリプトのバージョンの切り替え
    node-reload         ノードのスクリプトの再ロードコマンドを実行

optional arguments:
  -h, --help            show this help message and exit

試しに「upload」をアップロードしたいファイルを指定して実行しました。APIのレスポンスのステータスコード(status_code)、ヘッダー(header)、ボディ(text)が出力されるようにしています。

% python app.py upload test.js
status_code 204
header {'Date': 'Wed, 01 Jun 2022 08:51:13 GMT', 'Content-Length': '0', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'POST, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text

次の章では、このPythonスクリプトの実装方法についてご紹介していきます。

4. 実装方法

実装は、ログイン方法、スクリプトに関するAPI、コマンドラインインターフェースの3部構成に分けてご紹介します。

言語

  • Python3.9
  • サードパーティライブラリ
  • Requests

ログイン方法

リージョンAPIを使用するためには認証情報が必要となります。認証APIから取得したログイン情報を用いてリージョンAPIにログインします。ドキュメントの情報に従ってログイン関数を以下のように実装します。

import requests

session = requests.Session()

def login():
  url = "https://auth.neqto.com/account/api-token-auth"
  payload = {
    "company_code": COMPANY_CODE,
    "email": EMAIL,
    "password": PASSWORD
  }
  headers = {
    'content-type': "application/json"
  }
  response = session.request("POST", url, json=payload, headers=headers)
  j_res = response.json()
  session.cookies.set("auth_token", j_res["token"])

payloadのログイン情報は各自でセットが必要です。

session.cookiesを使用していますが、他のAPIを使用するときにAuthorizationトークンが必要となるため、requestsライブラリのsession機能を使用しています。

他のAPIでは以下のように使用します。

response = session.request("GET", url, headers=headers)

スクリプトに関するAPI

では、本題の実装に入りましょう。特に難しいことはなく、必要なAPIを関数化していくだけです。APIの仕様はAPIドキュメントに記載の通りです。

今回はスクリプトに関するAPIのみをコマンド化するため、必要な情報は予め取得しておく必要があります。これは例えば、group_idやnode_id、script_idなどです。取得方法としては、以下の方法が挙げられます。

  • 対象のNEQTO APIから取得する
    • /groups
    • /groups/{groups_group_id}/nodes
    • /groups/{groups_group_id}/scripts
    • ...etc
  • Consoleを開き、ブラウザーの「Delveloper Tools」の「Network」から対象APIを探し、「Response」の中からidを探す
  • Consoleを開き、図1、図2のように対象のページのURLからidを取得する
図1. 対象ノードのページのURLからIdを取得する方法

図1. 対象ノードのページのURLからidを取得する方法

図2. 対象スクリプトのページのURLからidを取得する方法

図2. 対象スクリプトのページのURLからidを取得する方法

基本的にはスクリプトコードの「upload」、スクリプトの情報(名前、環境変数、ライブラリなど)の「update」、ノードのスクリプトの再ロードコマンドを実行する「node-reload」ができるとローカルのみで開発してそれをデバイスで試す事ができるようになるため、その部分のみご説明します。

他のAPIについても同様に実装できますので、このブログの最後に載せる全体のコードをご覧ください。

upload

スクリプトコードをアップロードすることができるコマンドです。ローカルで開発したコードを、NEQTOのスクリプトに反映させることができます。

from urllib.parse import urljoin

import requests

base_url = "https://asia-pacific-1.neqto.com/"
group_id = GROUP_ID
script_id = SCRIPT_ID

@api_response
def script_upload(filename):
with open(filename, "rb") as uploaded_file:
  url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}/upload")

  response = session.request("POST", url, files={"file": uploaded_file})
return response

group_id、script_idは各自で設定が必要です。fileのアップロードをするときは、requestのfilesパラメータを使用します。バイナリファイルを開き、files={"file": uploaded_file}として指定します。filenameはパスを含めて指定する必要があります。filenameが見つからない場合はエラーが起きますので、ご注意ください。

@api_responseについては後ほどご説明します。

update

NEQTOのスクリプトの設定を更新することができます。これは、メタデータ、ライブラリ、環境変数などが該当します。

import requests

base_url = "https://asia-pacific-1.neqto.com/"
group_id = GROUP_ID
script_id = SCRIPT_ID

@api_response
def script_partical_update(payload):
  url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}")
  headers = {
    'content-type': "application/json"
  }

  # payload = {
  #     # "name": "name",
  #     # "note": "note",
  #     "lib": ["TYPE/NAME/VERSION"],
  #     # "env": [
  #     #     {
  #     #         "key": "key",
  #     #         "value": "value",
  #     #         "display": True
  #     #     }
  #     # ]
  # }
  response = session.request("PATCH", url, json=payload, headers=headers)
  return response

関数の引数のpayloadは、dict型を渡します。session.requestjson=payloadと指定することで、JSONとしてリクエストボディに渡すことができます。

payloadは執筆時点でのAPIのドキュメントを参考にしています。libは、Library usage requirementsのType, Name, Versionを"/"区切りで指定します。PATCHメソッドなので、変更したいパラメータだけを渡します。

node-reload

「upload」, 「update」で更新したものをデバイスに反映させるために、ノードの「スクリプトの再ロード」コマンドを実行することができます。

from urllib.parse import urljoin

import requests

base_url = "https://asia-pacific-1.neqto.com/"
group_id = GROUP_ID
node_id = NODE_ID

@api_response
def node_reload():
  url = urljoin(base_url, f"groups/{group_id}/nodes/{node_id}/command:reload")
  response = session.request("POST", url)
return response

API Responseの確認

次に、APIの実行状況(レスポンス)を出力して確認します。全ての関数に同じprint文を記述するのは面倒なので、ここではデコレータを使用します。上記の各コードの関数の上にある@api_responseの実装は、以下の通りです。

def api_response(func):
@functools.wraps(func)
def innner(*args, **kwargs):
  response = func(*args, **kwargs)
  print("status_code", response.status_code)
  print("header", response.headers)
  print("text", response.text)
return innner

各関数ではsession.request()responseをそのまま返すようにして、api_responseではレスポンスのステータスコード、ヘッダー、テキストを出力するようにしています。textとしたのは、レスポンスがJSON形式の場合にも、500エラーなどでHTML形式の場合と同様に文字列形式で出力できるようにするためです。

例えば、script_partical_update関数を実行すると、以下のように出力されます。この時、payloadは以下にしています。

payload = {"note": "test update"}
status_code 200
header {'Date': 'Thu, 02 Jun 2022 06:13:33 GMT', 'Content-Type': 'application/json', 'Content-Length': '191', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'GET, PUT, PATCH, DELETE, HEAD, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text {"id":"<id>","name":"script-auto","note":"test update","updated_at":"2022-06-02T06:13:33.138683Z","created_at":"2022-06-01T07:19:00.460634Z","lib":[],"env":[]}

APIをリクエストする実装の説明については以上です。

ただし、このままだとAPIのリクエストするたびに実行関数を変更するためにコードを書き換える手間が生じます。実行APIをコマンドラインから選択できれば便利になるため、次の章ではコマンドラインインターフェースの原型を作成します。

コマンドラインインターフェース

本章では、コマンドラインからAPIのリクエストの指示ができるように実装します。自作コマンドツール化までできればかっこいいのですが趣旨から外れるため、今回はPythonスクリプトを実行する際に引数を渡すことで、関数を指定できるようにするところまでをご説明します。

まず、ディレクトリ構造は以下のようにしました。

/
|__app.py (メイン)
|__nq/
    |__api.py (APIの関数)
    |__command_line_args.py (コマンドライン引数)

実装の説明に入りますが、最終的に以下のような物を目指します。

% python app.py upload test.js
status_code 204
header {'Date': 'Wed, 01 Jun 2022 08:51:13 GMT', 'Content-Length': '0', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'POST, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text

また、コマンドライン引数を受け取るために、Python標準ライブラリのargparseを使用しています。

python app.py <command> <arguments>

上記のようにcommandを使い分けできるようにするために、Sub-commandsを使用します。パーサーはコマンドラインを解析して情報を保持しますが、パーサーにサブパーサーを登録することで、サブパーサーごとに異なる引数を設定することができます。

例えばpython app.py upload test.jsを実行した場合にscript_upload関数を実行するためには、以下のように設定します。

command_line_args.py

import argparse  

import nq.api as api

def init_parser():
  parser = argparse.ArgumentParser()

  # parserにsub parsersの登録
  subparsers = parser.add_subparsers(dest="sub_name", help="sub-command help")

  # sub parserにuploadを登録
  parser_upload = subparsers.add_parser('upload', help="スクリプト(file)をアップロード")

  # uploadの引数を登録
  parser_upload.add_argument('filename', help="アップロードするファイル名")

  # uploadを指定したときに実行する関数を指定
  parser_upload.set_defaults(func=api.script_upload)

  # 引数を受け取る
  args = parser.parse_args()

  # コマンド指定なしで実行されたらhelpを表示する
  if not hasattr(args, "func"):
      parser.print_help()
return args

python app.py upload test.jsの時であれば、以下の引数を受け取ります。

args = Namespace(sub_name='upload', filename='test.js', func=<function script_upload at 0x1076c9f70>)

app.pyでは、以下のように呼び出します。コマンドが指定された時(funcが存在する時)のみAPIのリクエストを行うようにします。基本的にはargs.func()を実行すると、subparser.set_defaults(func=func)で指定した関数が呼び出されますが、その関数が引数を受け取りたい場合は、場合分けが必要になります。

app.py

from nq.command_line_args import init_parser
import nq.api as api
if __name__ == '__main__':
  args = init_parser()
  if hasattr(args, "func"):
    api.login()
    if(args.sub_name == "upload"):
      args.func(args.filename)
    else:
      args.func()

これで「upload」に関してはコマンドラインから指示できるようになりました。

他の場合も同様に設定できるので省略しますが、「update」の時だけ注意点がありますので、ご説明します。リクエストボディをコマンドラインから受け取るように作成するときは、文字列を辞書に変換する必要があります。argparseではtypeで型の指定が可能ですが、dict型を指定してもうまく動作しませんでした。そこで、typeには自作関数を指定できため文字列で受け取り、dictに変換する関数を指定しました。文字列を辞書に変換するときは標準モジュールのastを使います。

command_line_args.py

import ast

def str2dict(payload):
return ast.literal_eval(payload)

init_parser

parser_update = subparsers.add_parser('update', help="スクリプトのアップデート")
parser_update.add_argument('payload', type=str2dict, help="変更するパラメータを辞書形式で指定")
parser_update.set_defaults(func=api.script_partical_update)

今回はpayloadをまとめてコマンドラインから受け取るように作成しましたが、厳格にしたい場合はオプション引数でキーごとに受け取るように作成しても良いでしょう。

5. 実行結果

今回作成したものを実行してみます。全体のコードはブログの最後に載せていますのでそちらをご覧ください。

できること一覧の表示

% python app.py -h            
usage: app.py [-h] {list,read,code-read,upload,download,update,history,revert,node-reload} ...

positional arguments:
  {list,read,code-read,upload,download,update,history,revert,node-reload}
                        sub-command help
    list                スクリプトのリスト
    read                スクリプト情報を取得
    code-read           スクリプトコードを取得
    upload              スクリプト(file)をアップロード
    download            スクリプトのダウンロード
    update              スクリプトのアップデート
    history             スクリプトのバージョン履歴
    revert              スクリプトのバージョンの切り替え
    node-reload         ノードのスクリプトの再ロードコマンドを実行

optional arguments:
  -h, --help            show this help message and exit

スクリプトのリスト

グループに属するスクリプト一覧を取得します。

% python app.py list

script name list ['script-auto']
status_code 200
header {'Date': 'Thu, 02 Jun 2022 09:05:07 GMT', 'Content-Type': 'application/json', 'Content-Length': '1611', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'GET, POST, HEAD, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text {"count":1,"next":null,"previous":null,"results":[{"id":"<script_id>","name":"script-auto","note":"","updated_at":"2022-06-02T07:40:54.065987Z","created_at":"2022-06-01T07:19:00.460634Z","lib":[],"env":[]}]}

スクリプトのアップデート

あるスクリプトIDの情報の一部をアップデートします。

% python app.py update  '{"name": "script-auto2", "note": "test-2", "lib": ["I2C/HTS221_V2/latest"], "env": [{"key": "test", "value": "test-2", "display": True}]}'

status_code 200
header {'Date': 'Thu, 02 Jun 2022 09:10:37 GMT', 'Content-Type': 'application/json', 'Content-Length': '255', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'GET, PUT, PATCH, DELETE, HEAD, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text {"id":"<script_id>","name":"script-auto2","note":"test-2","updated_at":"2022-06-02T09:10:37.441286Z","created_at":"2022-06-01T07:19:00.460634Z","lib":["I2C/HTS221_V2/latest"],"env":[{"key":"test","value":"test-2","display":true}]}

スクリプト(file)をアップロード

あるスクリプトIDにスクリプトコードをアップロードします。

% python app.py upload test.js

status_code 204
header {'Date': 'Thu, 02 Jun 2022 09:06:58 GMT', 'Content-Length': '0', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'POST, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text

スクリプトのバージョン履歴

あるスクリプトIDのスクリプトコードのバージョン履歴を取得します。

% python app.py history
status_code 200
header {'Date': 'Thu, 02 Jun 2022 09:13:17 GMT', 'Content-Type': 'application/json', 'Content-Length': '775', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'GET, HEAD, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text [{"version_id":"<version_id>","updated_at":"2022-06-02T09:06:58Z"},{"version_id":"<version_id>","updated_at":"2022-06-02T07:20:46Z"}]

スクリプトのバージョンの切り替え

あるスクリプトIDのスクリプトコードのバージョンを指定して切り替えます。

% python app.py revert &#x22;&#x3C;version_id&#x3E;&#x22;
status_code 200
header {'Date': 'Thu, 02 Jun 2022 09:14:54 GMT', 'Content-Length': '0', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'POST, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text

スクリプトのダウンロード

あるスクリプトIDのスクリプトコードをダウンロード時のファイル名を指定してダウンロードします。APIはバージョンを指定できる仕様ですが今回の実装では現在のスクリプトのみ対応しています。

% python app.py download download.js
status_code 200
header {'Date': 'Thu, 02 Jun 2022 09:16:45 GMT', 'Content-Type': 'application/javascript', 'Content-Length': '0', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Content-Disposition': 'attachment; filename=run.js', 'Allow': 'GET, HEAD, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text

スクリプトコードを取得

あるスクリプトIDのスクリプトコードを取得します。APIはバージョンを指定できる仕様ですが今回の実装では現在のスクリプトコードのみ対応しています。

% python app.py code-read
status_code 200
header {'Date': 'Thu, 02 Jun 2022 09:13:46 GMT', 'Content-Type': 'application/json', 'Content-Length': '917', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'GET, POST, HEAD, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text {"updated_at":"2022-06-02T09:06:58Z","etag":"\"<etag>\"","body":"console.log(\"test\")"}

スクリプト情報を取得

あるスクリプトIDのスクリプト情報を取得します。

% python app.py read
status_code 200
header {'Date': 'Thu, 02 Jun 2022 09:12:51 GMT', 'Content-Type': 'application/json', 'Content-Length': '255', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'GET, PUT, PATCH, DELETE, HEAD, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text {"id":"<script_id>","name":"script-auto2","note":"test-2","updated_at":"2022-06-02T09:12:01.074295Z","created_at":"2022-06-01T07:19:00.460634Z","lib":["I2C/HTS221_V2/latest"],"env":[{"key":"test","value":"test-2","display":true}]}

ノードの「スクリプトの再ロード」コマンドを実行

あるスクリプトIDが紐づいたノードに対して、「スクリプトの再ロード」を行います。

% python app.py node-reload         
status_code 200
header {'Date': 'Thu, 02 Jun 2022 09:17:17 GMT', 'Content-Length': '0', 'Connection': 'keep-alive', 'Server': 'nginx/1.16.1', 'Allow': 'POST, OPTIONS', 'X-Frame-Options': 'SAMEORIGIN', 'Vary': 'Accept-Language, Origin', 'Content-Language': 'en'}
text

6. 全体のコード

最後に作成したコードの全体を載せます。

app.py

from nq.command_line_args import init_parser
import nq.api as api

if __name__ == '__main__':
  args = init_parser()
  if hasattr(args, "func"):
    api.login()
    if(args.sub_name == "upload"):
        args.func(args.filename)
    elif(args.sub_name == "download"):
        args.func(args.filename)
    elif(args.sub_name == "update"):
        args.func(args.payload)
    elif(args.sub_name == "revert"):
        args.func(args.version_id)
    else:
        args.func()

command_line_args.py

import argparse
import ast

import nq.api as api


def str2dict(payload):
  return ast.literal_eval(payload)


def init_parser():
  parser = argparse.ArgumentParser()
  subparsers = parser.add_subparsers(dest="sub_name", help="sub-command help")

  parser_list = subparsers.add_parser('list', help="スクリプトのリスト")
  parser_list.set_defaults(func=api.script_list)

  parser_read = subparsers.add_parser('read', help="スクリプト情報を取得")
  parser_read.set_defaults(func=api.script_read)

  parser_code_read = subparsers.add_parser('code-read', help="スクリプトコードを取得")
  parser_code_read.set_defaults(func=api.script_code_read)

  parser_upload = subparsers.add_parser('upload', help="スクリプト(file)をアップロード")
  parser_upload.add_argument('filename', help="アップロードするファイル名")
  parser_upload.set_defaults(func=api.script_upload)

  parser_download = subparsers.add_parser('download', help="スクリプトのダウンロード")
  parser_download.add_argument('filename', help="ダウンロード時のファイル名")
  parser_download.set_defaults(func=api.script_download)

  # payloadに渡すパラメータを全て定義しても良い。
  parser_update = subparsers.add_parser('update', help="スクリプトのアップデート")
  parser_update.add_argument('payload', type=str2dict, help="変更するパラメータを辞書形式で指定")
  parser_update.set_defaults(func=api.script_partical_update)

  parser_history = subparsers.add_parser('history', help="スクリプトのバージョン履歴")
  parser_history.set_defaults(func=api.script_history)

  parser_revert = subparsers.add_parser('revert', help="スクリプトのバージョンの切り替え")
  parser_revert.add_argument('version_id', type=str, help="version_id")
  parser_revert.set_defaults(func=api.script_revert)

  parser_reload = subparsers.add_parser('node-reload', help="ノードのスクリプトの再ロードコマンドを実行")
  parser_reload.set_defaults(func=api.node_reload)

  args = parser.parse_args()
  if not hasattr(args, "func"):
      parser.print_help()
return args

api.py

from urllib.parse import urljoin
import functools

import requests

base_url = "https://asia-pacific-1.neqto.com/"
group_id = "GROUP_ID"
node_id = "NODE_ID"
script_id = "SCRIPT_ID"

company_code = "COMPANY_CODE"
email = "EMAIL"
password = "PASSWORD"

session = requests.Session()


def login():
  url = "https://auth.neqto.com/account/api-token-auth"
  payload = {
    "company_code": company_code,
    "email": email,
    "password": password
  }
  headers = {
    'content-type': "application/json"
  }
  response = session.request("POST", url, json=payload, headers=headers)
  j_res = response.json()
  session.cookies.set("auth_token", j_res["token"])

def api_response(func):
  @functools.wraps(func)
  def innner(*args, **kwargs):
    response = func(*args, **kwargs)
    print("status_code", response.status_code)
    print("header", response.headers)
    print("text", response.text)
  return innner

@api_response
def script_list():
  url = urljoin(base_url, f"groups/{group_id}/scripts")
  headers = {
    'content-type': "application/json"
  }
  response = session.request("GET", url, headers=headers)
  try:
    j_res = response.json()
    name = [script.get("name", "") for script in j_res.get("results", [])]
    print("script name list", name)
  except Exception:
    pass
return response

@api_response
def script_create():
  url = urljoin(base_url, f"groups/{group_id}/scripts")
  headers = {
    'content-type': "application/json"
  }
  payload = {
    "name": "blog",
    "note": "blogで作成",
    "lib": ["I2C/HTS221/latest"],
    "env": [
      {
        "key": "GROUP_ID",
        "value": "123456789",
        "display": True
      }
    ]
  }

response = session.request("POST", url, json=payload, headers=headers)
return response

@api_response
def script_read():
  url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}")
  headers = {
    'content-type': "application/json"
  }
response = session.request("GET", url, headers=headers)
return response


@api_response
def script_partical_update(payload):
  url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}")
  headers = {
    'content-type': "application/json"
  }

  # payload = {
  #     # "name": "name",
  #     # "note": "note",
  #     "lib": ["TYPE/NAME/VERSION"],
  #     # "env": [
  #     #     {
  #     #         "key": "key",
  #     #         "value": "value",
  #     #         "display": True
  #     #     }
  #     # ]
  # }
response = session.request("PATCH", url, json=payload, headers=headers)
return response


@api_response
def script_code_read():
  url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}/code")
  headers = {
    'content-type': "application/json"
  }
response = session.request("GET", url, headers=headers)
return response


@api_response
def script_code_create():
  url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}/code")
  headers = {
    'content-type': "application/json"
  }
  payload = {
    "body": "console.log(\"test2\")"
  }

response = session.request("POST", url, json=payload, headers=headers)
return response

@api_response
def script_download(filename):
  url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}/download")
  headers = {
    'content-type': "application/json"
  }

response = session.request("GET", url, headers=headers)
with open(filename, mode='wb') as f:  # wb でバイト型を書き込める
  f.write(response.content)
return response


@api_response
def script_history():
  url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}/history")
  headers = {
    'content-type': "application/json"
  }

response = session.request("GET", url, headers=headers)
return response


@api_response
def script_revert(version_id):
  url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}/revert")
  headers = {
    'content-type': "application/json"
  }
  payload = {
    "version_id": version_id
  }

response = session.request("POST", url, json=payload, headers=headers)
return response


@api_response
def script_upload(filename):
  with open(filename, "rb") as uploaded_file:
    url = urljoin(base_url, f"groups/{group_id}/scripts/{script_id}/upload")

response = session.request("POST", url, files={"file": uploaded_file})
return response


@api_response
def node_reload():
  url = urljoin(base_url, f"groups/{group_id}/nodes/{node_id}/command:reload")
  response = session.request("POST", url)
  return response

7. まとめ

スクリプトの更新からノードにスクリプトを再ロードさせるまでの手順にフォーカスして、NEQTOリージョンAPIの使い道の一つをご紹介しました。

Pythonスクリプト実行時に引数を渡すだけで簡単にスクリプトに関する操作ができるようになったため、開発が捗ると思います。今回は、グループやノード、スクリプトのIDは固定としていますが、これらも指定できるようにするなど、拡張していくと自由度が上がるでしょう。

これを機に、NEQTO APIの活用法を試してみていただければと思います。