#ターミナル

0 フォロワー · 15 投稿

ターミナルツールの使用に関するディスカッションに関連する投稿。

記事 Saori Murata · 9月 30, 2024 19m read

開発者の皆さん、こんにちは! InterSystems IRIS(以下、IRIS)を使用したアプリケーション開発において、皆さんは環境設定をどうされていますか? 私は最近になって、「インストールマニフェスト」という機能があることを知りました。 これは、管理ポータルでポチポチしていた作業をコード化・自動化できる強力なツールです! 最初こそとっつきづらかったものの良いところがたくさんあるなと思ったので、簡単にではありますが皆さんにその良さと始め方をご紹介したいと思います。

なお、私が使用しているIRISバージョンは以下です。

2022.1

バージョンが異なる場合、違う書き方になっているもの等が存在する場合がありますので、 公式ドキュメント等を参照し適宜読み替えていただければと思います。

目次

  1. はじめに
  2. インストールマニフェストとは
  3. インストールマニフェストのメリット・デメリット
  4. インストールマニフェストの始め方と基本構造
    1. マニフェストの作成
    2. マニフェストの編集
    3. マニフェストの実行
  5. インストールマニフェストでできること
    1. 変数の設定
    2. ネームスペースとデータベースの設定
    3. データのインポートとマッピング
    4. セキュリティ設定
    5. InterOperability 機能の設定
  6. インストールマニフェスト以外で行う環境設定
    1. ObjectScriptによる実装
    2. Pythonによる実装
  7. まとめ

1. はじめに

IRISを使用するにあたって、管理ポータルでの環境設定は切っても切れない作業だと思います。 IRIS のインストールマニフェストを使用することで、ネームスペース、データベース、セキュリティ設定、InterOperabilityの有効化、 さらにはカスタムコードの実行まで、一連のプロセスを自動化することができます。

本記事では、実際のマニフェストファイルを例に挙げながら、IRIS の環境設定自動化の方法について解説します。

なお、本記事で解説しているサンプルコードの全文と、付随するフォルダ・ファイルについては、Githubにて公開しています。

2. インストールマニフェストとは

IRIS のインストールマニフェストとは、%Installerというツールのことを指します。 インストールマニフェスト定義を記述するだけで、 IRIS環境設定を変更するために必要なコードを自動生成することができます。 他人に共有する際は、定義したインストールマニフェストを配布するだけで済みます。

インストールマニフェストは XML 形式で記述されます。 主要な要素として <Manifest><Namespace><Configuration><Database> などがあります。

なお、公式ドキュメントは以下です。

インストール・マニフェストの作成および使用

3.インストールマニフェストのメリット・デメリット

私が使ってみて感じた、インストールマニフェストのメリット・デメリットについてお伝えします。 全ての場合でインストールマニフェストが優れているとは言えないと思いますので、下記を参考に使用したほうが良いかどうか判断してもらえたらと思います。

  • メリット

    • IRIS環境構築(管理ポータルで手動で実行していた作業)の自動化ができる
    • 環境構築内容をコードとして管理できる
    • 共通の環境を誰でも簡単に、素早く作れるようになる
    • 事前に内容を定義しておけるので、管理ポータルで操作してうっかり設定ミスや設定忘れをしてしまうといったことがなくなる
  • デメリット

    • 環境構築経験が豊富でない人にとっては、公式ドキュメントを読みながらでもコードが示す内容の把握が難解なところがある
    • マニフェストの記法を理解した人でないと、コードのメンテナンスを実施できない
    • 定義の作成、定義の配布、実行方法の共有など、事前準備に手間がかかるため、少人数の環境が対象だったり、設定項目が少なかったりすると手間のほうが大きくなる可能性がある

4. インストールマニフェストの始め方と基本構造

4-1. マニフェストの作成

インストールマニフェストは、簡単に作成することができます。 例えばあなたがスタジオを使っている場合、下記の手順でテンプレートを作成できます。

ファイル > 新規作成 > 一般 > %Installer マニフェスト > OK

こうして作成されるマニフェストは、以下のようになっています。

Include %occInclude  

/// %Installer Manifest MyApp.MyInstaller
Class MyApp.MyInstaller
{  
  
/// マニフェスト定義.  
XData MyManifest [ XMLNamespace = INSTALLER ]  
{  
<Manifest>
	<Namespace>
		<Configuration>
			<Database>
				<!-- Your Manifest code here -->
			</Database>
		</Configuration>
	</Namespace>
</Manifest>
}  
  
/// これは XGL により生成されたメソッド・ジェネレーターです。.  
ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]  
{  
    #; XGL ドキュメントでこのメソッドのコードを生成する.  
    Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyManifest")  
}  
  
}

4-2. マニフェストの編集

なにやら見覚えのないコードがたくさん生成されますが、安心してください。 そのうち、私たちが編集すればよいのは基本的に下記の部分のみです。

<Manifest>
	<Namespace>
		<Configuration>
			<Database>
				<!-- Your Manifest code here -->
			</Database>
		</Configuration>
	</Namespace>
</Manifest>

■マニフェストの基本構造

詳細については後ほど解説しますが、ここでは基本的な構造について紹介していきます。

  • <Manifest>すべてのタグのルートタグである必要がある。 他のすべてのタグを含む。
  • <Namespace>ネームスペースを定義する。 【親タグ:<Manifest>
  • <Configuration><Namespace>内で構成タグの親タグとして記述する必要がある。【親タグ:<Namespace>
  • <Database>データベースを定義する。 【親タグ:<Configuration>

ここで覚えるべきは、<Manifest>タグは唯一無二ですべてのルートタグとなること、 その中身となるそれぞれのタグにも親子関係があるためそれを守らなければならないこと、の2点です。

これら以外の記述は、インストールマニフェストの作成や実行に必要なコードです。 詳細の説明については、下記のページにあるので任せたいと思います。

%InstallerでInterSystems Cachéにアプリケーションをデプロイする

4-3. マニフェストの実行

このマニフェストを実行したいときは、ターミナルで以下のコマンドを実行してください。 ※マニフェストを実行する時は、%SYSネームスペースで実行することを推奨します。(それ以外のネームスペースでも動作はします)

USER>zn "%SYS"
%SYS>do ##class(MyApp.MyInstaller).setup()

そうすると、マニフェストが環境設定のためのコードを生成し、そのコードが実際にIRISの環境設定を変更していきます。

5. インストールマニフェストでできること

それではさっそく、インストールマニフェストで実際にできることについてみていきましょう。 私が触ったことのある機能を中心に紹介していますが、前述の公式ドキュメントにはそれ以外の設定もできるXMLタグが多数紹介されていますので、参照してみてください。

5-1. 変数の設定

マニフェストでは、変数を設定することができます。 変数設定ができるタグには2種類あります。

  • <Default>:変数値がまだ設定されていない場合のみ、変数値を設定します。(すでに値が設定されている場合、無効になる)【親タグ:<Manifest>
  • <Var>:マニフェストで使用できる変数を定義および設定します。【親タグ:<Manifest>
<Var Name="var1" Value="aaa" />
// 事前に変数 var1 が設定されているので、var1 は aaa のまま
<Default Name="var1" Value="bbb" />
// 事前に変数 var2 は設定されていないので、var2 は ccc になる
<Default Name="var2" Value="ccc" />

5-2. ネームスペースとデータベースの設定

マニフェストを使用して、ネームスペースとそれに関連するデータベースを簡単に作成できます。 関連するタグは3種類です。

  • <Namespace>:ネームスペースを定義する。【親タグ:<Manifest>
    • Name:ネームスペースの名前
    • Create:新しいネームスペースを作成するかどうか(yes/no/overwrite) ※デフォルト:yes
    • Code:コード用データベースの指定
    • Data:データ用データベースの指定
    • Ensemble:InterOperabilityを使用するネームスペースかどうかの指定
  • <Configuration><Namespace> 内で構成タグの親タグとして記述する必要がある。【親タグ:<Namespace>
  • <Database>:データベースを定義する。【親タグ:<Configuration>
    • Name:データベース名
    • Dir:データベース・ディレクトリ
    • Create:新しいデータベースを作成するかどうか
    • InitialSize:データベースの初期サイズ(MB)
    • Resource:データベースへのアクセスを制御するリソース
    • PublicPermissions:リソースに割り当てられる許可の値(新規作成リソースのみ適用)(R/W/RW)
<Default Name="Namespace" Value="TESTNMSP"/>  
<Default Name="DATADB" Value="${Namespace}-GBL"/>  
<Default Name="CODEDB" Value="${Namespace}-RTN"/>
<Default Name="SetupDir" Value="C:\work\git"/>

<!-- ネームスペース作成 -->  
<Namespace Name="${Namespace}" Code="${CODEDB}" Data="${DATADB}" Create="yes" Ensemble="0">  
	<!-- DB作成 -->  
	<Configuration>  
		<Database Name="${DATADB}" Dir="C:\IRISDB\${Namespace}\GBL" Create="yes" InitialSize="100" Resource="%DB_${DATADB}" PublicPermissions="R"/>  
		<Database Name="${CODEDB}" Dir="C:\IRISDB\${Namespace}\RTN" Create="yes" InitialSize="10" Resource="%DB_${CODEDB}" PublicPermissions="R"/>  
	</Configuration>  
</Namespace>

■結果(管理ポータル画面)

(ネームスペース)

(データベース)

5-3. データのインポートとマッピング

