デバイスドライバの開発

Wolfram言語に組み込まれているWolfram Device Frameworkは,外部デバイスを表す記号的オブジェクトを作成し,デバイスとのインタラクトを合理化し,デバイスドライバのオーサリングを簡易化する.フレームワークの「デバイス」とは,温度センサーのような実際のデバイス,もしくはシリアルポートのようなポートのカプセル化のどちらも意味する.このチュートリアルは上級ユーザやデバイスドライバの開発のためのフレームワークの内部構造を説明する.ユーザレベルでのデバイスとのインタラクトの詳細については「接続デバイスを使う」を参照のこと.
ほとんどのデバイスでは,フレームワークを構成する関数は実際のデバイスプログラミングやハードウェアとの低レベル通信と直接関係しているわけではなく,場合によって異なる.例えば,ある実装ではCで低レベルプログラムを書き(あるいはサードパーティーで提供され),WSTP APIを使ってCインターフェースをパッケージレベルのWolfram言語関数に示す.これらの関数はフレームワークによって適切に接続され,Wolfram言語のデバイスドライバを作成する.別の実装では, 低レベルのCプログラミングを完全に避け,Wolfram言語の .NET/Link 機能を使ってドライバレベルのWolfram言語関数内からデバイスとインタラクトすることもある.フレームワークが実行するのは,これらの関数を統合しユーザレベルの関数の集合を使ってデバイスとインタラクトする統合された方法を提供することである.
概要
デバイスドライバを作成しデバイスとの低レベルのインタラクトをカプセル化するための開発者レベルの関数は,DeviceFramework`コンテキストに集められている.「デバイスフレームワーク関数」をご参照いただきたい.デバイスドライバの中心となっているのは,DeviceClassRegister関数である.
DeviceFramework`DeviceClassRegister["class",opts]
操作がオプション opts により定義される,指定されたクラスに対するデバイスドライバを登録する
DeviceFramework`DeviceClassRegister["class","parent",opts]
オプション opts により定義される操作以外は parent と同じクラスを登録する
デバイスドライバの作成
DeviceClassRegister["class",opts]の1つの引数が通常のドライバを生成する.また,DeviceClassRegister["class","parent",opts]の2つの引数が基本的な継承を実装し,定義を繰り返さなくても親クラスが拡張できるようにする.
次のドライバは読込み操作だけを実装する.このクラスのデバイスは書き込めない:
書込み操作で親クラスを拡張する:
子クラスのデバイスは読み書きが可能である:
DeviceClassRegisterのオプションはさまざまなデバイスやドライバの特性を決定し,クラスのデバイスを発見するためにフレームワークが呼び出す関数を定義し,一般のデバイスで行うことが想定されている操作を実行する.その操作には,デバイスとの接続開始,デバイスの設定,デバイスからの読込み,デバイスへの書込み,コマンドの実行,デバイスとの接続の解除,外部リソースの解放が含まれる.
オプション
デフォルト値
"FindFunction"{}&
デバイスを発見する方法
"OpenFunction"None
デバイスを開くときに実行する関数
"ConfigureFunction"None
デバイスを設定する方法
"ReadFunction"None
デバイスから読み込む方法
"ReadBufferFunction"None
デバイスバッファから読み込む方法
"WriteFunction"None
デバイスに書き込む方法
"WriteBufferFunction"None
デバイスバッファに書き込む方法
"ExecuteFunction"None
デバイスでコマンドを実行する方法
"ExecuteAsynchronousFunction"None
コマンドを非同期で実行する方法
"CloseFunction"None
デバイスを閉じるときに実行する関数
"Properties"{}
標準化された特性
"DeviceIconFunction"None
デバイスオブジェクトに対するアイコンを生成する方法
"DriverVersion"Missing["NotAvailable"]
ドライバのバージョン番号
重要なデバイスドライバオプション
より一般的なワークフローは,デバイスを開く前にデバイスマネージャ(初期化オブジェクトとそのハンドル)を作成し,新しいデバイスオブジェクトをユーザに見せる前にデバイスを事前に設定することである.初期化オブジェクトは,デバイスが閉じられた後の解放段階で処分される.
あるいは,標準化されたデバイス特性を設定して取得し,ネイティブのデバイス特性とメソッドを示し,そのデバイスに関連付けられた入力および出力ストリームへのアクセスをユーザに与えるハンドラを定義することも可能である.デバイスは他のカスタムハンドラを指定することも,クラスの複数のドライバを生成するポリシーを定義することもできる.
オプション
デフォルト値
"OpenManagerFunction"None
デバイスマネージャを開くために実行する関数
"MakeManagerHandleFunction"Identity
デバイスマネージャのハンドルを生成する方法
"PreconfigureFunction"{}&
新しいデバイスを事前に設定する方法
"ReleaseFunction"Null&
デバイスマネージャオブジェクトを処分する方法
"ReadTimeSeriesFunction"Automatic
DeviceReadTimeSeriesのハンドラ
"StatusLabelFunction"Automatic
デバイスオブジェクトの状態ラベルを生成する方法
"NativeProperties"{}
ネイティブの特性
"NativeMethods"{}
ネイティブのメソッド
"GetPropertyFunction"DeviceFramework`DeviceGetProperty
標準化された特性を得る方法
"SetPropertyFunction"DeviceFramework`DeviceSetProperty
標準化された特性を設定する方法
"GetNativePropertyFunction"Automatic
ネイティブの特性を得る方法
"SetNativePropertyFunction"Automatic
ネイティブの特性を設定する方法
"NativeIDFunction"None
デバイスのネイティブIDを得る方法
"OpenReadFunction"None
デバイスに関連付けられた入力ストリームを開く方法
"OpenWriteFunction"None
デバイスに関連付けられた出力ストリームを開く方法
"DeregisterOnClose"False
デバイスが閉じられた後にデバイスの登録を解除するかどうか
"Singleton"Automatic
複数のデバイスに対する生成ポリシー
高度なデバイスドライバオプション
DeviceClassRegisterの詳細は次のセクションで説明する.他のWolfram言語オプションでそうであるように,DeviceClassRegisterのオプションはいずれも必須ではないので,ドライバの実装において通常多くは省略される.
例として,デバイス読込み操作を実行することのできる,ファイルにパッケージ化された簡単なデバイスのドライバを挙げる.
BeginPackage["MyDevice`"]

