デバイスドライバの開発
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[]
フレームワークが自動的に発見できるようにするために,class に対するDeviceClassRegister["class",…]文はWolfram言語パッケージの class.mの中になければならない.DeviceClassRegisterの最初の文字列の引数はドライバファイルの名前と一致していなければならないという名前付け規則もある.さらに,ドライバは以下のいずれかの場所に置かれなければならない.
- 現行の評価ノートブックのディレクトリ(NotebookDirectory)
- コンピュータシステム上の特別なディレクトリ FileNameJoin[{$InstallationDirectory,"SystemFiles","Devices","DeviceDrivers"}]
- $Path上の任意の場所
- パクレットサーバ上,あるいはドライバのパクレットディレクトリ(「デバイスドライバパクレットの開発」を参照)
ドライバは上の規則に従っていなくても使用することはできるが,Get等でWolfram言語セッションに明示的にロードされていなければならない.
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,…}]に与えることのできる引数のリストである.
"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"がない場合,マネージャハンドルが初期化オブジェクト自体になる.
マネージャハンドルの例として,ソケットハンドルや.NETオブジェクトがある.
"OpenFunction"
"OpenFunction"f で指定される関数は,ユーザのDeviceOpenの呼出しに応答してフレームワークによって呼ばれるメインの関数である. f[ihandle,args…]は"MakeManagerHandleFunction"によって生成されたマネージャハンドル ihandle と,ユーザがDeviceOpen["class",{args…}]に提供する引数 args を受け取る.戻り値はデバイスハンドルと呼ばれる.このハンドルはマネージャハンドルとともに多数のドライバ関数に与えられる.そうでない場合はDeviceHandleで取り戻すことができる.
このフレームワークは"OpenFunction"によって返されるデバイスハンドルと,DeviceOpenによって返されるトップレベルのDeviceObjectとの間に1対1の対応を維持しようとする.それゆえ,ドライバハンドルは一意であるか,少なくとも自分で制御できない他のドライバによって生成されるハンドルと一致しないようにすることをお勧めする.その簡単な方法として,CreateUUIDを使ってハンドルを生成するというものがある.あるいは"com.company.class"[… ]という形式でハンドルを返すとか,同様な方法を使うとかすることができる.
"OpenReadFunction"
"OpenReadFunction"f は,"OpenFunction"の後,デバイスに関連付けられた任意の入力ストリームを開くためのオープンシーケンスの途中で f[{ihandle,dhandle},args…]が呼び出されるよう指定する.第一引数の{ihandle,dhandle}は多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.引数 args はDeviceOpen["class",{args…}]に与えられるものである.戻り値は入力ストリームオブジェクトか入力ストリームオブジェクトのリストでなければならない.
"OpenWriteFunction"
"OpenWriteFunction"f は,"OpenFunction"の後,デバイスに関連付けられた任意の入力ストリームを開くためのオープンシーケンスの途中で f[{ihandle,dhandle},args…]が呼び出されるよう指定する.第一引数の{ihandle,dhandle}は多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.引数 args はDeviceOpen["class",{args…}]に与えられるものである.戻り値は出力ストリームオブジェクトか出力ストリームオブジェクトのリストでなければならない.
"PreconfigureFunction"
"PreconfigureFunction"f で指定される関数は,デバイスのオープンシーケンスを終了させる.ユーザに戻される寸前のデバイスオブジェクト dev では,f[dev] はDeviceOpenの呼び出しの最後に確実に呼び出される.これはデバイスのトップレベルのデバイス特性(以前デバイス上で設定されていた場合)に再適用する,あるいはクラス特性(ドライバによって定義されている場合)を設定する初期設定の前である."PreconfigureFunction"はドライバ,"PreconfigureFunction"自身,"OpenFunction",あるいはデバイスオープンシーケンスの他の関数によって設定された特性のリストを返す.これらの特性はDeviceOpenが終了するまでフレームワークによってそれ以上変更されない.AllあるいはNoneも返される.
"ConfigureFunction"
"ConfigureFunction"f は f[{ihandle,dhandle},args…] がデバイスを設定するトップレベルのコマンドDeviceConfigure[dev,{args…}]への応答として呼ばれるように指定する.第1引数の{ihandle,dhandle}(ihandle はマネージャハンドル,dhandle はデバイスハンドル)は,多数のドライバ関数に共通である.この関数の戻り値は無視される.
デバイスを開き,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]
|
"WriteFunction"
"WriteFunction"f は,デバイスにデータを書き込むためのトップレベルコマンドDeviceWrite[dev,{args…}]に応答して f[{ihandle,dhandle},args…]が呼ばれるよう指定する.第一引数{ihandle,dhandle}は多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.この関数の戻り値は無視される.
このドライバはDeviceWriteへのすべての呼出しに対して新しいノートブックを生成する:
デバイスがいくつかのパラメータの値を書く場合,その値を連想あるいは規則の集合としてDeviceWriteに与えるのが一般的である.そのような規則を解析することは"WriteFunction"の実装になる.
"WriteBufferFunction"
"WriteBufferFunction"f は,デバイスバッファにデータを書き込むためのトップレベルコマンドDeviceWriteBuffer[dev,{args…}]に応答して f[{ihandle,dhandle},args…]が呼ばれるよう指定する.第一引数{ihandle,dhandle}は多くのドライバ関数に共通である.ここで ihandle はマネージャハンドル,dhandle はデバイスハンドルである.この関数の戻り値は無視される.
"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 がサポートされない場合,関数 f はMissing[]オブジェクトを生成するか,単に未評価で返すかしなければならない.どちらの場合でも,フレームワークは適切なメッセージを生成する.他のエラーの場合,f は適切なメッセージを出力し$Failedを返す.これは特に,指定されたコマンドが与えられた引数で実行できない場合に起こる.
"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でない限り無視される.
"DeregisterOnClose"
"DeregisterOnClose"Trueは,DeviceCloseがすべての外部リソースを解放するだけでなく,現行のWolfram言語セッションからデバイスを完全に削除するよう指定する.デフォルトの"DeregisterOnClose"Falseでは,デバイスはまだ利用可能であり厳密に同じパラメータで再開することができる.
"Singleton"
"Singleton"オプションはフレームワークがどのようにDeviceOpen["class",{args…}]コマンドを処理するかを指定し,複数デバイスの生成ポリシーを設定する."Singleton"オプションに可能な値は以下の通りである.
Automatic | 新しい引数集合に対する新しいデバイスを返す |
True | 常に同じデバイスを返す |
False | 常に新しいデバイスを返す |
crit | crit[dev,{args}]を評価してTrueとなるような登録されたデバイスの最初のものを返す |
同じ引数でDeviceOpenを呼ぶと,同じデバイスが与えられる:
重要なことは,DeviceOpenの応答として新しいデバイスを開く必要がない場合,フレームワークは通常"OpenFunction"を含むドライバ関数のオープニングシーケンスを実行するということである.指定された基準 crit がTrueを返す場合はオープニングシーケンスを実行するが,引数 args はデバイスが前に開かれるときに使った引数と完全には一致しない.このため,異なる引数の集合に対して同じデバイスを再設定する.
以下のドライバでは,DeviceOpenはすべての異なる第一引数に対する新しいデバイスを返す:
"Properties"
オプション"Properties"{"property1"value1,"property2"value2,…}は新規に生成されたデバイスの標準化された(トップレベルの)特性を通常決定するクラス特性を指定する.標準化されたデバイス名は文字列として与えられる.ドライバはネイティブ特性も指定することができる.
"GetPropertyFunction"
"GetPropertyFunction"f は,デバイス dev の標準化された特性 p の値をクエリするために f[dev,p]が呼ばれるよう指定する.戻り値はユーザに渡される."GetPropertyFunction"のデフォルトはDeviceGetPropertyであり,関数 f の内部で呼ぶことができる.
"SetPropertyFunction"
"SetPropertyFunction"f は,デバイス dev の標準化された特性 p を値 val に設定するために f[dev,p,val]が呼び出されるよう設定する.f の戻り値は無視される."SetPropertyFunction"のデフォルトはDeviceSetPropertyであり,関数 f 内部で呼ぶことができる.
デバイスの特性値を変更するためには通常"SetPropertyFunction"を使うが,無効な特性値を除去したり,いくつかの特性を読み出し専用にしたり,標準化された特性をネイティブ特性にリンクしたり等するために同じ関数を使うこともできる.
高度なトピック:トップレベルの特性をデバイスと同期したままにする
"NativeProperties"
オプション"NativeProperties"{property1,property2,…}は,通常指定されたクラスのデバイス上で利用可能なネイティブ特性のリストを指定する.ネイティブ特性は,フレームワークで提供される標準的な方法でデバイスにアクセスすると同時に,直接デバイスを操作するオプションも欲しいときに便利である.ネイティブ特性の名前はユーザが制御できないサードパーティライブラリによって定義されている.これらは妥当なWolfram言語式でありえる.
例えば,DeviceOpen,DeviceRead,DeviceExecute等の関数や,.NET/Link を介して接続されたデバイス用の他の標準Wolfram言語関数を使うドライバを設定し,同時にユーザが.NET/Link インターフェースを介して直接デバイスと通信できるようにしたいとする.
"GetNativePropertyFunction"
"GetNativePropertyFunction"f は,デバイスハンドル dhandle によって見付けられたデバイスのネイティブ特性 p の値をクエリするために f[dhandle,p]が呼び出されるよう指定する.関数 f は特性値を返す.デフォルトのAutomaticは本質的にFunction[{dhandle,p},dhandle@p]と等価である.
"SetNativePropertyFunction"
"SetNativePropertyFunction"f は,デバイスハンドル dhandle によって見付けられたデバイスのネイティブ特性 p を値 val に設定するために f[dhandle,p,val]が呼び出されるよう指定する.この関数の戻り値は無視される.デフォルトのAutomaticは本質的にFunction[{dhandle,p,val},dhandle@p=val]と等価である.
"StatusLabelFunction"
デフォルトではフレームワークはDeviceOpen[class]で開かれたデバイスの状態ラベルを生成するためにはDeviceDefaultStatusLabels[]を,DeviceOpen[class,{p,…}]のパラメータ p で開かれたデバイスにはDeviceDefaultStatusLabels[p]を使う.オプション"StatusLabelFunction"fを使うと自分のラベルが生成できる.引数 args をDeviceOpen["class",{args…}]に与えると,関数 f[{args}]はデバイスの準備段階で呼び出される.これはラベルを開いたデバイスに置き換える文字列,あるいはラベル olbl とラベル clbl をそれぞれ開いたデバイスと閉じたデバイスに置き換える2つの文字列{olbl,clbl}のリストを返す.
"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] | |
クラスの定義をクリアする |
内部的にはDeviceClassRegisterがDeviceClassClearを呼び,新しいクラスを作成する前に既存の定義を実質的にすべて削除する.それゆえドライバを開発するときに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を使うとどのファイルがロードされているかを正確に知ることができる.これは,ドライバが複数実装されており,フレームワークによって正確なバージョンがロードされているかどうかを確かめたい場合に便利である.この他にも,編集した後でドライバを再ロードしたりドライバの実装の詳細を知ったりするのにも便利である.
デバイスに関するユーザ可視情報にアクセスする
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はいつでも呼びことができるが,派生クラスで親の関数を呼ぶのに特に便利である.
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 に基づくデフォルトの状態ラベルを与える |
デバイス状態を判定する
トップレベルの関数DeviceOpen,DeviceClose,DeviceOpenQ,Devicesは,デバイスが開いているか閉じているかにかかわらず,現行セッションに登録されたすべてのデバイスのリストをそれぞれ開き,閉じ,テストし,与える.フレームワークは,類似しているがそれほど一般的ではない操作に対する補完関数おいくつか提供する.
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スクリーン上の文字を表示するリクエストとして解釈される.
デバイスとの低レベル通信のための関数すべてが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]
}
read[{_, {lightHandle_, humHandle_, baroHandle_}}, rest___] := XXX
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までご連絡いただきたい.