マニフェストを使用して、特定のネームスペースに既存のデータやコードをインポートすることもできます。 関連するタグは1種類です。(既出除く)

  • <Import>:ファイルをインポートする。(%SYSTEM.OBJ.ImportDir または %SYSTEM.OBJ.Load を使用する)【親タグ:<Namespace>
    • File:インポートするファイルまたはフォルダ
    • Flags:コンパイル・フラグ(参考:フラグおよび修飾子
    • Recurse:再帰的にインポートするかどうかを指定 ※デフォルト:0
    • IgnoreErrors:エラー時に続行するかどうかを指定 ※デフォルト:0
<Default Name="Namespace" Value="TESTNMSP"/>  
<Default Name="DATADB" Value="${Namespace}-GBL"/>  
<Default Name="CODEDB" Value="${Namespace}-RTN"/>
<Default Name="SetupDir" Value="C:\work\git"/>

<Namespace Name="${Namespace}" Code="${CODEDB}" Data="${DATADB}" Create="yes" Ensemble="0">  
	<!-- グローバル、クラス、ルーチンインポート -->  
	<Import File="${SetupDir}\Test1" Flags="ck" Recurse="1" IgnoreErrors="1"/>  
	<Import File="${SetupDir}\Test2" Flags="ck" Recurse="1" IgnoreErrors="1"/>  
</Namespace>

■結果(管理ポータル画面)

マニフェストを使用して、グローバルやクラスをマッピングすることもできます。 関連するタグは3種類です。(既出除く)

  • <GlobalMapping>:グローバルを現在のネームスペースにマッピングする。【親タグ:<Configuration>
    • Global:グローバル名
    • From:グローバルのソース・ネームスペース
  • <ClassMapping>:パッケージを現在のネームスペースにマッピングする。【親タグ:<Configuration>
    • Package:マップするパッケージ
    • From:マッピングに使用するソース・データベース
  • <RoutineMapping>:ルーチンを現在のネームスペースにマッピングする。【親タグ:<Configuration>
    • Name :ルーチン
    • Type :ルーチン・タイプ
    • From:ルーチンのソース・データベース
<Default Name="Namespace" Value="TESTNMSP"/>
<Default Name="Namespace2" Value="${Namespace}2"/>  
<Default Name="DATADB2" Value="${Namespace2}-GBL"/>  
<Default Name="CODEDB2" Value="${Namespace2}-RTN"/>

<Namespace Name="${Namespace2}" Code="${CODEDB2}" Data="${DATADB2}" Create="yes" Ensemble="0">  
	<Configuration>
		<!-- DB作成 -->  
		<Database Name="${DATADB2}" Dir="C:\IRISDB\${Namespace2}\GBL" Create="yes" InitialSize="100" Resource="%DB_${DATADB2}" PublicPermissions="R"/>  
		<Database Name="${CODEDB2}" Dir="C:\IRISDB\${Namespace2}\RTN" Create="yes" InitialSize="10" Resource="%DB_${CODEDB2}" PublicPermissions="R"/>  
		<!-- グローバル、クラス、ルーチンマッピング設定 -->  
		<GlobalMapping Global="test2" From="${DATADB}"/>  
		<ClassMapping Package="Test2" From="${CODEDB}"/>  
	</Configuration>  
</Namespace>

■結果(管理ポータル画面)

5-4. セキュリティ設定

マニフェストを使用して、ロールやユーザアカウントを複数作成することができます。 関連するタグは2種類です。

  • <Role>:ロールを定義する。【親タグ:<Manifest>
    • Name:ロール名
    • Description:ロールの説明(カンマ不可)
    • Resources:ロールで保持されているリソースと特権。
    • RolesGranted:付与されるロール
  • <User>:ユーザを定義する。【親タグ:<Manifest>
    • Username:ユーザ名
    • PasswordVar:ユーザ・パスワードが含まれる変数の名前(変数名を渡す必要があることに注意)
    • Fullname:ユーザのフルネーム
    • Roles:ユーザを割り当てるロールのリスト
    • Namespace:ユーザの実行開始ネームスペース
    • Enabled:ユーザが有効かどうか
    • Comment:オプションコメント
    • ChangePassword:次回ログイン時にユーザにパスワードの変更を要求するかどうか
    • ExpirationDate:それ以降のユーザ・ログインが無効になる日付
<Default Name="TestUserPw" Value="12345"/>

	<!-- ロール作成・変更 -->  
<Role  
	Name="TestOperator"  
	Description="テスト運用者ロール"  
	Resources="%DB_TESTNMSP-GBL:RW,%DB_TESTNMSP-RTN:RW"  
	RolesGranted="%Developer"/>  
<Role  
	Name="TestAdministrator"  
	Description="テスト管理者ロール"  
	RolesGranted="%All"/>  
 
<!-- ユーザ作成・変更 -->  
<User  
	Username="TestUser1"  
	PasswordVar="TestUserPw"  
	Fullname="テストユーザ1"  
	Roles="TestOperator"  
	Namespace="USER"  
	Enabled="1"  
	Comment="テストユーザ1"/>  
<User  
	Username="TestUser2"  
	PasswordVar="TestUserPw"  
	Fullname="テストユーザ2"  
	Roles="TestAdministrator"  
	Namespace="USER"  
	Enabled="1"  
	Comment="テストユーザ2"/> 

■結果(管理ポータル画面)

(ロール)

(ユーザ)

■クラスメソッドの実行(Invoke)

また、管理ポータルのセキュリティ設定をまるっと読み込むこともできます。 これにより、管理ポータルやターミナル等のログインの有効化や、詳細のセキュリティ設定までを一括で行うことができます。 ロールやユーザアカウントについても、この読み込みにより一括で作成することができます。 関連するタグは2種類です。(既出除く)

  • <Invoke>:クラス・メソッドを呼び出して、実行結果を変数の値として返す。【親タグ:<Namespace>
    • Class:クラス名
    • Method:メソッド名
    • CheckStatus:返されたステータスをチェックするかどうか
    • Return:結果の書き込み先の変数の名前
  • <Arg><Invoke> または <Error> から呼び出されるメソッドに引数を渡す。【親タグ:<Invoke>, <Error>
    • Value:引数の値
<Default Name="SetupDir" Value="C:\work\git"/>

<Namespace Name="%SYS" Create="overwrite">  
	<!-- セキュリティ設定のインポート -->  
	<Invoke Class="Security.System" Method="ImportAll" CheckStatus="false">  
		<Arg Value="${SetupDir}\SecurityExport.xml"/>  
	</Invoke>  
</Namespace>

これらのタグは、セキュリティ設定のインポート以外にも様々な用途に使用することができます。 例えば、タスクスケジュール全体のインポートをセキュリティと同じように行ったり、自作のクラスの処理を実行したり、既存のシステムクラスの処理を実行したりなどです。

※管理ポータルのログインの有効化設定については、2024.3以降のバージョンであればマニフェストのタグにて変更することができます。 (ターミナルのログイン有効化については、現在は<Invoke>を用いる方法か、後述の別途マニフェスト以外のコードを書く方法で実現するしかありません) 具体的には、下記のタグで実現できます。

  • <CSPApplication>:クラス内で定義されている 1 つ以上の Web アプリケーションを定義する。【親タグ:<Namespace>
    • AuthenticationMethods:有効な認証方法(例:32=password、64=unauthenticated)
    • CSPEnabled:CSP が有効かどうか ※デフォルト:1
    • DefaultTimeout:セッション・タイムアウト
    • Description:説明
    • Directory:CSP ファイルへのパス
    • Url:Webアプリケーションの名前

※セキュリティ設定のインポートの事前準備として、管理ポータルのセキュリティ設定(上述の「SecurityExport.xml」にあたるもの)をXMLとしてエクスポートしておく必要があります。 エクスポートは、次のように実施します。 ターミナルで^SECURITY ユーティリティを起動し、12) System parameter setup > 5) Export All Security settings を選択します。 そうすると、C:\InterSystems\[IRISインスタンス名]\mgr\SecurityExport.xml ができているはずです。 (IRISを入れている場所によっては出力場所は異なります)

★4.セキュリティ情報(ユーザ・サービスなど)のインポートInterSystems 製品の設定内容をインポート/エクスポートする方法 > セキュリティについて

%SYS>Do ^SECURITY
:
Option? 12       // System parameter setup
:
Option? 5        // Export All Security settings

Export ALL security records? Yes => Yes

Export to file name SecurityExport.xml =>
Parameters? "WNS" =>
Confirm export of selected security records to SecurityExport.xml? No => Yes

■結果(管理ポータル画面)

(デフォルトでは管理ポータルのログイン時にパスワード認証はないが、それをマニフェストで有効化した例)

5-5. InterOperability 機能の設定

InterOperability機能を使用する場合、専用のネームスペースを作成することができます。 また、プロダクションを自動的に設定することもできます。 関連するタグは2種類です。<Namespace>も改めて紹介します。

  • <Namespace>:ネームスペースを定義する。【親タグ:<Manifest>
    • Name:ネームスペースの名前
    • Create:新しいネームスペースを作成するかどうか(yes/no/overwrite) ※デフォルト:yes
    • Code:コード用データベースの指定
    • Data:データ用データベースの指定
    • Ensemble:InterOperabilityを使用するネームスペースかどうかの指定
  • <Production>:プロダクションを定義する。【親タグ:<Namespace>
    • Name:プロダクション名
    • AutoStart:プロダクションを自動的に起動するかどうか ※デフォルト:0
<Default Name="Namespace3" Value="ENSNMSP"/>
<Default Name="DATADB3" Value="${Namespace3}-GBL"/>  
<Default Name="CODEDB3" Value="${Namespace3}-RTN"/>  
<Default Name="SetupDir" Value="C:\work\git"/>

<Namespace  Name="${Namespace3}"  Code="${CODEDB3}"  Data="${DATADB3}"  Create="yes"  Ensemble="1">
	<!-- Database作成などの設定は省略 -->
	<!-- グローバル、クラス、ルーチンインポート -->  
	<Import File="C:\work\git\Test3" Flags="ck" Recurse="1" IgnoreErrors="1"/>
	<!-- プロダクションの作成 -->
	<Production  Name="Test3.job.Main"  AutoStart="1"  />
</Namespace>

■結果(管理ポータル画面)

6. インストールマニフェスト以外で行う環境設定

マニフェストを作成していると、管理ポータルでは設定できるけどマニフェストではどうやるのだろう?という設定に度々出会います。 公式ドキュメントを見るなどしてマニフェストでの実現方法がわかる場合もありますが、 中にはマニフェストでは実現方法がないもの、後続のバージョンでは修正されたが今使っているバージョンではバグが残っており実現できないもの、 等がある場合があります。

その場合、あきらめて管理ポータルで設定するというのも手ですが、 別途コード化してマニフェストの処理と一緒に実施してしまうのもありです。

実際に、いくつかの設定をマニフェスト以外のコードで実現し、それをマニフェストと一緒に実行するコードの簡単な例をご紹介します。

6-1. ObjectScriptによる実装

下記のコードでは、マニフェストの生成・実行処理であるsetupメソッド(前述のスタジオでの新規作成にて自動作成されるメソッド)を、 自作のsetupExecuteというメソッドでラップしています。 その上で、ロケールの変更や不要タスクの一時停止といった処理をコードで記述し、実行します。

/// <h2>マニフェスト生成・実行処理</h2>  
/// <p><pre>SAMPLES>  
/// Do ##class(MyApp.MyInstaller).setup()  
/// </p></pre>  
ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]  
{  
	#; XGL ドキュメントでこのメソッドのコードを生成する.  
	Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyManifest")  
}  
  
/// <h2>セットアップ開始処理</h2>  
/// <p>return %Status</p>  
/// <p><pre>SAMPLES>  
/// Do ##class(MyApp.MyInstaller).setupExecute()  
/// </p></pre>  
ClassMethod setupExecute(ByRef pVars) As %Status  
{  
	Set tSC = 0  
	New $NAMESPACE  
	Set $NAMESPACE = "%SYS"  
	#; すでにネームスペースがあるときはManifestの処理はスキップ  
	If ('##class(%SYS.Namespace).Exists("TESTNMSP")) {  
		Set tSC = ..setup(.pVars)  
	}  
	  
	#; 日本語ロケールへ変更  
	Do ##class(Config.NLS.Locales).Install("jpuw")  
	#; 不要なタスクを停止  
	Do ..SuspendTask()  
	  
	Return tSC  
}

/// <h2>タスク停止</h2>  
/// <p>不要なタスクスケジュールを停止する。</p>  
/// <p><pre>SAMPLES>  
/// Do ##class(MyApp.MyInstaller).SuspendTask()  
/// </p></pre>  
ClassMethod SuspendTask()  
{  
	Set targetId = -1  
	Set query = 0  
	Set query($INCREMENT(query)) = "SELECT %ID,Name FROM %SYS.Task"  
	Set tStatement = ##class(%SQL.Statement).%New()  
	Do tStatement.%Prepare(.query)  
	Set result = tStatement.%Execute()  
	While (result.%Next()) {  
		If (result.%GetData(2) = "機能トラッカー") {  
			Set targetId = result.%GetData(1)  
			Quit  
		}  
	}  
	If (targetId '= -1) {  
		#; 機能トラッカーを一時停止  
		Do ##class("%SYS.TaskSuper").Suspend(targetId)  
	}  
}

これを実行する時は、setupメソッドではなくsetupExecuteメソッドを、ターミナルから実行します。 %SYSネームスペースから実行してください。

USER>ZN "%SYS"
%SYS>Do ##class(MyApp.MyInstaller).setupExecute()

■結果(管理ポータル画面)

(機能トラッカーを一時停止した結果)

6-2. Pythonによる実装

本題からは逸れてしまいますが、「InterSystems Embedded Python」について私は今まで触ったことがなかったため、この機会にチャレンジしてみました。 簡単にですが、その結果と所感について述べたいと思います。

6-1の内容を、Embedded Pythonを用いて書き換えたコードは以下です。 ※setupメソッドについては「objectgeneratorメソッド」という特別なメソッドのため、ObjectScriptのみでしか動作しません。そのため、Pythonへの書き換えはできないようでした。

/// <h2>マニフェスト生成・実行処理</h2>
/// <p><pre>SAMPLES>
/// Do ##class(MyApp.MyInstaller).setup()
/// </p></pre>
ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]
{
     #; XGL ドキュメントでこのメソッドのコードを生成する.
     Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyManifest")
}

/// <h2>セットアップ開始処理</h2>
/// <p>return %Status</p>
/// <p><pre>SAMPLES>
/// Do ##class(MyApp.MyInstaller).setupExecute()
/// </p></pre>
ClassMethod setupExecute(pVars As %SYS.Python = "") As %Status [ Language = python ]
{
	import iris
	tSC = 0
	# すでにネームスペースがあるときはManifestの処理はスキップ
	if not iris.cls('%SYS.Namespace').Exists('TESTNMSP') == 1:
		tSC = iris.cls(__name__).setup(pVars)

	# 日本語ロケールへ変更
	iris.cls('Config.NLS.Locales').Install('jpuw')
	# 不要なタスクを停止
	iris.cls(__name__).SuspendTask()

	return tSC
}

/// <h2>タスク停止</h2>
/// <p>不要なタスクスケジュールを停止する。</p>
/// <p><pre>SAMPLES>
/// Do ##class(MyApp.MyInstaller).SuspendTask()
/// </p></pre>
ClassMethod SuspendTask() [ Language = python ]
{
	import iris
	
	targetId = -1
	df = iris.sql.exec('SELECT %ID,Name FROM %SYS.Task').dataframe()
	for i in range(len(df)):
		if df.iloc[i, 1] == '機能トラッカー':
			targetId = df.iloc[i, 0]
			break

	if not targetId == -1:
    	# 機能トラッカーを一時停止
		iris.cls('%SYS.TaskSuper').Suspend(int(targetId))
}

※SuspendTaskメソッドの実行に当たっては、事前にpandasライブラリのインストールが必要です。 未インストールの場合、下記のコマンドを実行してインストールを実行してください。 (IRISを入れている場所によっては対象のパスは異なります)

> cd C:\InterSystems\[IRISインスタンス名]\bin
> irispip install --target ..\mgr\python pandas