Begin["`Private`"]

read[__] := XXXXX

DeviceFramework`DeviceClassRegister["MyDevice", "ReadFunction" read]

End[]

EndPackage[]
基本的なドライバファイルMyDevice.m
フレームワークが自動的に発見できるようにするために,class に対するDeviceClassRegister["class",]文はWolfram言語パッケージの class.mの中になければならない.DeviceClassRegisterの最初の文字列の引数はドライバファイルの名前と一致していなければならないという名前付け規則もある.さらに,ドライバは以下のいずれかの場所に置かれなければならない.
ドライバは上の規則に従っていなくても使用することはできるが,Get等でWolfram言語セッションに明示的にロードされていなければならない.
特定のプラットフォーム上でドライバが利用できるようにするためには,DeviceClassRegister[]文をIfで囲む必要がある.
以下のドライバはMac OS Xでのみ利用可能になる:
デバイスドライバオプション
DeviceClassRegisterのオプションはデバイスドライバオプションとも呼ばれるが,これを使うと自分のデバイスに合ったドライバが生成できる."Function" (ドライバ関数)という言葉を含む名前を持つドライバオプションは,概してユーザレベルの関数に対応している.例えば,DeviceConfigureが実行される間に"ConfigureFunction"が呼ばれ,FindDevices"FindFunction"が呼ばれる,等である.しかし,1つ以上のドライバ関数にまたがるユーザレベル関数もある.その例としてDeviceOpenで実行されるデバイスオープンシーケンスには"OpenManagerFunction""MakeManagerHandleFunction""OpenFunction""PreconfigureFunction"がある.
多くのドライバ関数に与えられる第1引数は形式{ihandle, dhandle}のリストである.ここで,ihandle は初期化オブジェクトのハンドル(またはマネージャハンドル."MakeManagerHandleFunction"を参照),dhandle はデバイスハンドル("OpenFunction"を参照)である."ConfigureFunction""ReadFunction"をはじめ他のデバイス関数でも使われているこの方法により,必要なハンドルはどれでも使え,別のハンドルを省略することができるようになる.例えば ihandle が必要な場合,ドライバ関数はf[{ih_, _}, args___]:=(* use ih *)として実装することができる.dhandle だけを使うためには,f[{_, dh_}, args___]:=に対する規則を与えることができる.両方のハンドルを省略するためにはf[_, args___]:=を使う等する.そのような使用法の例は以下で多数見ることができる.
ドライバ関数に与えられる残りの引数は,通常親のトップレベル関数(リストラッパから取られたものの可能性がある)から渡される.例えばDeviceRead[dev,param]DeviceRead[dev,{param}]の両方では,"ReadFunction"f の実装は f[handles,param]として呼出しを受ける.DeviceRead[dev,{param1,param2,}]では,関数は f[handles,param1,param2,]として呼ばれる.DeviceRead[dev]では呼出しは f[handles]という形式になる.親について文書化されている引数の可能な組合せすべてが想定される.組合せが意味をなさない場合は適切なエラーメッセージを出力する.もし失敗したら,このチュートリアルで特に言及されない限りドライバ関数は$Failedを返すことが想定される.
エラーを報告する場合,ドライバがユーザから見えるシンボルをエキスポートするならば,メッセージをそのようなシンボルと関連付けるのが普通である.あるいは,クラスのメッセージを特殊シンボルDeviceFramework`Devices`class に割り当てることができる.このチュートリアルではその例が紹介してある.詳細は「メッセージ」をご覧いただきたい.
もちろんドライバが他の関数を実装しない場合は,その関数のエラーメッセージについて考える必要はない.メッセージはフレームワークにより生成される.
このダミードライバはどのドライバ関数も実装しない:
ダミーデバイスを開くことができるが,それに対する特定の操作を実行しようとするとエラーになる:
このチュートリアルでは,ほとんどの例題でトップレベルのWolfram言語コマンドのみが使われている.これは,読者が例題を再評価し,特定のデバイスなしでフレームワークの使い方が学べるようにすることを意図したためである.もちろん実際のドライバは外部プログラムも呼び出すであろう.概要は「外部プログラムを呼び出す」に,通常の実装は以下の「例題」セクションに記載されている.
このチュートリアルの例題の他,Wolfram言語のドキュメントで言及されているデモドライバの実装もご一読いただきたい.例えば,"ExecuteAsynchronousFunction"の実装方法はDeviceExecuteAsynchronousのデモドライバを見るとよい.
"ExecuteAsynchronousFunction"のデモ実装を調べるためには,次のコマンドを実行する:

"FindFunction"

デバイスを発見可能にするためには,ドライバは"FindFunction"オプションを実装する必要がある."FindFunction"f は,指定されたクラスのデバイスを見付けるために f[]FindDevices[class]で呼び出されるよう指定する.この関数 f は引数を受け付けず,形式{{arg11,arg12,},{arg21,arg22,},}の出力を返す.ここで{argi1,argi2,}は,デバイス番号 i を開くためにDeviceOpen[class,{argi1,argi2,}]に与えることのできる引数のリストである.
関数 f により返される一般的な引数は,ポート名,GPIBアドレス,カメラ名である.引数なしでデバイスを開くことができるならば,f{{}}&を返す.
ドライバが"FindFunction"を実装すると,最初に明示的にクラスのデバイスを開かなくてもそのデバイスを使うこともできる.
適切な"FindFunction"を持たないデバイスは自動的に発見可能にならない:
そのようなクラスのデバイスは,使う前に開かなければならない:
以下のドライバは"FindFunction"オプションを与えることで前のドライバを拡張している:
このクラスのデバイスはフレームワークにより自動的に開くことができる:
デバイスは明示的に閉じられるまで,要求された操作を実行した後も開いたままである:

"OpenManagerFunction"

"OpenManagerFunction"f は,初期化オブジェクト(デバイスマネージャとも呼ばれる)を生成するために,f[args] がデバイスオープンシーケンスの最初に呼び出されるよう指定する.args はユーザがDeviceOpen["class",{args}]に与えるものと同じである.戻り値はデバイスの寿命の最後の非初期化段階で"ReleaseFunction"に渡される.デバイスが閉じる前にDeviceInitObjectを使うと初期化オブジェクトをいつでも取り戻すことができる.
外部プログラムとのWSTP接続を開くとき等に,"OpenManagerFunction"を指定したいと思うだろう.この場合,この関数はLinkObjectオブジェクトを返すが,これは"ReleaseFunction"の中のLinkCloseで閉じることができる.
このドライバは,クラスの中の開いたデバイスの数を追跡する:
デバイスが開くと(閉じると),カウンタは増加する(減少する):

"MakeManagerHandleFunction"

"MakeManagerHandleFunction"f を指定すると,初期化オブジェクト(マネージャハンドル)の別のハンドルを生成することができる.関数 f[obj]"OpenManagerFunction"の後に呼ばれ,"OpenManagerFunction"によって生成された初期化オブジェクト obj が与えられる.戻り値はマネージャハンドルとなることが想定される."MakeManagerHandleFunction"がない場合,マネージャハンドルが初期化オブジェクト自体になる.
マネージャハンドルは第一引数の一部として多数のドライバ関数に与えられる.これ以外の場合はDeviceManagerHandleでハンドルを取得することができる.
マネージャハンドルの例として,ソケットハンドルや.NETオブジェクトがある.

"OpenFunction"

"OpenFunction"f で指定される関数は,ユーザのDeviceOpenの呼出しに応答してフレームワークによって呼ばれるメインの関数である. f[ihandle,args]"MakeManagerHandleFunction"によって生成されたマネージャハンドル ihandle と,ユーザがDeviceOpen["class",{args}]に提供する引数 args を受け取る.戻り値はデバイスハンドルと呼ばれる.このハンドルはマネージャハンドルとともに多数のドライバ関数に与えられる.そうでない場合はDeviceHandleで取り戻すことができる.
このフレームワークは"OpenFunction"によって返されるデバイスハンドルと,DeviceOpenによって返されるトップレベルのDeviceObjectとの間に1対1の対応を維持しようとする.それゆえ,ドライバハンドルは一意であるか,少なくとも自分で制御できない他のドライバによって生成されるハンドルと一致しないようにすることをお勧めする.その簡単な方法として,CreateUUIDを使ってハンドルを生成するというものがある.あるいは"com.company.class"[ ]という形式でハンドルを返すとか,同様な方法を使うとかすることができる.
このクラスのデバイスはオープン引数を出力する.ihandle パラメータは使われない:
デバイスハンドルを取り戻し,それを使ってデバイスオブジェクトを戻す:
"OpenFunction"が省略されると,フレームワークはクラスの新しいデバイスすべてにランダムなデバイスハンドルを割り当てる.
このドライバは"OpenFunction"を実装しない:
フレームワークによって提供されるデフォルトのデバイスハンドルをまだ使用することができる:

"OpenReadFunction"

"OpenReadFunction"f は,"OpenFunction"の後,デバイスに関連付けられた任意の入力ストリームを開くためのオープンシーケンスの途中で f[{ihandle,dhandle},args]が呼び出されるよう指定する.第一引数の{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.引数 argsDeviceOpen["class",{args}]に与えられるものである.戻り値は入力ストリームオブジェクトか入力ストリームオブジェクトのリストでなければならない.
このデモドライバはデバイスの入力ストリームと出力ストリームのサンプル実装を提供する:

"OpenWriteFunction"

"OpenWriteFunction"f は,"OpenFunction"の後,デバイスに関連付けられた任意の入力ストリームを開くためのオープンシーケンスの途中で f[{ihandle,dhandle},args]が呼び出されるよう指定する.第一引数の{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.引数 argsDeviceOpen["class",{args}]に与えられるものである.戻り値は出力ストリームオブジェクトか出力ストリームオブジェクトのリストでなければならない.
このデモドライバはデバイスの入力ストリームと出力ストリームのサンプル実装を提供する:

"PreconfigureFunction"

"PreconfigureFunction"f で指定される関数は,デバイスのオープンシーケンスを終了させる.ユーザに戻される寸前のデバイスオブジェクト dev では,f[dev]DeviceOpenの呼び出しの最後に確実に呼び出される.これはデバイスのトップレベルのデバイス特性(以前デバイス上で設定されていた場合)に再適用する,あるいはクラス特性(ドライバによって定義されている場合)を設定する初期設定の前である."PreconfigureFunction"はドライバ,"PreconfigureFunction"自身,"OpenFunction",あるいはデバイスオープンシーケンスの他の関数によって設定された特性のリストを返す.これらの特性はDeviceOpenが終了するまでフレームワークによってそれ以上変更されない.AllあるいはNoneも返される.
このドライバは,デバイスIDに基づいたデバイス特性"parity"を設定する:
"PreconfigureFunction"はデバイスの開閉状態に対する状態ラベルを設定するのに便利な場所でもある.
このドライバは20秒後に開いているデバイスを自動的に閉じ,閉じるまでの時間を表示する:

"ConfigureFunction"

"ConfigureFunction"ff[{ihandle,dhandle},args] がデバイスを設定するトップレベルのコマンドDeviceConfigure[dev,{args}]への応答として呼ばれるように指定する.第1引数の{ihandle,dhandle}ihandle はマネージャハンドル,dhandle はデバイスハンドル)は,多数のドライバ関数に共通である.この関数の戻り値は無視される.
このドライバの"ConfigureFunction"はトップレベルの特性を設定する:
デバイスを開き,DeviceConfigureを呼ぶことでそれを設定する:
以下が特性の新しい値である:

"ReadFunction"

"ReadFunction"f は,デバイスからデータを読み出すトップレベルのコマンドDeviceRead[dev,{args}]に応答して f[{ihandle,dhandle},args]が呼び出されるよう指定する.第一引数の{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.この関数の戻り値はDeviceRead[]コマンドの出力としてユーザに渡される.
このクラスのデバイスは文字列から連続した文字を読み込む.各デバイスに対する現在の文字列位置は,デバイスハンドルをキーとして連想に保存されている.キーは"OpenFunction"の連想に加えられ,"CloseFunction"で削除される:
デバイスを開き,それから文字を読み込む:
別のデバイスを開き,いくつかの文字を読み込む:
開いているデバイスそれぞれに対する現在の読込みカウンタは記憶される:
最初のデバイスから読み込むと,デバイスが完全に文字列を読み込むまで再び次の文字を与える:
デバイスを閉じるとそのカウンタがクリアされる:
"ReadFunction"の実装は強力な引数チェックが最も役に立っているものの一つである.要求されたエラーメッセージは,必要に応じてDeviceFramework`Devices`コンテキストのクラス名に対応するシンボルに割り当てることができる.
このドライバにより,デバイスは実数あるいは整数の乱数値を読み,初歩的な引数チェックを実装できるようにする:
デバイスを開き,正しい引数でDeviceReadを呼び出す:
不正な引数でDeviceReadを呼ぶと,エラーメッセージが出力される:

"ReadBufferFunction"

"ReadBufferFunction"f は,デバイスバッファからデータを読み出すためのトップレベルコマンドDeviceReadBuffer[dev,args]に応答して f[{ihandle,dhandle},]が呼ばれるよう指定する.第一引数{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.関数 f に渡される引数は以下の通りである.
DeviceReadBuffer[dev]
f[{ihandle,dhandle},Automatic]
DeviceReadBuffer[dev,crit]
f[{ihandle,dhandle},crit,Automatic]
DeviceReadBuffer[dev,crit,params]
f[{ihandle,dhandle},crit,params]
"ReadBufferFunction"の可能なシグネチャ
f の戻り値はDeviceReadBuffer[]コマンドの出力としてユーザに渡される.
"ReadBufferFunction"を使う例には,シリアルデバイスからバイト列を読むことがある.

"WriteFunction"

"WriteFunction"f は,デバイスにデータを書き込むためのトップレベルコマンドDeviceWrite[dev,{args}]に応答して f[{ihandle,dhandle},args]が呼ばれるよう指定する.第一引数{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.この関数の戻り値は無視される.
このドライバはDeviceWriteへのすべての呼出しに対して新しいノートブックを生成する:
デバイスを開き,書込み操作を実行する:
デバイスがいくつかのパラメータの値を書く場合,その値を連想あるいは規則の集合としてDeviceWriteに与えるのが一般的である.そのような規則を解析することは"WriteFunction"の実装になる.
"WriteFunction"の規則を解析する:
デバイスを開き,指定されたテキスト,タイトル,空白のセクションセルを含む新しいノートブックを生成する操作を実行する:

"WriteBufferFunction"

"WriteBufferFunction"f は,デバイスバッファにデータを書き込むためのトップレベルコマンドDeviceWriteBuffer[dev,{args}]に応答して f[{ihandle,dhandle},args]が呼ばれるよう指定する.第一引数{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.この関数の戻り値は無視される.
"WriteBufferFunction"を使う例としては,シリアルデバイスにバイト列を書き込むことが挙げられる.

"ExecuteFunction"

"ExecuteFunction"f は,デバイス上でコマンドを実行するためのトップレベルコマンドDeviceExecute[dev,"command",{args}]に応答して f[{ihandle,dhandle},"command",args]が呼ばれるよう指定する.第一引数{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.この関数の戻り値はDeviceExecute[]コマンドの出力としてユーザに渡される.

"ExecuteAsynchronousFunction"

"ExecuteAsynchronousFunction"f は,デバイス上で指定されたコマンドの非同期実行を開始するためのトップレベルコマンドDeviceExecuteAsynchronous[dev,"command",{args},fun]に応答して f[{ihandle,dhandle},"command",args,fun]が呼ばれるよう指定する.第一引数{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.この関数の戻り値はDeviceExecute[]コマンドの出力としてユーザに渡される.事象が起きたとき,関数 f はユーザ指定のハンドラ関数 fun をダウンストリームで渡して実行されるようにする.f も戻り値(AsynchronousTaskObject[]あるいは同様のオブジェクト)は,DeviceExecuteAsynchronous[]コマンドの出力としてユーザに渡される.
command がサポートされない場合,関数 fMissing[]オブジェクトを生成するか,単に未評価で返すかしなければならない.どちらの場合でも,フレームワークは適切なメッセージを生成する.他のエラーの場合,f は適切なメッセージを出力し$Failedを返す.これは特に,指定されたコマンドが与えられた引数で実行できない場合に起こる.
command"Read""Write""ReadBuffer""WriteBuffer",あるいはドライバでサポートされる任意の他のコマンドである.
このドライバはURLのコンテンツを非同時でファイルに保存する:
進行関数と進行バーを生成する:
非同期タスクを開始し,進行状況を見る:
フレームワークは,サポートされない非同期操作にエラーメッセージを出力する:

"CloseFunction"

"CloseFunction"f は,デバイスを閉じ関連したリソースを解放するためのトップレベルコマンドDeviceClose[dev]に応答して f[{ihandle,dhandle}]が呼ばれるよう指定する.第一引数{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.この関数の戻り値はDeviceExecute[]コマンドの出力としてユーザに渡される.成功した場合はNullとなる.
"CloseFunction"の実装には通常,"ReleaseFunction"で閉じられたものを除き,すべてのポートとソケットを閉じ,他のリソースを解放することが含まれる."OpenReadFunction""OpenWriteFunction"で開かれたストリームは,Closeを使って閉じられるならばフレームワークによって自動的に閉じられる.そうでない場合は"CloseFunction"でストリームを閉じることができる.
閉じられたデバイスは,現行のWolfram言語セッションでまだ利用可能である.デバイスが閉じられたら,"DeregisterOnClose"を使って完全にデバイスを削除する.

"ReleaseFunction"

"ReleaseFunction"f は,"CloseFunction"の後,"OpenManagerFunction"によって生成された初期化オブジェクト obj を破壊し,必要に応じて残った他のクリーンアップを実行するDeviceCloseの操作中に f[obj]が呼ばれるよう指定する.この関数の戻り値は$Failedでない限り無視される.
WSTPの外部プログラムとの接続を開くために"OpenManagerFunction"を使った場合は,"ReleaseFunction"で接続を閉じることができる.

"DeregisterOnClose"

"DeregisterOnClose"Trueは,DeviceCloseがすべての外部リソースを解放するだけでなく,現行のWolfram言語セッションからデバイスを完全に削除するよう指定する.デフォルトの"DeregisterOnClose"Falseでは,デバイスはまだ利用可能であり厳密に同じパラメータで再開することができる.
"DeregisterOnClose"オプションのデフォルト値では,閉じられたデバイスはまだ利用可能である:
フレームワークは,デバイスを開くのに使われたパラメータを追跡する:
開く引数を指定しなくてもデバイスを再開することができる:
このクラスのデバイスは,閉じられた後自動的に登録が解除される:

"Singleton"

"Singleton"オプションはフレームワークがどのようにDeviceOpen["class",{args}]コマンドを処理するかを指定し,複数デバイスの生成ポリシーを設定する."Singleton"オプションに可能な値は以下の通りである.
Automatic
新しい引数集合に対する新しいデバイスを返す
True
常に同じデバイスを返す
False
常に新しいデバイスを返す
crit
crit[dev,{args}]を評価してTrueとなるような登録されたデバイスの最初のものを返す
あるクラスの複数デバイスに対する生成ポリシー
デフォルト値のAutomaticSameQ[DeviceOpenArguments[#1],#2]&に等しい基準 crit と等価である.
"Singleton"Automaticでは,DeviceOpenは新しい引数集合で呼ばれたときだけ新しいデバイスを返す:
同じ引数でDeviceOpenを呼ぶと,同じデバイスが与えられる:
"Singleton"Trueでは,フレームワークはDeviceOpenの最初の呼び出しの後はすべての呼出しを無視し,異なる引数で呼ばれたとしても同じデバイスを返す:
"Singleton"Falseでは,DeviceOpenは同じ引数で呼ばれたとしても新しいデバイスを返す:
重要なことは,DeviceOpenの応答として新しいデバイスを開く必要がない場合,フレームワークは通常"OpenFunction"を含むドライバ関数のオープニングシーケンスを実行するということである.指定された基準 critTrueを返す場合はオープニングシーケンスを実行するが,引数 args はデバイスが前に開かれるときに使った引数と完全には一致しない.このため,異なる引数の集合に対して同じデバイスを再設定する.
以下のドライバでは,DeviceOpenはすべての異なる第一引数に対する新しいデバイスを返す:
異なる入力引数で最初のデバイスを再設定する:
このデモデバイスはより強力な"Singleton"オプションの実装を持つ:

"Properties"

オプション"Properties"{"property1"value1,"property2"value2,}は新規に生成されたデバイスの標準化された(トップレベルの)特性を通常決定するクラス特性を指定する.標準化されたデバイス名は文字列として与えられる.ドライバはネイティブ特性も指定することができる.

"GetPropertyFunction"

"GetPropertyFunction"f は,デバイス dev の標準化された特性 p の値をクエリするために f[dev,p]が呼ばれるよう指定する.戻り値はユーザに渡される."GetPropertyFunction"のデフォルトはDeviceGetPropertyであり,関数 f の内部で呼ぶことができる.
以下のドライバは標準化された特性が読まれたときのタイムスタンプ出力する:
デバイスを開き,特性を読み込む:
特性値を読み込むことでカウンタの値が増える:
デバイスに保存されている特性値をクエリするためには,一般に"GetPropertyFunction"を定義する.

"SetPropertyFunction"

"SetPropertyFunction"f は,デバイス dev の標準化された特性 p を値 val に設定するために f[dev,p,val]が呼び出されるよう設定する.f の戻り値は無視される."SetPropertyFunction"のデフォルトはDeviceSetPropertyであり,関数 f 内部で呼ぶことができる.
デバイスの特性値を変更するためには通常"SetPropertyFunction"を使うが,無効な特性値を除去したり,いくつかの特性を読み出し専用にしたり,標準化された特性をネイティブ特性にリンクしたり等するために同じ関数を使うこともできる.
以下のドライバは普通の特性"p"と読み出し専用特性"r"を生成する.
デバイスを開き特性"p"を変更する:
"r"の値は変更することができない:

高度なトピック:トップレベルの特性をデバイスと同期したままにする

やや高度な練習として,標準化された特性をデバイス上で独立して変更することのできる特性と同期したままにするドライバを開発することができる.
この場合,外部の値は変数$externalを使ってモックアップされる.実際のドライバでは,外部特性設定子(getter)を呼び出して特性を変更する(特性の値を得る):
set関数はトップレベルの計算にはデフォルトのハンドラを使う.また,デバイスが開いていると,その特性値を設定するデバイスと通信もする:
開いたデバイスでは,get関数はデバイス上の特性値をクエリし,得る値をトップレベルのインターフェースに保存する.デバイスが閉じている場合は,関数は単純にトップレベルの値を返す:
以下はドライバを設定する.この場合の"PreconfigureFunction"はドライバに,特性"p"の設定を飛ばすよう指示する:
デバイスを開き,その特性をチェックする.その値は変数$externalから来る(つまり値"x"は無視される):
特性値を変更する.外部変数も変更される:
外部変数がセッション外(ユーザがデバイスのボタンを押した等)で変更されたら,特性に対する次のクエリは新しい値を使用する:
デバイスを閉じ,デバイス上の外部特性を再び変更する:
デバイスとの通信がないため,報告される特性の値は古いものになる:
デバイスが再開されると値は自動的に更新される:

"NativeProperties"

オプション"NativeProperties"{property1,property2,}は,通常指定されたクラスのデバイス上で利用可能なネイティブ特性のリストを指定する.ネイティブ特性は,フレームワークで提供される標準的な方法でデバイスにアクセスすると同時に,直接デバイスを操作するオプションも欲しいときに便利である.ネイティブ特性の名前はユーザが制御できないサードパーティライブラリによって定義されている.これらは妥当なWolfram言語式でありえる.
例えば,DeviceOpenDeviceReadDeviceExecute等の関数や,.NET/Link を介して接続されたデバイス用の他の標準Wolfram言語関数を使うドライバを設定し,同時にユーザが.NET/Link インターフェースを介して直接デバイスと通信できるようにしたいとする.
以下のデモデバイスは標準化された特性とネイティブ特性の両方のサンプル実装を提供する:

"GetNativePropertyFunction"

"GetNativePropertyFunction"f は,デバイスハンドル dhandle によって見付けられたデバイスのネイティブ特性 p の値をクエリするために f[dhandle,p]が呼び出されるよう指定する.関数 f は特性値を返す.デフォルトのAutomaticは本質的にFunction[{dhandle,p},dhandle@p]と等価である.
以下のデモデバイスは"GetNativePropertyFunction"のサンプル実装を提供する:

"SetNativePropertyFunction"

"SetNativePropertyFunction"f は,デバイスハンドル dhandle によって見付けられたデバイスのネイティブ特性 p を値 val に設定するために f[dhandle,p,val]が呼び出されるよう指定する.この関数の戻り値は無視される.デフォルトのAutomaticは本質的にFunction[{dhandle,p,val},dhandle@p=val]と等価である.
以下のデモデバイスは"SetNativePropertyFunction"のサンプル実装を提供する:

"StatusLabelFunction"

デフォルトではフレームワークはDeviceOpen[class]で開かれたデバイスの状態ラベルを生成するためにはDeviceDefaultStatusLabels[]を,DeviceOpen[class,{p,}]のパラメータ p で開かれたデバイスにはDeviceDefaultStatusLabels[p]を使う.オプション"StatusLabelFunction"fを使うと自分のラベルが生成できる.引数 argsDeviceOpen["class",{args}]に与えると,関数 f[{args}]はデバイスの準備段階で呼び出される.これはラベルを開いたデバイスに置き換える文字列,あるいはラベル olbl とラベル clbl をそれぞれ開いたデバイスと閉じたデバイスに置き換える2つの文字列{olbl,clbl}のリストを返す.
以下のドライバはカスタムの状態ラベルを実装する:
より高度なラベルを構築するためには"PreconfigureFunction"DeviceStatusLabelsを呼ぶ.

"DeviceIconFunction"

"DeviceIconFunction"f は,DeviceOpen["class",{args}]の応答で生成されたデバイスオブジェクトのカスタムアイコンを生成するために f[{ihandle,dhandle},args]が呼ばれるよう指定する.第一引数{ihandle,dhandle}多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.この関数はGraphicsオブジェクトを返す.

"DriverVersion"

"DriverVersion"num を使うとドライバのバージョンを数値 num で指定することができる.フレームワークは,前のバージョンが利用可能だとしても最新のバージョンを自動的にロードする.他のバージョンをロードすることもできる.詳細は「ドライバファイルを操作する」に記載されている.
ロードされたドライバのバージョン番号はDeviceDriverVersionで得ることができる.
バージョン番号を指定し,それを得る:
デバイスフレームワーク関数
フレームワークはDeviceClassRegisterの他にも,ドライバファイルを操作したり,デバイスオブジェクトに関するユーザ可視情報および内部情報を抽出したり,クラスの初期設定や他のユーティリティにアクセスしたりするための関数を提供している.
DeviceFramework`DeviceClassRegister[class,]
指定されたクラスを登録する
DeviceFramework`DeviceClassClear[class]
クラスの定義をクリアする
ドライバクラスの追加と削除
内部的にはDeviceClassRegisterDeviceClassClearを呼び,新しいクラスを作成する前に既存の定義を実質的にすべて削除する.それゆえドライバを開発するときにDeviceClassRegisterを複数回でも安全に呼ぶことができる.

ドライバファイルの操作

DeviceFramework`FindDeviceDrivers[form]
文字列パターン form に一致する名前を持つドライバのリストを与える
DeviceFramework`DeviceDriverLoad[class]
指定されたクラスに対するドライバを発見しロードする
DeviceFramework`DeviceDriverFile[dev | class]
デバイス dev あるいはクラス class に対して現在登録されているドライバへのパスを与える
DeviceFramework`DeviceDriverPaclet[dev | class]
デバイス dev あるいはクラス class に対して現在ロードされているパクレットドライバのパクレットオブジェクトを与える
DeviceFramework`DeviceDriverVersion[dev | class]
デバイス dev あるいはクラス class に対して現在ロードされているドライバのバージョン番号を返す
ドライバファイルの操作
FindDeviceDriversは形式{"path/to/driver",class,version}で三重項のリストを返す.この情報を使うと,Wolfram言語セッションにドライバファイルをロードせずにそれを検査したり,異なるドライバのバージョンを比較したり,DeviceOpenで自動的にロードされる最新バージョンのドライバの代りに前バージョンのドライバをロードしたりすることができる.
クラスに対して利用できるすべてのドライバを見付け,それに対応するファイルをシステムのエディタで開く:
すでにロードされているドライバでは,DeviceDriverFileを使うとどのファイルがロードされているかを正確に知ることができる.これは,ドライバが複数実装されており,フレームワークによって正確なバージョンがロードされているかどうかを確かめたい場合に便利である.この他にも,編集した後でドライバを再ロードしたりドライバの実装の詳細を知ったりするのにも便利である.
デモドライバを開き,ドライバファイルを見付ける:
次のコマンドを実行して,Wolfram 言語セッションでドライバファイルをノートブックとして開く:

デバイスに関するユーザ可視情報にアクセスする

DeviceFramework`DeviceClass[dev]
デバイス dev のクラスを返す
DeviceFramework`DeviceID[dev]
デバイス dev のIDを返す
DeviceFramework`DeviceStatusLabels[dev],DeviceFramework`DeviceStatusLabels[dev]={olbl,clbl}
デバイス dev の開閉状態に対する状態ラベルを取得し,設定する
デバイスオブジェクトについてのユーザ可視情報にアクセスする
状態ラベルはデバイス操作中のいつでも変更することができる.新しいラベルは,次回フロントエンドがデバイスオブジェクトのUIを生成したときに表示される.したがって,デバイスがユーザの目に触れる前に,"PreconfigureFunction"でカスタムの状態ラベルを作成することがよくある.
デモデバイスの組込み状態ラベルを変更する:

デバイスに関する内部情報にアクセスする

DeviceFramework`DeviceInitObject[dev]
デバイス dev に対する初期化オブジェクトを返す
DeviceFramework`DeviceManagerHandle[dev]
デバイス dev に対する初期化オブジェクトのハンドルを返す
DeviceFramework`DeviceHandle[dev]
デバイス dev のハンドルを返す
DeviceFramework`DeviceObjectFromHandle[h]
ハンドル h のデバイスオブジェクトを返す
DeviceFramework`DeviceOpenArguments[dev]
デバイス dev を開くのに使われた引数を返す
DeviceFramework`DeviceDriverOption[class,"opt"],DeviceFramework`DeviceDriverOption[class,"opt"]=val
指定されたクラスに対するデバイスドライバオプション"opt"の値を取得し設定する
フレームワークによって保持される内部オブジェクトにアクセスする
DeviceDriverOptionはいつでも呼びことができるが,派生クラスで親の関数を呼ぶのに特に便利である.
次のドライバは"RandomSignalDemo"に基づいており,それ自身の内部で親の"ReadFunction"を呼ぶ:
デバイスから読み込むと累積された乱数値のリストが与えられるが,親から読み込むと独立値が与えられる:
DeviceDriverOptionを使うと,デバイスの操作中にドライバ関数の値を動的に変更することもできる.
元来このクラスのデバイスはDeviceReadに与えられるパラメータの正弦を読み取る:
次によりドライバがのこぎり波を読み取るよう変更される:

クラス特性

フレームワークがDeviceObjectの特殊なインスタンスで標準化された特性を割り当てる必要がある場合,クラス特性からの値を取る.クラス特性はDeviceClassRegister"Properties"オプションによって指定され,ドライバがロードされた後いつでもアクセスすることができる.
DeviceFramework`DeviceClassProperties[class]
指定されたクラスに対して利用できる特性のリストを返す
DeviceFramework`DeviceClassProperties[class,prop]
指定されたクラスに対する特性 prop の値を返す
DeviceFramework`DeviceClassProperties[class,{p1,p2,}]
複数の特性の値を返す
DeviceFramework`DeviceClassProperties[class,prop]=val,DeviceFramework`DeviceClassProperties[class,{p1,p2,}]={v1,v2,}
指定された特性の値を設定する
クラスの特性にアクセスする
クラスに対するデバイスオブジェクトが生成される前でも,クラスの特性は利用できる:
デバイスの特性を変更してもクラスの特性には影響しない:
逆にクラスの特性は既存のデバイスの特性をリセットしないが,クラスの新しいデバイスの特性は変更する:

クラス初期設定

DeviceClassPreferencesでアクセスできるクラスの初期設定はクラスのより柔軟な保存庫であり,Wolfram言語セッション間で持続している.初期設定はデバイスドライバとは独立であるため,ドライバをロードする前でも,後でも,あるいはロードしなくてもDeviceClassPreferencesを呼ぶことができる.この呼出しでドライバがロードされることはない.
DeviceFramework`DeviceClassPreferences[class]
指定されたクラスに対して利用可能な初期設定の連想を返す
DeviceFramework`DeviceClassPreferences[class,pref]
指定されたクラスの初期設定 pref の値を返す
DeviceFramework`DeviceClassPreferences[class,{p1,p2,}]
複数の初期設定の値を返す
DeviceFramework`DeviceClassPreferences[class,pref]=val,DeviceFramework`DeviceClassPreferences[class,{p1,p2,}]={v1,v2,}
指定された初期設定の値を設定する
DeviceFramework`DeviceClassPreferences[class]=assoc
クラスの初期設定を assoc に割り当てる
DeviceFramework`DeviceClassPreferences[class]=.,DeviceFramework`DeviceClassPreferences[class,pref]=.,DeviceFramework`DeviceClassPreferences[class,{p1,p2,}]=.
クラスの初期設定をクリアするか指定されたキーを削除する
クラス初期設定の集合にアクセスする
クラスの初期設定の連想はキーと値の任意のペアを含む.これらは常にDeviceClassRegisterの発生時に内部的にチェックされる.初期設定にDeviceClassRegisterのオプションに対応するキーが含まれている場合,その値はドライバで指定されたオプションより優先される.
クラスの"Properties"設定を保存する:
ドライバが作成されるとき,DeviceClassRegister文によって記述された値ではなくその初期設定からの値が使われる.この場合は単に親クラスからの特性をコピーすることである:
初期設定をクリアし,クラス定義を再評価する:
初期設定がない場合は,このクラスの特性は必要に応じて親クラスから派生される:
デバイスクラスの初期設定は,Wolfram言語セッション中のいつでも設定しクエリすることができる.特に,初期設定はデバイスの発見に使われるデータを保存するためや,デバイスにより必要とされるサードパーティのソフトウェアがユーザのマシンにインストールされていることを示すフラグを設定するためにも便利である.
このクラスのデバイスは,クラスの初期設定でフラグが設定されていない限り見付からない:
フラグを設定するとデバイスが見付かるようになる:
クラスの初期設定を削除する:

デフォルトの特性ハンドラ

クラスに対してカスタムの特性ハンドラを定義するとき,ある段階でデフォルトのハンドラを呼び出すと,最初から再生成するのではなく組み込みのハンドラを拡張することができるので便利である..
DeviceFramework`DeviceGetProperty[dev,prop]
"GetPropertyFunction"のデフォルトハンドラを実行する
DeviceFramework`DeviceSetProperty[dev,prop,val]
"SetPropertyFunction"のデフォルトハンドラを実行する
DeviceFramework`DeviceDefaultStatusLabels[]
デフォルトの開閉状態ラベルのリストを与える
DeviceFramework`DeviceDefaultStatusLabels[p]
パラメータ p に基づくデフォルトの状態ラベルを与える
デフォルトの特性ハンドラを使う

デバイス状態を判定する

トップレベルの関数DeviceOpenDeviceCloseDeviceOpenQDevicesは,デバイスが開いているか閉じているかにかかわらず,現行セッションに登録されたすべてのデバイスのリストをそれぞれ開き,閉じ,テストし,与える.フレームワークは,類似しているがそれほど一般的ではない操作に対する補完関数おいくつか提供する.
DeviceFramework`OpenDevices[]
現在開いているデバイスのリスト
DeviceFramework`DeviceRegisteredQ[dev]
デバイス dev が現行のWolfram言語セッションに登録されているかどうかをテストする
DeviceFramework`DeviceDeregister[dev]
現行セッションからデバイス dev の登録を解除し完全に削除する
デバイスの状態をテストし変更する
DeviceDeregisterはデバイスが開いていたら閉じる.この関数は,ドライバを開発するときの古い記録を除去するのに便利な場合がある.
現行セッションですべてのデバイスの登録を解除するための便利なパレットを生成する:
デバイスドライバパクレットの開発
フレームワークは,ドライバがコンピュータ上にローカルで存在するかのように,Wolframパクレットサーバからデバイスドライバを自動的にロードする.そのため,ドライバを広く利用可能にしたい場合は常にそれをパクレットとして配布するとよい.
デバイスドライバパクレットを生成するためには,デバイスドライバファイルと平行して,ルートデバイスドライバディレクトリに特別なファイルPacletInfo.mを入れる.慣例では,ドライバパクレットの名前は,例えば"DeviceDriver_MyDevice"等,"DeviceDriver_"で始まるドライバクラス名である.必要に応じて,ルートドライバディレクトリには他のサポートファイルやディレクトリを入れることができる.
また,パクレットにはデバイス用のWolfram言語ドキュメントを入れることもできる.ドキュメントはエラーメッセージの参照ページを含んだりデバイスのインストールや操作についてのカスタムのトラブルシューティング手順を含むことができる.
このデバイスのデバイスドライバはパクレットとして配布される:
ドキュメントにはトラブルシューティングの手順付きのカスタムの参照ページが含まれる.以下のをクリックして手順を見る:
パクレットのコンテンツを調べるためには,次のコマンドを実行する:
例題