Embededd Pythonを少しだけ使用してみた所感としては、 純粋にPythonを書くところは知っている記法をそのまま書けるので苦なく書けましたが、 IRISとの連携部分(IRISのクラスを利用するなどIRIS独自の機能を利用したいとき)は都度書き方を調べる必要があったので少し大変でした。 一度書き方を覚えてしまえばより多くのコードを書く時でも問題なさそうですが、慣れが必要だと思います。 ObjectScriptの記法に慣れていると、ObjectScriptで当たり前にできることがPythonでは実装が難しい、直感的に書けない場合もあるようなので、注意が必要です。

しかし、それを踏まえてもコードをPythonで書けるのは良いと思いました! Pythonの記法のメリットとしてObjectScriptよりもシンプルに記載できる箇所も多いですし、 前述のPythonでは実装が難しい部分についても、バージョンがあがっていくことで機能は徐々に追加されていくと思いますので、これからに期待ができます。 なにより、ObjectScriptの経験がない人に対しても共通の言語を持てるというのはとても素晴らしいことだと思います。

これを実行する時は、ObjectScriptのときと同じで問題ありません。 %SYSネームスペースから実行してください。 ※そうでないと、「Config.NLS.Locales」の実行時にエラーとなります(%SYSにあるパッケージのため)

USER>ZN "%SYS"
%SYS>Do ##class(MyApp.MyInstaller).setupExecute()

7. まとめ

IRIS のインストールマニフェストを使用することで、環境設定の自動化が可能になり、共通の環境を素早く構築できます。 これにより、開発チームの生産性が向上し、人為的なミスも減らすことができそうです。

本記事で興味を持っていただいた方は、ぜひインストールマニフェストを使ってみてください! 以上、ここまでお読みいただきありがとうございました。

0
1 309
記事 Toshihiko Minamoto · 5月 9, 2024 3m read

VS Code でファイルを編集しているときに、グローバル値のチェックやいくつかの ObjectScript コマンドの実行が必要だったことはありませんか? これが可能になりました。しかもセットアップは不要です! vscode-objectscript 拡張機能バージョン 2.10.0 以上を持っており、InterSystems IRIS 2023.3 以降に接続している場合は、サーバーの場所に関係なくサーバーへのターミナル接続を開けるようになりました。

この新しいターミナルを開く方法には 3 つあります。

WebSocket ターミナルは、読み取り、ネームスペースの切り替え、中断、カスタムターミナルプロンプトなど、標準 ObjectScript シェルの多数の機能をサポートしていますが、 この記事では、これに特有の 3 つの機能に焦点を当てたいと思います。

  • コマンド入力は構文の色付けがされるため、実行する前に入力が構文的に正しいことを確認できます。 syntax colored input

  • ターミナルは複数行の編集モードをサポートしています。Enter を押すと、入力が実行されるのではなく新しい行が追加されます。 新しい行は、コマンド入力に閉じられていない開始波括弧 { または開始丸括弧 ( がある場合に追加されます。 multi-line input

  • VS Code のシェル統合に完全に組み込まれているため、VS Code はコマンドの入出力を検出できます。 このため、コマンドを簡単に再実行したり、カーソルでテキストをハイライトせずにコマンド出力をクリップボードにコピーしたりすることができます。 command output

機能の全リストについては、公式ドキュメントをご覧ください。 この機能の改善アイデアがありますか? ぜひお聞かせください! 提案は、拡張機能の GitHub リポジトリに投稿できます。 この投稿のすべてのスクリーンショットでは、新しい "InterSystems Default Dark Modern" VS Code テーマが使用されています。InterSystems Language Server 拡張機能バージョン 2.4.0 以降で使用可能です。

0
0 256
記事 Toshihiko Minamoto · 4月 18, 2024 13m read

我々には、Redditユーザーが書いた、おいしいレシピデータセット がありますが, 情報のほとんどは投稿されたタイトルや説明といったフリーテキストです。埋め込みPythonLangchainフレームワークにあるOpenAIの大規模言語モデルの機能を使い、このデータセットを簡単にロードし、特徴を抽出、分析する方法を紹介しましょう。

データセットのロード

まず最初に、データセットをロードするかデータセットに接続する必要があります。

これを実現するにはさまざまな方法があります。たとえばCSVレコードマッパーを相互運用性プロダクションで使用したり csvgenのようなすばらしい OpenExchange アプリケーションを使用することもできます。

今回、外部テーブルを使用します。これは物理的に別の場所に保存されているデータをIRIS SQLで統合する非常に便利な機能です。

まずは外部サーバ(Foreign Server)を作成します。

CREATE FOREIGN SERVER dataset FOREIGN DATA WRAPPER CSV HOST '/app/data/'

その上でCSVファイルに接続する外部テーブルを作成します。

CREATE FOREIGN TABLE dataset.Recipes (
  CREATEDDATE DATE,
  NUMCOMMENTS INTEGER,
  TITLE VARCHAR,
  USERNAME VARCHAR,
  COMMENT VARCHAR,
  NUMCHAR INTEGER
) SERVER dataset FILE 'Recipes.csv' USING
{
  "from": {
    "file": {
       "skip": 1
    }
  }
}

以上です。すぐに「dataset.Recipes」にSQLクエリを実行できます。 image

## どんなデータが必要?

データセットは興味深く、直ぐに処理したいと思うのですが、調理のレシピを決めたいのであれば、分析に使える情報がもう少し必要です。 2つの永続化クラス(テーブル)を使用します。

  • yummy.data.Recipe抽出分析したいレシピのタイトルと説明、他のプロパティが入ったクラス (例: スコア、難易度、材料、調理タイプ、準備時間)
  • yummy.data.RecipeHistory レシピのログを取るためのシンプルなクラス

これで 「yummy.data*」 テーブルにデータセットの内容をロードすることができます。

do ##class(yummy.Utils).LoadDataset()

一見良さそうに見えますが、スコア、難易度、材料、準備時間、調理時間フィールドのデータをどのように生成するのかを見つける必要があります。

## レシピの分析 各レシピのタイトルと説明を処理します

  • 難易度, 材料, 調理タイプなどの抽出
  • 何を作りたいか決められるよう、基準に基づいて独自のスコアを構築

以下を使用します

  • より多くの分析を構築したい場合に再利用できる一般的な分析構造

LLM(大規模言語モデル)は自然言語を処理するための本当に素晴らしいツールです。

LangChainはPythonで動くようになっているので、Embedded Pythonを使ってInterSystems IRISで直接使うことができます。 LangChain is ready to work in Python, so we can use it directly in InterSystems IRIS using Embedded Python.

完全な SimpleOpenAI クラスは以下のようになります。

/// レシピ向けのシンプルな OpenAI 分析
Class yummy.analysis.SimpleOpenAI Extends Analysis
{

Property CuisineType As %String;

Property PreparationTime As %Integer;

Property Difficulty As %String;

Property Ingredients As %String;

/// 実行
/// ターミナルから実行できます。
/// set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8))
/// do a.Run()
/// zwrite a
Method Run()
{
    try {
        do ..RunPythonAnalysis()

        set reasons = ""

        // 好きな調理タイプ
        if "spanish,french,portuguese,italian,korean,japanese"[..CuisineType {
            set ..Score = ..Score + 2
            set reasons = reasons_$lb("It seems to be a "_..CuisineType_" recipe!")
        }

        // 丸一日調理に費やしたくない :)
        if (+..PreparationTime < 120) {
            set ..Score = ..Score + 1
            set reasons = reasons_$lb("You don't need too much time to prepare it") 
        }
        
        // 好きな材料ボーナス
        set favIngredients = $listbuild("kimchi", "truffle", "squid")
        for i=1:1:$listlength(favIngredients) {
            set favIngred = $listget(favIngredients, i)
            if ..Ingredients[favIngred {
                set ..Score = ..Score + 1
                set reasons = reasons_$lb("Favourite ingredient found: "_favIngred)
            }
        }

        set ..Reason = $listtostring(reasons, ". ")

    } catch ex {
        throw ex
    }
}

/// 分析結果でレシピを更新する
Method UpdateRecipe()
{
    try {
        // 親クラスの処理を先に呼び出す
        do ##super()

        // 個別のOpenAI 解析結果を追加
        set ..Recipe.Ingredients = ..Ingredients
        set ..Recipe.PreparationTime = ..PreparationTime
        set ..Recipe.Difficulty = ..Difficulty
        set ..Recipe.CuisineType = ..CuisineType

    } catch ex {
        throw ex
    }
}

/// 埋め込み Python + Langchain で分析を実行
/// do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8)).RunPythonAnalysis(1)
Method RunPythonAnalysis(debug As %Boolean = 0) [ Language = python ]
{
    # load OpenAI APIKEY from env
    import os
    from dotenv import load_dotenv, find_dotenv
    _ = load_dotenv('/app/.env')

    # account for deprecation of LLM model
    import datetime
    current_date = datetime.datetime.now().date()
    # date after which the model should be set to "gpt-3.5-turbo"
    target_date = datetime.date(2024, 6, 12)
    # set the model depending on the current date
    if current_date > target_date:
        llm_model = "gpt-3.5-turbo"
    else:
        llm_model = "gpt-3.5-turbo-0301"

    from langchain.chat_models import ChatOpenAI
    from langchain.prompts import ChatPromptTemplate
    from langchain.chains import LLMChain

    from langchain.output_parsers import ResponseSchema
    from langchain.output_parsers import StructuredOutputParser

    # init llm model
    llm = ChatOpenAI(temperature=0.0, model=llm_model)

    # prepare the responses we need
    cuisine_type_schema = ResponseSchema(
        name="cuisine_type",
        description="What is the cuisine type for the recipe? \
                     Answer in 1 word max in lowercase"
    )
    preparation_time_schema = ResponseSchema(
        name="preparation_time",
        description="How much time in minutes do I need to prepare the recipe?\
                     Anwer with an integer number, or null if unknown",
        type="integer",
    )
    difficulty_schema = ResponseSchema(
        name="difficulty",
        description="How difficult is this recipe?\
                     Answer with one of these values: easy, normal, hard, very-hard"
    )
    ingredients_schema = ResponseSchema(
        name="ingredients",
        description="Give me a comma separated list of ingredients in lowercase or empty if unknown"
    )
    response_schemas = [cuisine_type_schema, preparation_time_schema, difficulty_schema, ingredients_schema]

    # get format instructions from responses
    output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
    format_instructions = output_parser.get_format_instructions()
    
    analysis_template = """\
    Interprete and evaluate a recipe which title is: {title}
    and the description is: {description}
    
    {format_instructions}
    """
    prompt = ChatPromptTemplate.from_template(template=analysis_template)

    messages = prompt.format_messages(title=self.Recipe.Title, description=self.Recipe.Description, format_instructions=format_instructions)
    response = llm(messages)

    if debug:
        print("======ACTUAL PROMPT")
        print(messages[0].content)
        print("======RESPONSE")
        print(response.content)

    # populate analysis with results
    output_dict = output_parser.parse(response.content)
    self.CuisineType = output_dict['cuisine_type']
    self.Difficulty = output_dict['difficulty']
    self.Ingredients = output_dict['ingredients']
    if type(output_dict['preparation_time']) == int:
        self.PreparationTime = output_dict['preparation_time']

    return 1
}

}

「RunPythonAnalysis」メソッドがOpenAIが詰め込むところです :)  ターミナルから直接実行してレシピを受け取れます。

do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)

以下のような出力を受け取れます。

USER>do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)
======実際の課題
                    レシピタイトルを解釈、評価: 巻きずし - アラスカロール
                    説明: 寿司がたべたいのに巻きすがない? 代わりに簡単なバージョンを試してみてください。超簡単なのに、同じようにおいしい!
[Video Recipe](https://www.youtube.com/watch?v=1LJPS1lOHSM)
# 材料
提供量:  \~サンドイッチ5枚分
* 米1カップ
* 水 3/4 カップ + 大さじ 2 1/2
* 昆布 小口切り 1枚
* 米酢 大さじ2
* さとう 大さじ1
* 塩 小さじ1
* アボカド 2個
* カニカマ 6個
* 和風マヨ 大さじ2
* サーモン 1/2 ポンド  
# レシピ     
* 酢飯1合をボウルに入れ、2回以上、または水が透明になるまで米を洗う。炊飯器に米を移し、昆布の小口切り1枚と水3/4カップ+大さじ2と1/2杯を加える。炊飯器の指示に従って炊く。
* 米酢大さじ2、砂糖大さじ1、塩小さじ1を中くらいのボウルに入れる。全体がよく混ざるまで混ぜる。
* 炊き上がったら昆布を取り除き、すぐに酢を入れた中ボウルに米をすべてすくい入れ、飯ベラを使ってよく混ぜる。米をつぶさないように、切るように混ぜること。炊きあがったら、キッチンタオルをかけて室温まで冷ます。
* アボカド1個の上部を切り、アボカドの中央に切り込みを入れ、ナイフに沿って回転させる。次にアボカドを半分ずつ取り、ひねる。その後、ピットのある側を取り、慎重にピットに切り込みを入れ、ひねって取り除く。その後、手で皮をむく。この手順をもう片方のアボカドでも繰り返す。作業スペースを確保するため、作業台を片付けるのを忘れずに。次に、アボカドを下向きに置き、薄くスライスする。スライスしたら、ゆっくりと広げていく。それが終わったら、脇に置いておく。
* カニカマから包みをはずす。カニカマを縦にむいていく。すべてのカニカマを剥いたら、横に回転させながら細かく刻み、和風マヨ(大さじ2)とともにボウルに入れ、全体がよく混ざるまで混ぜる。
* 鋭利なナイフを斜めに入れ、木目に逆らって薄くスライスする。切り口の厚さは好みによる。ただ、すべてのピースが同じような厚さになるようにする。
* 海苔巻きラップを手に取る。キッチンバサミを使い、海苔巻きラップの半分の位置から切り始め、ラップの中心を少し過ぎるまで切る。ラップを垂直に回転させ、作り始める。すし飯を握るために、手に水をつけておく。酢飯を手に取り、海苔巻きの左上の四辺に広げる。次に、右上にサーモンを2切れ並べる。右下にアボカドを2切れのせる。最後に左下にカニサラダを小さじ2杯ほどのせる。次に、右上の四つ角を右下の四つ角に折り込み、さらに左下の四つ角に折り込む。最後に、左上の四つ角をサンドイッチの残りの部分に折り込む。その後、ラップを上に置き、半分に切って、生姜とわさびを2、3枚添えれば出来上がり。
                    
                    出力は、先頭と末尾の"``json "と"``"を含む、以下のスキーマでフォーマットされたマークダウンのコードスニペットでなければなりません:
json
{
        "cuisine_type": string  // レシピの調理タイプは?                                  小文字の1単語で回答
        "preparation_time": integer  // レシピの準備に必要な時間(分)は? 整数で回答(不明な場合はnull)
        "difficulty": string  // レシピの難易度は?                               「容易」「標準」「難しい」「とても難しい」のうちから1つを回答
        "ingredients": string  // 小文字のカンマ区切りの材料リスト、不明な場合は空
}

                    
======応答
json
{
        "cuisine_type": "japanese",
        "preparation_time": 30,
        "difficulty": "easy",
        "ingredients": "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
}

良さそうです。OpenAIのプロンプトは有用な情報を返してくれるようです。ターミナルから分析クラス全体を実行してみましょう:

set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12))
do a.Run()
zwrite a
USER>zwrite a
a=37@yummy.analysis.SimpleOpenAI  ; <OREF>
+----------------- general information ---------------
|      oref value: 37
|      class name: yummy.analysis.SimpleOpenAI
| reference count: 2
+----------------- attribute values ------------------
|        CuisineType = "japanese"
|         Difficulty = "easy"
|        Ingredients = "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
|    PreparationTime = 30
|             Reason = "It seems to be a japanese recipe!. You don't need too much time to prepare it"
|              Score = 3
+----------------- swizzled references ---------------
|           i%Recipe = ""
|           r%Recipe = "30@yummy.data.Recipe"
+-----------------------------------------------------

## 全レシピを解析する!

当然、読み込んだすべてのレシピで分析を実行したいでしょう。 この方法でレシピ ID の範囲を分析することができます

USER>do ##class(yummy.Utils).AnalyzeRange(1,10)
> Recipe 1 (1.755185s)
> Recipe 2 (2.559526s)
> Recipe 3 (1.556895s)
> Recipe 4 (1.720246s)
> Recipe 5 (1.689123s)
> Recipe 6 (2.404745s)
> Recipe 7 (1.538208s)
> Recipe 8 (1.33001s)
> Recipe 9 (1.49972s)
> Recipe 10 (1.425612s)

その後、レシピテーブルを再度表示させ、結果をチェックします。

select * from yummy_data.Recipe

image

どんぐりカボチャのピザか、豚肉入り韓国風豆腐キムチを試してみます:) いずれにせよ、家で再確認する必要がありますね :)

最後に

サンプルソースは全て https://github.com/isc-afuentes/recipe-inspector にあります。

この簡単な例で、InterSystems IRIS で LLM テクニックを使用して機能を追加したり、データの一部を分析する方法を学びました。

これを起点に以下のことが考えられます

  • InterSystems BIを使い、キューブやダッシュボードでデータの検索やナビゲートをおこなう。
  • Webアプリを作成し、UIを提供する(例:Angular)RESTForms2のようなパッケージを活用することで、永続クラスへのREST APIを自動的に生成することができます。 *レシピが好きか嫌いかを保存し、新しいレシピが好きかどうかを判断するのはいかがでしょうか。IntegratedMLアプローチ、あるいはLLMアプローチでいくつかの例データを提供し、RAG(Retrieval Augmented Generation)ユースケースを構築してみるのも良いでしょう。

他にどんなことが試せそうでしょうか?ご意見をお聞かせください!

0
0 458
記事 Toshihiko Minamoto · 3月 18, 2024 7m read

はじめに

InterSystems は先日、Visual Studio Code(VSC)IDE 用の拡張機能は InterSystems Studio に比べてより優れたエクスペリエンスを提供するという考えから、VSC IDE 用の拡張機能を独占的に開発するためにバージョン 2023.2 より InterSystems Studio のサポートを終了すると発表しました。それ以来、VSC に切り替えた開発者や、VSC を使用し始めた開発者が大勢います。 VSC には Studio のような出力パネルがなく、InterSystems が開発したプラグインをダウンロードする以外に IRIS ターミナルを開く統合機能もないため、多くの人は演算を実行する際にターミナルの開き方に迷ったことでしょう。

概要

  • はじめに 
  • 解決策
    • 少なくとも IRIS 2020.1 または IRIS 2021.1.2 を使用するユーザー: Web ターミナルを使用
    • 少なくとも least IRIS 2023.2 を使用するユーザー: WebSocket ターミナルを使用
    • Docker ベースの IRIS を使用するユーザー
    • ローカルマシンでバージョン 2023.2 より前の IRIS を使用するユーザー
    • SSH 接続を使ってリモートサーバーの IRIS でコーディングするユーザー

解決策

VSC でターミナルを開く方法は使用している特定の構成によって異なるため、以下に、状況に最適な解決策をまとめました。

少なくとも IRIS 2020.1.1 または IRIS 2021.1.2 を使用するユーザー: Web ターミナルを使用

少なくとも IRIS 2020.1.1 または IRIS 2021.1.2 を使用し、外部の拡張機能のインストールが許可されている(サードパーティアプリケーションに関する会社のポリシーにより許可されていない場合もあります)ユーザーの場合、VSC 用の Web ターミナル拡張機能が役立つ可能性があります。 ご存知ない方のために説明すると、Web ターミナルは、ObjectScript で制作された InterSystems 製品(IRIS、Caché、Ensemble、HealthShare、TrakCare など)用の Web ベースのターミナルで、ブラウザ内でより高度なバージョンのターミナルを使用することができます(プロジェクトページはこちらです)。 この VSC 拡張機能によって、直接 VSC からクリックするだけで Web ベースのターミナルを起動できます。

Web ターミナルを開くには、InterSystems ツール をクリックし、ネームスペースを選択して、いずれかのアイコン( または )をクリックします。それぞれ、VSC ターミナルパネルかブラウザで Web ターミナルが開きます(Alt を押すとデフォルトアイコンを変更できます)。

 

 

少なくとも least IRIS 2023.2 を使用するユーザー: WebSocket ターミナルを使用

少なくとも IRIS 2023.2 を使用するユーザーは、最新バージョンの VSC 拡張機能に含まれる新しい「WebSocket ターミナル」機能を利用できるため、他の回避策は必要ありません。

WebSocket ターミナルを開くには、InterSystems ツールをクリックし、ネームスペースを選択して、Web ターミナルの隣のアイコンをクリックします。

編集: 詳細については、これについて @Brett Saviano が書いたおもしろい記事をご覧ください!

How to run ObjectScript commands in the VS Code integrated terminal(VS Code の統合ターミナルで ObjectScript コマンドを実行する方法)

Docker ベースの IRIS を使用するユーザー

Docker 内の IRIS 環境で作業しており、VSC を使用しているユーザーは、Docker 環境から直背悦ターミナルセッションを開始できます。

ステータスバーで Docker voice をクリックし、Docker でターミナルを開くを選択します。

このポイントについて画像と説明を提供してくれた @Evgeny Shvarov に感謝しています。

 

ローカルマシンでバージョン 2023.2 より前の IRIS を使用するユーザー

ローカルマシンで実行するバージョンの IRIS で作業するユーザーの場合は、VSC 内に専用の IRIS ターミナルをセットアップすることができます。

    1. settings.json ファイルを開きます。 このファイルを見つけるには、表示 > コマンドパレットをクリックして「settings」と入力し、ユーザー設定を開く(JSON)を選択するなど、様々な方法があります。
    2. terminal.integrated.profiles.windows」の下に以下のコードを追加します。
"terminal.integrated.profiles.windows":{
<span class="hljs-string">"IRIS Terminal"</span>: {

&nbsp;&nbsp;&nbsp; <span class="hljs-string">"path"</span>: [

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="hljs-string">"C:\\InterSystems\\IRISHealth\\bin\\irissession.exe"</span>
&nbsp;&nbsp;&nbsp; ],

&nbsp;&nbsp;&nbsp; <span class="hljs-string">"args"</span>: [<span class="hljs-string">"IRISHEALTH"</span>],

&nbsp;&nbsp;&nbsp; <span class="hljs-string">"icon"</span>: <span class="hljs-string">"terminal-cmd"</span>
} 

}

注意: 正しい irissession.exe のパスを挿入してください。

c. VSC からターミナルを開くには、ターミナル > 新しいターミナル > プロファイルを起動… > IRIS ターミナルに移動します。

d. ターミナルメニューに 'IRIS ターミナル' voice が表示されます。

SSH 接続を使ってりモードサーバーの IRIS でコーディングするユーザー 

SSH 接続(PuTTY の使用など)を使ってアクセスできるリモートサーバー(会社サーバーなど)の IRIS バージョンを使用するユーザーの場合、Remote - SSH VSC 拡張機能を使用して、VSC をサーバーに直接接続できます。 これを行うには、以下を実行します。

    1. VSC に Remote - SSH: Editing Configuration Files 拡張機能をインストールします。
    2. サイドバーの「リモートエクスプローラーアイコンをクリックします。
    3. SSH 構成ファイルを開く」を選択します。

  

次のパスの構成ファイルを開きます: C:\Users\<username>\.ssh\config

    1. 構成ファイルに以下のコードを挿入します。 
Host my-putty-connection

    HostName < IP address or server name >

    User < username >

    IdentityFile < private key path on your local machine >

    Port < port >

IP アドレスとポートは PuTTY に指定されたホスト名とポート、ユーザー名はリモートサーバーにアクセスする際に使用するユーザー認証情報、そして IdentityFile は PuTTY 秘密鍵へのファイルパスです。

注意:  PuTTY が生成した秘密鍵の元のフォーマット(.ppk)は VSC で読み取れません。 PuTTY 経由で VSC とリモートサーバーの接続を確立するには、元の秘密鍵を複製して .pem フォーマットに変換する必要があります。 この変換は、以下のように行います。

  1. PuTTYgen アプリケーションを起動します。
  2. File メニューから、Load private key をクリックします。
  3. .ppk フォーマットの秘密鍵を選択して、Open を選択します。
  4. Conversions メニューで Export OpenSSH Key (force new file format) をクリックします。
  5. .pem 拡張子を使った新しい名前を設定し、Save ボタンをクリックします。
  6. この新しい .pem ファイルへのパスを VSC の IdentifyFile パラメーターにリンクします。
    1. ファイルを保存します。 数秒ほどすると、Remote Explorer パネルに新しい接続が表示されます。
    2. Connect in New Window...」をクリックして新しい VSC ウィンドウに SSH 接続を開きます。
  7. リモートマシンのオペレーティングシステムを選択します(初回アクセスのみ)。
  8. 新しいウィンドウで、Terminal New Terminal に移動します(または Ctrl + ò または Ctrl + Shift + ò を使用します)。
  9. これで、リモートマシンに接続し、VSC 内で IRIS ターミナルを使用できるようになりました。

注意: この操作は、以前に PuTTY 経由でリモート接続を開始したことがあり、PuTTY が閉じられている場合またはリモートサーバーに接続していない場合にのみ機能します。 この操作では、PuTTY は起動せず、PuTTY が確立するトンネルに VSC が接続されるだけです。

VSC を通じて PuTTY 接続を開始するには、バッチファイルを使用できます(Windows)。 提供されている connect_remote.bat ファイルは、PuTTY に含まれる Plink コマンドを使用してセッションを開始します。