Tinker Forge Weather Station

Tinker Forge Weather StationはミニUSBケーブルでコンピュータと接続し,32ビットのARMマイクロコントローラによって制御される4つの周囲センサー(気温,気圧,湿度,輝度)からなる.このデバイスには文字列が表示される20×4文字LCDスクリーンが付属している.センサーとLCDスクリーンはサブデバイスとして,あるいは共通のプラットフォームを共有し共に"Tinker Forge Weather Station"デバイスを構成するブリックレットと考えられる.デバイスの読込みコマンドはセンサーからの測定を読み込むリクエストとして扱われ,書込みコマンドはLCDスクリーン上の文字を表示するリクエストとして解釈される.

18.gif

デバイスとの低レベル通信のための関数すべてがTinker Forge C/C++ API bindingsを使ってCプログラミング言語で実装されており,結果の.cソースファイルすべてが実行ファイルにコンパイルされたと想定する.Wolfram言語からこのデバイスと通信するためには,まずこの実行ファイルをInstallでインストールすることである.これは"OpenManagerFunction"で簡単に行うことができる.ドライバは以下のような他の関数をいくつか実装する.
DeviceFramework`DeviceClassRegister["myTinkerForge",
    "OpenManagerFunction" (Install["\path\to\tinkerforge\executable"]&),
    "ReleaseFunction" release,
    "MakeManagerHandleFunction" make,
    "OpenFunction" open,
    "ReadFunction" read,
    "WriteFunction" write,
    "CloseFunction" close
]
"OpenManagerFunction"Install文は通常LinkObjectを返す.ここでLinkClose"ReleaseFunction"の中で起動し非初期化段階でWSTP接続を閉じる.
release[link_] := LinkClose[link]
次に"MakeManagerHandleFunction"の実装は,制御Cプログラムとデバイスの間のTCP/IP接続を表すIP接続オブジェクトのハンドルを返す.
make[___] := Tinkerforge`IPConnectionCreate[]
接続ハンドルipconnは,デバイスマネージャハンドル ihandle としての役割を果たし,"OpenFunction"に渡される.ここで,この接続ハンドルは各々のブリックレットに接続して,それぞれに一意のハンドルを生成し,これらのハンドルのリストを返す.従って,リストは合成デバイスハンドル dhandle としての役割を果たす.このWeather Stationでは,気圧計ブリックレットは気圧と気温をどちらも測定するので,4つのセンサーに対して実質的に3つのブリックレットがあることになる.
open[ipconn_, ___] := {
    LightSensorCreate[ipconn],
    HumiditySensorCreate[ipconn],
    BarometerCreate[ipconn]
}
3つ一組のブリックレットハンドラは,"ReadFunction"で天候データを読み込むのに使われ,そこではマネージャハンドル ihandle は必要とされない.
read[{_, {lightHandle_, humHandle_, baroHandle_}}, rest___] := XXX
それに反して,"WriteFunction"の実装はセンサーハンドルを必要としないが,その場でipconnからLCDブリックレットのハンドルを生成する.
write[{ipconn_, _}, rest___] := XXX
"CloseFunction"の実装はハンドルが与えられるとIP接続を解除し,センサーブリックレットを破壊する.
close[{ipconn_, {lightHandle_, humHandle_, baroHandle_}}] := (
    Tinkerforge`IPConnectionDisconnect[ipconn];
    LightSensorDestroy[lightHandle];
    HumiditySensorDestroy[humHandle];
    BarometerDestroy[baroHandle];
)
Tinker Forge Weather Stationドライバにはより精巧な実装もある.
次のコマンドを実行すると,システムエディタでドライバファイルが開く:
追加情報
Wolfram言語用のデバイスドライバを開発・配布することに関心のある方は,Wolfram Researchまでご連絡いただきたい.