@echo off

set SESSION="<your saved session name>"

plink -load %SESSION%

セッションを開始するには、VSC ターミナルに .\connect_remote.bat と入力して、リモート接続を開き、認証情報を入力します。

注意: こちらの後のメソッドによって、すべての VSC ショートカットをサポートするターミナルバージョンにアクセスできるようになります! Shift+Insert ではなく、Ctrl+V を使用できるようになります 🎉

1
1 611
記事 Toshihiko Minamoto · 7月 28, 2023 1m read

ターミナルにライセンス期限切れの警告メッセージ(「*\* Warning: This Cache license will expire in 3 days **」)が表示されており、そのメッセージを表示したくない場合は、以下のコマンドを実行すると、メッセージの表示を無効(または有効)にできます。

Do ExpirationMessageOff^%SYS.LICENSE - Disable

Do ExpirationMessageOn^%SYS.LICENSE - Enable

 

0
0 159
記事 Mihoko Iijima · 1月 6, 2022 4m read

開発者のみなさん、あけましておめでとうございます🎍 今年もどうぞよろしくお願いします!

さて、この記事では、IRIS ターミナルに(こっそり)追加された便利機能をご紹介します!(つい最近知りまして、びっくりしましたsurprise

2023/4/13 追記:Pythonシェルへ切り替えるメソッドのショートカットが追加されていたので返信欄に追記しました。

IRIS ターミナルで以前実行したコマンドを再実行する場合、上矢印キーを連打しながらコマンドを探されていると思うのですが、IRIS 2021.1 から履歴表示と、履歴番号を指定した実行ができるようになっていました!

では早速、履歴(history)の使い方をご紹介します。これがあれば、もう、上矢印キーを連打せずに以前実行したコマンドを再実行できます!!

まずは、履歴を作るため、いくつかコマンドを実行します。​​​

1
0 604
記事 Mihoko Iijima · 10月 19, 2022 1m read

開発者の皆さん、こんにちは!

ターミナルでクラスメソッドを実行するとき、パッケージ名やクラス名などの入力候補が出てこないので、 ##class(パッケージ名.クラス名).メソッド名() の記述をミスったりちょっと面倒だな・・と感じること、ありませんか??
第1回 InterSystems Idea-A-Thon(アイデアソン)  でも、もっとシンプルに呼び出せるようにしよう!のアイデアが投稿されていたようです。)

(私も含めて)そんな方に、朗報です!📣

なんと、VSCode ObjectScriptエクステンションに新機能「Copy Invocation」が追加されました!

クラスメソッドを記述すると、定義の上に Copy Invocation のリンクが表示され、クリックするとクラスメソッドの実行文がバッファにコピーされるんです!laugh

Copy Invocation をクリックした後で、ターミナルで 右クリック→貼り付け をやってみてください。

ちゃんと実行文がコピーされていることを確認できます(下図の黄色い線の文章がコピーされます)。

あとは、Do や Write や Set 文を記述するだけでいいんです!laugh

ぜひ、お試しください!​​​​​​

VSCode contributorの皆さん、素敵な機能追加をありがとうございました!

0
0 512
記事 Megumi Kakechi · 10月 8, 2021 1m read

これは InterSystems FAQ サイトの記事です。

ターミナルでルーチンを実行し、プログラム上でエラーが発生した時に、エラートラップを適切に設定していない場合、以下のようなデバッグモードになります。

USER>do ^error1
 write A
^
a+2^error1 *A
USER 2d0>

この状態から、ルーチン起動の前の状態に戻るにはQuitコマンドを入力します。

USER 2d0>Quit

またエラーが発生したルーチン内でトランザクション処理を行なっている場合には、以下のような表示のプロンプトになります。

USER>do ^error1
 write A
^
a+3^error1 *A
TL1:USER 2d0>q
TL1:USER>

このようにプロンプトの先頭にTL+数字が表示されている場合には完了していないトランザクションがあることを示しています。

この状態ではQuitコマンドを入力しても元のプロンプトには戻りません。

最初にTrollbackコマンドを入力し、その後でQuitコマンドを実行することで元のプロンプトに戻ります。

TL1:USER>Trollback
0
0 530
記事 Toshihiko Minamoto · 6月 23, 2021 10m read

この記事では、InterSystems Cachéにおけるマクロについて説明します。 マクロは、コンパイル中に一連の命令に置き換えられるシンボリック名です。 マクロは、渡されたパラメーターとアクティブ化したシナリオに応じて、呼び出されるたびに一連の命令セットに「展開」されます。 これは、静的コードの場合もあれば、ObjectScriptを実行して得られる結果である場合もあります。 それでは、アプリケーションでマクロをどのように使用できるのかを見てみましょう。

コンパイル

まず、ObjectScriptコードがどのようにコンパイルされているのかを見てみましょう。

  • クラスコンパイラはクラス定義を使用してMACコードを生成します。
  • 場合によっては、コンパイラはクラスを元に追加クラスを生成します。 これらのクラスはStudioで閲覧できますが、変更してはいけません。 この動作は、たとえば、WebサービスやWebクライアントのクラスを生成する際に発生します。
  • クラスコンパイラはランタイム時にCaché が使用するクラス記述子も生成します。
  • プリプロセッサ(マクロプロセッサまたはMPPとも呼ばれます)が、INCファイルを使用してマクロを置き換えます。 さらに、ObjectScriptルーチンにある埋め込みSQLも処理します。
  • これらの変更はすべてメモリで発生するため、ユーザーのコードに変化はありません。
  • その後、コンパイラはObjectScriptルーチンのINTコードを作成します。 このレイヤーは中間コードとして知られるレイヤーです。 このレベルでのデータへのすべてのアクセスは、グローバルを介して提供されます。
  • INTコードはコンパクトで、人間が読み取ることができます。 Studioで閲覧するには、Ctrl+Shift+Vを押してください。
  • INTコードを使用して、OBJコードが生成されます。
  • OBJコードは、Caché仮想マシンが使用するコードです。 OBJコードが生成されるとCLS/MAC/INTコードは不要になるため、不要となったそれらのコードは削除することができます(ソースコードを含めずに製品を出荷する場合など)。
    1. クラスが永続クラスである場合、SQLコンパイラは対応するSQLテーブルを作成します。

    マクロ

    前に述べたように、マクロは、プリプロセッサによって命令セットに置き換えられるシンボリック名です。 マクロは#Defineコマンドにマクロ名(おそらく引数のリストを含む)とその値を続けて定義します。

    #Define Macro[(Args)] [Value]
    

    マクロはどこに定義されるのでしょうか。 マクロの定義は、コード内か、マクロのみを含む独立したINCファイルで行われます。 必要なファイルは、クラス定義の最初にInclude MacroFileNameコマンドを使用してクラスに含められます。これがマクロをクラスに含めるための推奨される主な方法です。 この方法で含められるマクロは、クラスのどの部分にでも使用できます。 #Include MacroFileNameコマンドを使ってマクロを含むINCファイルをMACルーチンや特定のクラスメソッドのコードに含めることができます。

    マクロをコンパイル時に使用する場合、またはクラスにIncludeGeneratorキーワードを使用する場合は、メソッドジェネレーターの本文に#Includeを使用する必要があることに注意してください。

    Studioの自動補完でマクロを使用できるようにするには、前の行に///を追加します。

    ///
    #Define Macro[(Args)] [Value]

    例1

    では、例をいくつか見てみましょう。標準的な「Hello World」メッセージから始めます。 COSコードは次のようになります。 

    Write "Hello, World!"
    

    HWという、次の行を書き込むマクロを作成します。

    #define HW Write "Hello, World!"

    後は、$$$HW(マクロを呼び出す$$$と、その後にマクロ名を指定)を記述するのみです。

    ClassMethod Test()
    {
         #define HW Write "Hello, World!"
         $$$HW
    }

    これは、コンパイル中に次のINTコードに変換されます。

    zTest1() public {
         Write "Hello, World!" }

    このメソッドが呼び出されると、ターミナルに次のテキストが表示されます。

    Hello, World!

    例2

    次の例では、変数を使用し見ましょう。

    ClassMethod Test2()
    {
         #define WriteLn(%str,%cnt) For ##Unique(new)=1:1:%cnt { ##Continue
             Write %str,! ##Continue
         }
         
         $$$WriteLn("Hello, World!",5)
    }

    上記のコードでは、%str文字列が%cnt回書き込まれます。 変数名は%で始まる必要があります。 ##Unique(new) コマンドで、生成されたコードに新しい一意の変数を作成し、##Continueによって、次の行にマクロの定義が続くことを示します。 このコードは、次のINTコードに変換されます。

    zTest2() public {
         For %mmmu1=1:1:5 {
             Write "Hello, World!",!
         } }
    

    ターミナルには次のように表示されます。

    Hello, World!
    Hello, World!
    Hello, World!
    Hello, World!
    Hello, World!

     

    例3

    より複雑な例に進みましょう。 ForEach演算子は、グローバルを反復処理する上で非常に役立ちます。それでは作成してみましょう。

    ClassMethod Test3()
    {
        #define ForEach(%key,%gn) Set ##Unique(new)=$name(%gn) ##Continue
        Set %key="" ##Continue
        For { ##Continue
            Set %key=$o(@##Unique(old)@(%key)) ##Continue
            Quit:%key=""
        
        #define EndFor    }
        
           Set ^test(1)=111
           Set ^test(2)=222
           Set ^test(3)=333
           
           $$$ForEach(key,^test)
               Write "key: ",key,!
               Write "value: ",^test(key),!
           $$$EndFor
    }

    INTコードでは次のようになります。

    zTest3() public {
           Set ^test(1)=111
           Set ^test(2)=222
           Set ^test(3)=333
           Set %mmmu1=$name(^test)
           Set key=""
           For {
               Set key=$o(@%mmmu1@(key))
               Quit:key=""
               Write "key: ",key,!
               Write "value: ",^test(key),!
           } }

    これらのマクロでは何が起こっているのでしょうか。

    1. グローバルの名前を新しい%mmmu1変数に書き込みます($name関数)。
    2. キーは、初期の空の文字列値です。
    3. 反復サイクルが開始します。
    4. 間接演算子$order関数を使って、キーに次の値が割り当てられます。
    5. キーが""値を取っているかどうかを、事後条件を使ってチェックします。取っている場合は反復が完了し、サイクルが終了します。
    6. 任意のユーザーコードが実行されます。この場合、キーと値が出力されます。
  • サイクルが終了します。
  • このメソッドが呼び出されると、ターミナルには次のように表示されます。

    key: 1
    value: 111
    key: 2
    value: 222
    key: 3
    value: 333

    %Collection.AbstractIteratorクラスから継承したリストと配列を使用している場合は、同様のイテレーターを記述できます。

    例4

    マクロにはさらに、コンパイル段階で任意のObjectScriptコードを実行し、マクロの代わりにその結果に置き換えるという別の機能があります。 コンパイル時間を示すマクロを作成してみましょう。

    ClassMethod Test4()
    {
          #Define CompTS ##Expression("""Compiled: " _ $ZDATETIME($HOROLOG) _ """,!")
          Write $$$CompTS
    }

    これは、次のINTコードに変換されます。

    zTest4() public {
          Write "Compiled: 18.10.2016 15:28:45",! }
    

    このメソッドが呼び出されると、ターミナルには次の行が表示されます。

    Compiled: 18.10.2015 15:28:45

    ##Expressionは、コードを実行して結果を置き換えます。 入力には、ObjectScript言語の次の要素を使用できます。

    • 文字列: "abc"
    • ルーチン: $$Label^Routine
    • クラスメソッド: ##class(App.Test).GetString()
    • COS関数: $name(var)
    • 上記の要素の任意の組み合わせ

    例5

    コンパイル時に、ディレクティブの後に続く式の値に応じてソースコードを選択するには、プリプロセッサディレクティブの#If、#ElseIf、#Else、#EndIfを使用します。 たとえば、次のメソッドがあるとします。

    ClassMethod Test5()
    {
        #If $SYSTEM.Version.GetNumber()="2016.2.0" && $SYSTEM.Version.GetBuildNumber()="736"
            Write "You are using the latest released version of Caché"
        #ElseIf $SYSTEM.Version.GetNumber()="2017.1.0"
            Write "You are using the latest beta version of Caché"
        #Else
            Write "Please consider an upgrade"
        #EndIf
    }

    Cachéバージョン2016.2.0.736では、このメソッドは次のINTコードにコンパイルされます。

    zTest5() public {
        Write "You are using the latest released version of Caché"
    }

    ターミナルには次のように表示されます。

    You are using the latest released version of Caché

    ベータポータルからダウンロードしたCachéを使用している場合、コンパイルされたINTコードは異なります。

    zTest5() public {
        Write "You are using the latest beta version of Caché"
    }

    ターミナルには次のように表示されます。

    You are using the latest beta version of Caché

    古いバージョンのCachéは、次のようにプログラムの更新を提案するINTコードをコンパイルします。

    zTest5() public {
        Write "Please consider an upgrade"
    }
    

    ターミナルには次のように表示されます。

    Please consider an upgrade

    この機能は、クライアントアプリケーションで古いバージョンと新しいバージョンとの互換性を保証したい場合に、Cachéの新機能が使用される可能性があるときなどに役立ちます。 プリプロセッサディレクティブの#IfDef#IfNDefは、順にマクロの存在と不在を検証することで、同じ目的を果たすことができます。

    まとめ

    マクロは、コンパイル段階で、頻繁に使用される構造を単純化することでコードを読みやすくして一部のアプリケーションのビジネスロジックを実装しやすくするため、ランタイム時の負荷を軽減することができます。

    次の内容

    次の記事では、アプリケーションにおけるマクロのより実用的な使用例について説明します。ロギングシステムです。

    リンク

  • 第2部: ロギング
  • 0
    0 355
    記事 Toshihiko Minamoto · 6月 21, 2021 9m read

    この記事では、syslogテーブルについて説明したいと思います。  syslogとは何か、どのように確認するのか、実際のエントリはどのようなものか、そしてなぜそれが重要であるのかについて説明します。  syslogテーブルには、重要な診断情報が含まれることがあります。  システムに何らかの問題が生じている場合に、このテーブルの確認方法とどのような情報が含まれているのかを理解しておくことが重要です。

    syslogテーブルとは?

    Cachéは、共有メモリのごく一部を使って、関心のある項目をログに記録しています。  このテーブルは、次のようなさまざまな名前で呼ばれています。

    • Cachéシステムエラーログ
    • errlog
    • SYSLOG
    • syslogテーブル

    この記事では、単に「syslogテーブル」と呼ぶことにします。

    syslogテーブルのサイズは構成可能です。  デフォルトは500エントリで、  10~10,000エントリの範囲で構成できます。  syslogテーブルのサイズを変更するには、システム管理ポータル -> システム管理 -> 構成 -> 追加設定 -> 詳細メモリに移動し、「errlog」行の「編集」を選択します。  そこにsyslogテーブルに必要なエントリの数を入力してください。

    syslogテーブルのサイズを変更する理由は?

    syslogテーブルが500エントリに構成されていると、501番目のエントリによって最初のエントリが上書きされ、そのエントリの情報は失われてしまいます。  これはメモリ内にあるテーブルであるため、出力を明示的に保存しない限りどこにも永続されません。  また、Cachéを停止した場合には、以下に説明する方法でエントリをconsole.logファイルに保存するように構成していない限り、すべてのエントリは失われてしまいます。

    Cachéが多くのエントリをsyslogテーブルに書き込んでおり、問題の診断目的でそのエントリを確認する場合、テーブルのサイズが十分に大きくなければ、エントリは失われてしまいます。  syslogテーブルのDate/Time列を見ると、テーブルが書き込まれた期間を判別できます。  その上で、どれくらいのエントリ数を設定するのかを決めることができます。  個人的には、エントリを失わない数で判断を誤る方が良いと思います。  このことについては、以下でより詳しく説明します。

    syslogテーブルの確認方法

    syslogテーブルの確認方法にはいくつかあります。

    1. Cachéターミナルプロンプトから、%SYSネームスペースで「Do ^SYSLOG」を実行する。
    2. Cachéターミナルプロンプトから、%SYSネームスペースで「do ^Buttons」を実行する。
    3. 管理ポータル -> システム操作 -> 診断レポートに移動する。
    4. -e1オプションでcstatを実行する。
    5. Cachehungを実行する。
    6. シャットダウン中にsyslogテーブルをconsole.logファイルに書き込むようにCaché を設定し、console.logファイルを確認する。  設定するには、システム管理ポータル -> 構成 -> 追加設定 -> 互換性に移動し、「ShutDownLogErros」行で「編集」を選択します。  Cachéのシャッドダウン中にsyslogのコンテンツをconsole.logに保存する場合は「true」、保存しない場合は「false」を選択します。

    syslogのエントリとは?

    syslogテーブルの例を以下に示します。  この例は、Cachéターミナルプロンプトで「^SYSLOG」を実行して得られたものです。

    %SYS>d ^SYSLOG
    
    Device:
    Right margin: 80 =>
    Show detail? No => No
    Cache System Error Log printed on Nov 02 2016 at  4:29 PM
    --------------------------------------------------------
    Printing the last 8 entries out of 8 total occurrences.
    Err  Process         Date/Time                             Mod     Line      Routine                             Namespace
    9     41681038     11/02/2016 04:44:51PM     93        5690     systest+3^systest         %SYS
    9     41681038     11/02/2016 04:43:34PM     93        5690     systest+3^systest         %SYS
    9     41681038     11/02/2016 04:42:06PM     93        5690     systest+3^systest         %SYS
    9     41681038     11/02/2016 04:41:21PM     93        5690     systest+3^systest         %SYS
    9     41681038     11/02/2016 04:39:29PM     93        5690     systest+3^systest         %SYS
    9     41681036     11/02/2016 04:38:26PM     93        5690     systest+3^systest         %SYS
    9     41681036     11/02/2016 04:36:57PM     93        5690     systest+3^systest         %SYS
    9     41681036     11/02/2016 04:29:45PM     93        5690     systest+3^systest         %SYS
    

    列の見出しを見ればエントリの各項目が何であるかは明確のようですが、それらについて説明します。

    Printing the last 8 entries out of 8 total occurrences

    これは、syslogテーブルエントリの一部ではありませんが、確認することは重要なので、ここで説明します。  この行から、いくつのエントリがsyslogテーブルに書き込まれたかがわかります。  この例では、Cachéが起動してから8つのエントリしか書き込まれていません。  エントリ数が少ないのは、これが私のテストシステムであるためです。  「Printing the last 500 entries out of 11,531 total occurrences(発生総数11,531件中最新の500件のエントリを出力)」と書かれていれば、多数のエントリが見逃されていることがわかります。  見逃されたエントリを確認する場合は、テーブルサイズは最大10,000に増やすか、より頻繁にSYSLOGを実行してください。 

    Err

    これは、関心のあるイベントについてログに記録される情報です。  エラーは必ずOSレベルで/usr/include/errno.h(Unix)から発生しているものと思われがちですが、実際には「必ず」ではなく、ほとんどの場合です。  ログに記録できるものであれば、何でも記録されます。  たとえば、診断アドホックのデバッグ情報、C変数の値、エラーコードの定義(10000を超えるもの)などを記録することができます。  どのように区別すればよいのでしょうか。  実際に、エントリのModLineに示される、Cコードの行を確認する必要があります。  つまり、InterSystemsに連絡を取らなければ、それが実際に何であるかを区別することはできません。  では、わざわざ確認する必要はあるのでしょうか。  errの意味を正確に知らずとも、ほかの情報を見ることで把握できることがたくさんあるからです。  エントリがたくさんある場合や、普段から目にするエントリとは異なるエントリがある場合には、InterSystemsに問い合わせることもできます。  syslogテーブルのエントリは、必ずしもエラー状態を示すものではないことに注意してください。

    Process

    これは、syslogテーブルにエントリを書き込んだプロセスのプロセスIDです。  たとえば、スタックしたプロセス、スピンし続けるプロセス、またはデッドプロセスがある場合、syslogテーブルに何か記録されていないかを確認できます。  記録されていれば、プロセスで障害が起きた理由の重要な手がかりになるかもしれません。

    Date/Time

    これは、エントリが書き込まれた日時です。  問題になった原因の手がかりを得るために、エントリの日時とシステムイベントの日時を相関することは非常に重要です。

    ModとLine

    Modは特定のCファイルに対応しており、Lineは、そのエントリをsyslogテーブルに書き込んだファイルの行番号です。  カーネルコードにアクセスできるInterSystemsの従業員のみが、これを検索できます。  このコードを調べるだけで、エントリに何が記録されたのかを正確に知ることができます。

    Routine

    syslogテーブルにエントリが書き込まれたときにプロセスが実行していたタグ、オフセット、およびルーチンです。  何が起こっているのかを理解する上で非常に役立ちます。

    Namespace

    これは、プロセスが実行していたネームスペースです。

    では、err 9がsyslogテーブルに書き込まれた理由をどのようにして知ることができますか?

    まず、示されているルーチンを確認します。  私の^systestルーチンは次のようになっています。

    systest  ;test for syslog post
             s file="/home/testfile"
             o file:10
             u file w "hello world"
             c file
             q
    

    syslogエントリでは、エントリが書き込まれた時に実行していたものはsystest+3だったと示されています。  この行は次のようになっています。

    u file w "hello world"
    

    プロセスがファイルに書き込もうとしていたため、これは実際にOSレベルのエラーである可能性があります。そこで、/usr/include/errno.hで9を探すと、次のようになっています。

    #define EBADF   9       /* Bad file descriptor                  */
    

    9はファイル関連であり、示されたコードの行はファイルに書き込もうとしていたため、これは実際にOSエラーコードであると推測するのが合理的です。 

    何がエラーになっているのかわかりますか?

    これを解決するために、まず、/homeディレクトリとtestfileファイルの権限を確認しました。  両方とも777となっていたため、ファイルを開いて書き込むのは可能なはずです。  そこでコードをよく見ると、エラーに気づきました。  10秒のタイムアウトの前にコロンを2つ付けていなかったこと、そしてOpenコマンドにはパラメーターを使用していなかったことに気づいたのです。  以下は更新したルーチンです。実際にエラーなしで終了し、ファイルに書き込みます。

    systest  ;test for syslog post
             s file="/scratch1/yrockstr/systest/testfile"
             o file:"WNSE":10
             u file w "hello world"
             c file
             q
    

    最後に

    syslogテーブルは、正しく使用すればデバッグに役立つ貴重なツールです。  使用するときは、次の点に注意してください。

    1. errは必ずしもオペレーティングシステムのエラーとは限りません。ログに記録できるものは何でも記録されます。  記録された内容については、InterSystemsにお問い合わせください。
    2. ログに記録されているその他の情報を使用して、何が起きているのかを判断します。  エラーと組み合わされたCOSコードの行から、それがOSのエラーであるかどうかについて、合理的に推測することができます。
    3. 解決できない問題がある場合は、syslogテーブルを確認しましょう。手がかりが見つかるかもしれません。
    4. Date/Time、エントリ数、および合計発生数を使って、syslogテーブルのサイズを増やす必要があるかどうかを判断します。
    5. システムがsyslogテーブルに何を記録しているのかを把握しておきましょう。エントリに何らかの変更があったり、新しいエントリや異なるエントリが記録されたことに気づくことができます。
    6. syslogテーブルのエントリは、必ずしも問題を示すものではありません。
    0
    0 566
    記事 Tomoko Furuzono · 6月 15, 2021 1m read

    これは、InterSystems FAQサイトの記事です。
     

    Windowsのコマンドプロンプトからターミナルを起動するには以下の様な操作を行います。

    実行イメージの場所に移動します。 

    cd c:\interssytems\IRIS\bin

      以下のコマンドを実行します。 
     ( インスタンス名のデフォルトは「IRIS」です。)

    iristerm.exe /console=cn_ap:<インスタンス名>[]

     コンソールターミナルは以下のように実行します。

    iris console <インスタンス名>


    コンソールターミナルの場合、そのままでは日本語を正しく表示できませんのでご注意ください。
    日本語を表示させるためには、以下の記事の内容の設定が必要です。
    (外部技術情報サイト)
    コマンドプロンプト起動時、自動的に文字コードをUTF-8にして日本語もちゃんと表示できるようにする方法

    0
    0 597
    記事 Toshihiko Minamoto · 1月 13, 2021 2m read

    デベロッパーの皆さん、こんにちは! InterSystems Package Manager (ZPM) は、素晴らしいツールですが、インストールせずにすぐ使用できたら、さらに便利です。

    それを実現する方法はいくつかあります。以下に dockerfile を使って ZPM をビルドした IRIS コンテナを用意する方法をご紹介します。

    リポジトリを作成し、そのdockerfile に数行のコードを記述しました。これを使えば、最新バージョンの ZPM をダウンロードし、インストールできます。 

    IRIS コミュニティエディション用のあなたの dockerfile にこれらのコマンドを追加すれば、ZPM がインストールされ、使用できるようになります。

    最新の ZPM クライアントをダウンロードするコマンドは以下の通り。

    RUN mkdir -p /tmp/deps \
     && cd /tmp/deps \
     && wget -q https://pm.community.intersystems.com/packages/zpm/latest/installer -O zpm.xml

    IRIS に ZPM をインストールするコマンドは以下の通り。

    " Do \$system.OBJ.Load(\"/tmp/deps/zpm.xml\", \"ck\")" \

    これで完了です!

    このリポジトリで ZPM を試すには、以下を実行します

    $ git clone https://github.com/intersystems-community/objectscript-zpm-template.git

    リポジトリをビルドして実行します

    $ docker-compose up -d
    Open IRIS terminal:
    $ docker-compose exec iris iris session iris
    USER>

    ZPM を読み出します

    USER>zpm
    zpm: USER>

    ウェブターミナルをインストールします

    zpm: USER>install webterminal
    webterminal]   Reload START
    [webterminal]   Reload SUCCESS
    [webterminal]   Module object refreshed.
    [webterminal]   Validate START
    [webterminal]   Validate SUCCESS
    [webterminal]   Compile START
    [webterminal]   Compile SUCCESS
    [webterminal]   Activate START
    [webterminal]   Configure START
    [webterminal]   Configure SUCCESS
    [webterminal]   Activate SUCCESS
    zpm: USER>

    是非ご活用ください!

    全体のプロセスはこちらの GIF をご覧ください。

    0
    0 238
    記事 Toshihiko Minamoto · 12月 7, 2020 10m read

        以前の記事では Arduino を使い始め、最終的には気象観測所のデータを表示できるようになりました。 この記事ではさらに掘り下げ、InterSystems Caché アプリケーションに対して RFID カードと Arduino を介した認証をセットアップします。

     

    認証の委任

    Caché には認証コードの書き込みを許可することで、認証を委任するための仕組みがあります。 この仕組みを有効にするには、次の手順を実行する必要があります。

  • ZAUTHENTICATE ルーチンにユーザー認証コードを記述します。 このルーチンにはユーザー名/パスワードの取得、それらの検証と権限の割り当て、パスワード変更、トークン生成の 4 つのエントリポイントがあります。 詳細については、以下をお読みください。
    1. Caché で委任認証を有効にします([SMP] → [System Administration] → [Security] → [System Security] → [Authentication/CSP Session Options] を開き、[Allow Delegated authentication] ボックスにチェックを入れて設定を保存します)。
    2. 関連するサービスかアプリケーションの委任認証を有効にします(前者の場合は [SMP] → [Menu] → [Manage Services] → [Service] → [Allowed Authentication Methods] → [Delegated] を選択 → [Save]、後者の場合は [SMP] → [Menu] → [Manage Web Applications] → [Application] → [Allowed Authentication Methods] → [Delegated] を選択 → [Save])。

    仕組み

    委任認証は、委任認証が有効になっているサービスや Web アプリケーションに対してユーザーが認証される際に発生します。

    1. ZAUTHENTICATE ルーチンが呼び出されます。 このルーチンのコードはユーザーによって書かれたものであり、OS への呼び出しを含む任意の Caché ObjectScript コードである可能性があります。
    2. 次のステップは、ZAUTHENTICATE の呼び出しが成功したかどうかによって決まります。
    • ZAUTHENTICATE の呼び出しが成功し、ユーザーが ZAUTHENTICATE で認証されたのが初めてだった場合は「委任されたユーザー」が作成されます。 ZAUTHENTICATE がロールやその他の権限をユーザーに割り当てた場合は、それらがユーザープロパティになります。
    • ZAUTHENTICATE の呼び出しが成功し、ユーザーが ZAUTHENTICATE で認証されたのが初めてではなかった場合はそのユーザーのレコードが更新されます。
    • ZAUTHENTICATE の呼び出しが成功しなかった場合、ユーザーはアクセスエラーを受け取ります。
    1. インスタンスとサービスで 2 要素認証が有効になっている場合は、ユーザーの電話番号と事業者の検索が開始されます。 これらの情報が入力されている場合は、2 要素認証が実行されます。 入力されていない場合は、ユーザーは認証されません。

    ユーザー情報の出処は?

    次のように、アプリケーション/サービスで有効になっている認証方法に応じた 2 つの認証方法があります。

    • 委任: ユーザー名/パスワードは ZAUTHENTICATE ルーチン(GetCredentials エントリポイント)から取得され、ZAUTHENTICATE を使用して検証されます(ユーザータイプ: 委任)。
  • 委任およびパスワード: ユーザー名/パスワードは GetCredentials から取得されますが、標準の Caché ツールを使用してチェックされます(ユーザータイプ: Caché)。
  • 次に、ZAUTHENTICATE ルーチンとそのエントリポイントを見てみましょう。

    ZAUTHENTICATE

    これはメインルーチンであり、次の 4 つのエントリポイントで構成されています。

    GetCredentials

    このエントリポイントはサービスで委任認証が有効になっている場合に呼び出され、ユーザーにユーザー名/パスワードの入力を求める代わりに呼び出されます。 このルーチンのコードは、ユーザー名とパスワードを(何らかの方法で)取得します。 その後、(このエントリポイントの外部で)受信したユーザー名とパスワードはユーザーから通常の方法で入力されたかのように認証されます。 ユーザー名とパスワードは、キーボードからの入力、API、外部デバイスを使用したスキャンなど、任意の方法で取得できます。 この記事では、RFID カードを使用した認証を実装します。

    このエントリポイントはステータスを返します。ステータスがエラーの場合は監査ログに記録され、認証試行は拒否されます。 ただし、エラーステータス $SYSTEM.Status.Error($$$GetCredentialsFailed) が返された場合は、例外的に通常のユーザー名/パスワードの入力が続きます。 シグネチャは次のとおりです。

    GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public { }

    説明:

    • ServiceName – 接続が確立されるサービスの名前
    • Namespace – ネームスペース(接続時に指定されている場合)
    • Username – ユーザー名
    • Password – パスワード
  • Credentials – 現在使用されていません
  • このエントリポイントの重要な機能について説明します。 委任認証とパスワード認証の両方がサービスやアプリケーションで有効になっている場合、ユーザー名とパスワードは GetCredentials エントリポイントを介して受信されますが、それらの情報は標準のパスワード認証に使用されます(ユーザーが手動で入力した場合と同じ)。また、認証が成功した場合のユーザーは委任ユーザーではなく通常の Cache ユーザーになります。

    ZAUTHENTICATE

    初回認証が成功すると、ZAUTHENTICATE はロールやその他のユーザープロパティを定義します。 初回認証以外の場合はプロパティが更新されます(例えば、Roles はログインのたびに指定する必要があります)。 そのために、Properties 配列のプロパティを定型化したコードで設定する必要があります。 シグネチャは以下のとおりです。

    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) Public { }

    Properties 配列の説明:

    • Properties("Comment") — コメント
    • Properties("FullName") — 氏名
    • Properties("NameSpace") — 初期ネームスペース
    • Properties("Roles") — カンマ区切りのロールのリスト
    • Properties("Routine") — 初期ルーチン
    • Properties("Password") — パスワード
    • Properties("Username") — ユーザー名
    • Properties("PhoneNumber") — ユーザーの電話番号
    • Properties("PhoneProvider") — 電話会社
  • Properties("AutheEnabled") — 標準の 2 要素認証を有効化します(この目的のために、$$$AutheTwoFactorSMS に等しい値を設定する必要があります)
  • ChangePassword

    ユーザーパスワードを変更するためのエントリポイントです。シグネチャは次のとおりです。

    ChangePassword(Username, NewPassword, OldPassword, Status) Public { }

    説明:

    • NewPassword — 新しいパスワード
    • OldPassword — 古いパスワード
  • Status — パスワード変更の結果
  • SendTwoFactorToken

    標準の 2 要素認証で使用されるものです。 リクエストの形式と認証トークンを指定します。 シグネチャは以下のとおりです。

    SendTwoFactorToken(Username, ServiceName,Namespace,Application,Credentials,SecurityToken,TwoFactorTimeout,UserPhoneNumber) Public { }
    

    説明:

    • Application — ユーザーが接続している CSP アプリケーションまたはルーチン
    • SecurityToken — ユーザーに送信されるトークン
    • TwoFactorTimeout — トークンの有効期限が切れる時間
  • UserPhoneNumber — ユーザーの電話番号
  • まずは簡単な例から始めましょう。Windows での Caché ターミナルを担う %Service_Console サービスは、ユーザー名とパスワードの入力をユーザーに要求します。 このサービスに対して委任認証を有効にしましょう。 以下は、ユーザーにユーザー名とパスワードの入力を要求する ZAUTHENTICATE ルーチン(%SYS ネームスペース内)です。

    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC {
        #Include %occStatus
        Quit $$$OK
    }
    GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public {
        #Include %occErrors
        #Include %occStatus
        Do ##class(%Prompt).GetString("USER:",.Username)
        Do ##class(%Prompt).GetString("PASS:",.Password)
        Quit $$$OK
    }

    ターミナルの場合、これは通常のユーザー名認証と同じように見えます。

    >USER: _SYSTEM
    >PASS: SYS

    RFID

    それでは、RFID による認証を見てみましょう。 考え方は単純で、Caché が暗号化されたユーザー名とパスワードをカードに書き込み、認証中に Caché がカードをスキャンして復号化し、受け取ったユーザー名とパスワードを認証に使用するというものです。

    まず、Arduino Uno と RFID-RC522 モジュールの回路図をご覧ください。

    MF522 ライブラリを使用した C のコードはここにあります。 このコードでは、COM ポート経由で次の 2 つのコマンドを受信できます。

    • Get – RFID カードのブロック 2 / 4 / 5 / 6 の内容が COM ポートに渡されます
  • Set@bloc2@bloc4@bloc5@bloc6 — ブロック 2 / 4 / 5 / 6 の値が受信したデータに置き換えられます
  • Caché 側には Arduino.Delegate クラスがあり、その中に次の 2 つの対応するエントリポイントがあります。

    • SetCredentials — ユーザー名とパスワードの入力を取得し、それをシステムに格納されているキーを使用して AES 暗号化で暗号化し、RFID カードに書き込みます。
  • GetCredentials — カードから暗号化テキストを受信して復号化し、ユーザー名、パスワード、および操作のステータスを返します。
  • また、GetCredentials を使用して Arduino.Delegated クラスを呼び出す ZAUTHENTICATE ルーチンは以下のとおりです。

    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC {
        #Include %occStatus
        Quit $$$OK
    }
    GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public {
        #Include %occErrors
        #Include %occStatus
        Quit ##class(Arduino.Delegated).GetCredentials(.Username, .Password)
    }
    

    これで準備完了です! 組み立て後のデバイスは次のようになります。

    次のようにターミナルでシステム暗号化キーを設定します(%SYS ネームスペースと Arduino.Delegated クラスを使用できる必要があります)。

    Do ##class(Arduino.Delegated).InitEncryption(Key, IV)
    

    ここで、Key は暗号化キー、IV は初期化ベクトルです。 これらは、ユーザー名とパスワードを暗号化するために使用されます。 コマンドを使用して認証するには、Arduino を Caché に接続し、カードに情報を書き込みます。

    Do ##class(Arduino.Delegated).SetCredentials("_SYSTEM", "SYS")
    

    適切なサービスまたは Web アプリケーション(端末やシステム管理ポータルなど)で委任認証とパスワード認証を有効にすると、カードを RFID カードリーダーにかざすことで認証できるようになります。

    考えられる機能強化

    • マネージド暗号化キーを使用してユーザー名とパスワードを暗号化すると、セキュリティを強化できます。
    • 2 要素認証を使用すると、セキュリティを強化できます。具体的には、先にユーザー名とパスワードのペアを取得してから、ユーザーに固有のキーが格納されているカードを読み取ります。 次に、受信したキーをシステムに格納されている特定ユーザーのキーで確認する必要があります。 任意のユーザーデータを格納する方法は、InterSystems のコミュニティで議論されています
  • それぞれ 15 文字を超えるユーザー名とパスワードを格納する機能を追加します。
  • まとめ

    柔軟性の高い Caché の認証システムを使えば、任意のユーザー認証ロジックを実装できます。

    リンク

    0
    0 1034
    記事 Toshihiko Minamoto · 9月 28, 2020 6m read

    InterSystems ハッカソンの時、Artem Viznyuk と私のチームは Arduino ボード(1 台)とその各種パーツ(大量)を所有していました。 そのため、私たちは活動方針を決めました。どの Arduino 初心者もそうであるように、気象観測所を作ることにしたのです。 ただし、Caché のデータ永続ストレージと DeepSee による視覚化を利用しました!

    デバイスの操作


    InterSystems の Caché は、次のようなさまざまな物理デバイスと論理デバイスを直接操作することができます。

    • ターミナル
    • TCP
    • スプーラー
    • プリンター
    • 磁気テープ
    • COM ポート
    • その他多数

    Arduino は通信に COM ポートを使用しているため、私たちの準備は万端でした。
    一般的に、デバイスの操作は次の5つのステップに分けることができます。

    1. OPEN コマンドでデバイスを現在のプロセスに登録し、デバイスにアクセスする。
    2. USE コマンドでデバイスをプライマリにする。
    3. 実際の操作を行う。 READ でデバイスからデータを受信し、WRITE でデータを送信する。
    4. もう一度 USE でプライマリデバイスを切り替える。
    5. CLOSE コマンドでデバイスを解放する。

    理論上はこうなりますが、実際はどうなのでしょうか?

    Caché からの点滅操作


    まず、私たちはCOM ポートから数値を読み取り、指定したミリ秒の間だけ LED に電力を供給する Arduino デバイスを作りました。

    回路:
    <div class="spoiler_text">
      <span class="confluence-embedded-file-wrapper"><img class="confluence-embedded-image confluence-external-resource" data-image-src="https://habrastorage.org/files/ade/d64/954/aded6495459b49a4a632d36001f7b5ce.jpg" src="https://habrastorage.org/files/ade/d64/954/aded6495459b49a4a632d36001f7b5ce.jpg" /></span>
    </div>
    
    C のコード(Arduino 用)
    <div class="spoiler_text">
      <pre><code class="cpp hljs"><span class="hljs-comment">/* Led.ino
    
    • COM ポートでデータを受信
    • led を ledPin に接続 *

    // led を接続するピン#define ledpin 8

    // 受信したデータバッファ String inString = "";

    // 開始時に 1 回だけ実行 voidsetup(){ Serial.begin(9600); pinMode(ledpin, OUTPUT); digitalWrite(ledpin, LOW); }

    // 無期限に実行 voidloop(){ // com からデータを取得 while (Serial.available() > ) { int inChar = Serial.read(); if (isDigit(inChar)) { // 同時に 1 文字// さらにデータバッファに追加 inString += (char)inChar; }

    <span class="hljs-comment">// 改行を検出</span>
    <span class="hljs-keyword">if</span> (inChar == <span class="hljs-string">'\n'</span>) {
        <span class="hljs-comment">// led の電源をオン</span>
        digitalWrite(ledpin, HIGH);
        <span class="hljs-keyword">int</span> time = inString.toInt();
        delay(time);
        digitalWrite(ledpin, LOW);
        <span class="hljs-comment">// バッファをフラッシュ</span>
        inString = <span class="hljs-string">""</span>;
    }
    

    }

    }

    最後に、COM ポートに 1000\n を送信する Caché のメソッドを掲載します。

    /// 1000\n を com ポートに送信ClassMethod SendSerial()
    {
        set port = "COM1"open port:(:::" 0801n0":/BAUD=9600)     // デバイスを開くset old = $IO// 現在のプライマリデバイスを記録use port  // com ポートに切り替えwrite$Char(10) // テストデータを送信hang1write1000 _ $Char(10) // 1000\n を送信use old // 古いターミナルに戻るclose port // デバイスを解放
    }


    «0801n0» は Com ポートにアクセスするためのパラメーターを含む文字列であり、ドキュメントに記載されています。 また、/BAUD=9600 は言うまでもなく接続速度です。

    このメソッドをターミナルで実行する場合、次のようになります。

    do ##class(Arduino.Habr).SendSerial()


    上記を実行しても何も出力されませんが、LED が 1 秒間点滅します。

    データ受信

    次はキーパッドを Cache に接続し、入力データを受信します。 これは、 認証委任と ZAUTHENTICATE.mac ルーチンを使用するカスタムユーザー認証として使用できます。

    回路:
    <div class="spoiler_text">
      <span class="confluence-embedded-file-wrapper"><img class="confluence-embedded-image confluence-external-resource" data-image-src="https://habrastorage.org/files/c19/196/d9b/c19196d9b9b64d51974b87c1e94a7650.png" src="https://habrastorage.org/files/c19/196/d9b/c19196d9b9b64d51974b87c1e94a7650.png" /></span>
    </div>
    
    C のコード
    <div class="spoiler_text">
      <pre><code class="cpp hljs"><span class="hljs-comment">/* Keypadtest.ino * 
    
    • Keypad ライブラリを使用します。
    • Keypad を rowPins[] と colPins[] で指定されているように
    • Arduino のピンに接続します。

    */

    // リポジトリ:// https://github.com/Chris--A/Keypad#include<Keypad.h>

    const byte ROWS = 4; // 4 行const byte COLS = 4; // 3 列// 記号をキーにマッピングしますchar keys[ROWS][COLS] = { {'1','2','3','A'}, {'4','5','6','B'}, {'7','8','9','C'}, {'*','0','#','D'} }; // キーパッドのピン 1-8(上下)を Arduino のピン 11-4 に接続します: 1->11, 2->10, ... , 8->4 // キーパッド ROW0, ROW1, ROW2, ROW3 をこの Arduino ピンに接続します byte rowPins[ROWS] = { 7, 6, 5, 4 };

    // キーパッド COL0, COL1, COL2 をこの Arduino ピンに接続します byte colPins[COLS] = { 8, 9, 10, 11 };

    // Keypad の初期化 Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

    voidsetup(){ Serial.begin(9600); }

    voidloop(){ char key = kpd.getKey(); // 押されたキーを取得if(key) { switch (key) { case'#': Serial.println(); default: Serial.print(key); } } }


    また、以下は同時に 1 行ずつ COM ポートからデータを取得するために使用される Caché メソッドです。

    /// 行末文字を検出するまで COM1 から 1 行を受信しますClassMethod ReceiveOneLine() As%String
    {
        port = "COM1"set str=""try {
            open port:(:::" 0801n0":/BAUD=9600)
            set old = $iouse port
            read str // 行末文字を検出するまで読み込みますuse old
            close port
        } catch ex {
            close port
        }
        return str
    }


    ターミナルで以下を実行します。

    write ##class(Arduino.Habr).ReceiveOneLine()

    また、(行末文字として送信される)# が押されるまで入力待ち状態になります。その後、入力されたデータがターミナルに表示されます。

    以上が Arduino-Caché I/O の基本です。これで、自前の気象観測所を作る準備が整いました。

    気象観測所

    これで気象観測所に着手できる状態になりました。 私たちはフォトレジスタと DHT11 温湿度センサを使用し、データを収集しました。

    回路:

    C のコード
    <div class="spoiler_text">
      <pre><code class="cpp hljs"><span class="hljs-comment">/* Meteo.ino * 
    
    • 湿度、気温、光の強度を記録して
    • それらを COM ポートに送信します
    • 出力例: H=1.0;T=1.0;LL=1; */

    // フォトレジスタのピン(アナログ)int lightPin = ;

    // DHT-11 のピン(デジタル)int DHpin = 8;

    // DHT-11 の一次データを保存する配列 byte dat[5];

    voidsetup(){ Serial.begin(9600); pinMode(DHpin,OUTPUT); }  voidloop(){ delay(1000); // 1 秒ごとにすべてを測定int lightLevel = analogRead(lightPin); //輝度レベルを取得

    temp_hum(); <span class="hljs-comment">// 気温と湿度を dat 変数に格納</span>
    <span class="hljs-comment">// And output the result</span>
    Serial.print(<span class="hljs-string">"H="</span>); 
    Serial.print(dat[<span class="hljs-number"></span>], DEC);   
    Serial.print(<span class="hljs-string">'.'</span>); 
    Serial.print(dat[<span class="hljs-number">1</span>],DEC);  
    Serial.print(<span class="hljs-string">";T="</span>); 
    Serial.print(dat[<span class="hljs-number">2</span>], DEC); 
    Serial.print(<span class="hljs-string">'.'</span>); 
    Serial.print(dat[<span class="hljs-number">3</span>],DEC);   
    Serial.print(<span class="hljs-string">";LL="</span>); 
    Serial.print(lightLevel);
    Serial.println(<span class="hljs-string">";"</span>);
    

    }

    // DHT-11 のデータを dat に格納voidtemp_hum(){ digitalWrite(DHpin,LOW); delay(30);
    digitalWrite(DHpin,HIGH); delayMicroseconds(40); pinMode(DHpin,INPUT); while(digitalRead(DHpin) == HIGH); delayMicroseconds(80); if(digitalRead(DHpin) == LOW); delayMicroseconds(80); for(int i=;i<4;i++) { dat[i] = read_data(); } pinMode(DHpin,OUTPUT); digitalWrite(DHpin,HIGH); }

    // DHT-11 からデータの塊を取得byte read_data(){ byte data; for(int i=; i<8; i++) { if(digitalRead(DHpin) == LOW) { while(digitalRead(DHpin) == LOW); delayMicroseconds(30); if(digitalRead(DHpin) == HIGH) { data |= (1<<(7-i)); } while(digitalRead(DHpin) == HIGH); } } return data; }


    このコードを Arduino に読み込んだ後、次の形式で COM ポートからデータの送信を開始しました。

    H=34.0;T=24.0;LL=605;


    説明:

    • H — 湿度(0〜100%)
    • T — 気温(摂氏)
    • LL — 輝度(0〜1023)


    新しい Arduino.Info クラスを作成するため、このデータを Caché に保存します。

     
    <div class="spoiler_text">
      <pre><code class="hljs cos"><span class="hljs-keyword">Class</span> Arduino.Info <span class="hljs-keyword">Extends</span> <span class="hljs-built_in">%Persistent</span>
    

    {

    Parameter SerialPort As%String = "com1";

    Property DateTime As%DateTime;

    Property Temperature As%Double;

    Property Humidity As%Double(MAXVAL = 100, MINVAL = 0);

    Property Brightness As%Double(MAXVAL = 100, MINVAL = 0);

    Property Volume As%Double(MAXVAL = 100, MINVAL = 0);

    ClassMethod AddNew(Temperature = 0, Humidity = 0, Brightness = 0, Volume = 0) { set obj = ..%New() set obj.DateTime=$ZDT($H,3,1) set obj.Temperature=Temperature set obj.Humidity=Humidity set obj.Brightness=Brightness/1023*100set obj.Volume=Volume write$SYSTEM.Status.DisplayError(obj.%Save()) }

    その後は Arduino からデータを受信し、次のように Arduino.Info クラスオブジェクトに変換するメソッドを作成しました。

    /// 次の形式で生データを受信します: H=34.0;T=24.0;LL=605;\n
    /// Arduino.Info オブジェクトに変換します
    ClassMethod ReceiveSerial(port = {..#SerialPort})
    {
        try {
            open port:(:::" 0801n0":/BAUD=9600)
            set old = $IO
            use port
            for {
                read x //1 行読み込む
                if (x '= "") {
                       set Humidity = $Piece($Piece(x,";",1),"=",2)
                    set Temperature =  $Piece($Piece(x,";",2),"=",2)
                    set Brightness =  $Piece($Piece(x,";",3),"=",2)
                    do ..AddNew(Temperature,Humidity,Brightness) // データを追加
                }
            }
        } catch anyError {
            close port
        }
    }


    最後に Arduino を接続し、ReceiveSerial メソッドを実行しました。

    write ##class(Arduino.Info).ReceiveSerial()

    このメソッドは、Arduino から無期限にデータを受信して保存します。

    データの視覚化


    デバイスを作成後、室外に設置して夜間にデータを収集するようにしました。

    翌朝には 36000 件以上のレコードを取得できましたので、そのデータを DeepSeeMDX2JSON サーバーサイド REST API と DeepSeeWeb ダッシュボードレンダラーを使用して視覚化することにしました。結果は次のとおりです。


    以下は輝度です。 5:50 頃に日の出をはっきりと確認できます。


    以下は気温と湿度のグラフです。

    湿度と気温の負の相関がはっきりとわかります。

    デモ

    こちらからアクセスできます。

    まとめ


    InterSystems Cachéを使用すると、多数の異なるデバイスと直接通信できます。 データ処理および視覚化ソリューションを迅速に開発できます。自前の気象観測所を作成して Caché に接続し、結果を視覚化するまでには約 4 時間かかりましたが、その大部分は回路の設計と C のコーディングに費やした時間です。


    »ドキュメント
    »GitHub リポジトリ

    0
    0 924
    記事 Hiroshi Sato · 6月 29, 2020 2m read

    Config.Configurationクラス、SYS.Databaseクラスのメソッドを使用して、ネームスペース・データベースの作成及び登録をターミナルから実行することができます。
    以下はデータベースファル/CacheDB/AAA/cache.datを作成し、構成ファイル(cache.cpf)にデータベース AAA、及び、ネームスペースAAAの登録を行う一連の実行例です。 *実行は、%SYSネームスペースで行って下さい。*
     

    Set Directory="/CacheDB/AAA/"Setx=$ZF(-100, "/shell", "mkdir", Directory)
     Set db=##Class(SYS.Database).%New()
     Set db.Directory=Directory
     Set status=db.%Save()
     Set DBName="AAA"Set status=##class(Config.Configuration).AddDatabase(DBName,Directory)
     Set NSName=DBName
     Set status=##class(Config.Configuration).AddNamespace(NSName,DBName)
    3
    0 768