(57)【要約】      （修正有）【課題】ＵＳＢ接続の一台の機器で複数の機能を持つことを可能にするプログラムを提供する。【解決手段】ＣＰＵ１０１は主記憶装置１０２のＲＯＭ１０２１あるいはＲＡＭ１０２２あるいは補助記憶装置１０５に格納されたプログラムに従って情報処理装置全体を制御する。ＲＡＭ１０２２はＣＰＵ１０１が各種処理を行う際のワークエリアとしても使用される。補助記憶装置１０５はアプリケーション１０５１、プリンタードライバー１０５２、オペレーティングシステム（ＯＳ）１０５３等を記録する。出力Ｉ／Ｆ１０４は、データを外部に出力するためのインターフェースであり、モニタ１０４１やプリンター１０４２などの出力機器に対してデータを出力する。【選択図】図１
2016021157.tif 000002
【特許請求の範囲】
【請求項１】
プロパティバッグにポートの情報とチャネルの情報とクレジットを保存する手段と、プロパティバッグからチャネルとクレジットを取得して、データを送受信したら更新する手段と、印刷ジョブを送信するときと構成情報を取得するときで重ならないように、プロパティバッグに別々の情報として保持しておく手段を持つことを特徴とするＶ４プリンタードライバーのＵＳＢＪａｖａＳｃｒｉｐｔプログラム。
【発明の詳細な説明】
【技術分野】
【０００１】
本発明は、新しいアーキテクチャーのプリンタードライバーの、ＵＳＢ接続におけるＩＥＥＥ１２８４．４−２０００プロトコルを実現するためのプログラムに関する。
【背景技術】
【０００２】
Ｍｉｃｒｏｓｏｆｔ（登録商標）のＷｉｎｄｏｗｓ８（登録商標）では、新しいアーキテクチャーのＶ４プリンタードライバーが導入された。Ｖ４プリンタードライバーでは、従来のプリンタードライバーにおいて、プリンターとの通信プロトコルの実装のために用いられていた、ランゲージモニターとポートモニターが廃止された。Ｖ４プリンタードライバーで使用できるのは、Ｗｉｎｄｏｗｓ（登録商標）に標準搭載されているプロトコルのポートモニターだけである。その代り、Ｖ４プリンタードライバーでは、ポートモニターにプリンターごとの機能情報を伝えるためのＢｉｄｉ  Ｅｘｔｅｎｓｉｏｎとプロパティバッグが追加された。また、Ｖ４プリンタードライバーのＵＳＢ接続では、プリンターとのデータ通信をコントロールするためのＵＳＢ  Ｂｉｄｉ  Ｅｘｔｅｎｄｅｒが追加された。ＵＳＢ  ＢｉｄｉＥｘｔｅｎｄｅｒでは、Ｂｉｄｉ ＸＭＬファイルとＪａｖａＳｃｒｉｐｔ(登録商標）を使用して、プリンターとのデータ通信を行う（非特許文献１）。
【０００３】
また、１台のプリンターで複数の機能を実現するためのプロトコルとして、ＩＥＥＥ１２８４．４−２０００（以下、ＤＯＴ４とする）がある。ＤＯＴ４は、物理的に直接接続するピアツーピアの接続であるシリアルポートやパラレルポート、ＵＳＢで使用されるもので、インターフェース上にチャネルを確立して、双方向通信を実現する（特許文献１）。Ｗｉｎｄｏｗｓ（登録商標）に標準搭載されているプロトコルにはＤＯＴ４がないので、従来はランゲージモニターやポートモニターをプリンターごとに独自に作成して、ＤＯＴ４を実現していた。
【先行技術文献】
【特許文献】
【０００４】
【特許文献１】特開２０００−７６０２４号公報
【非特許文献】
【０００５】
【非特許文献１】Ｍｉｃｒｏｓｏｆｔ  Ｖ４プリンタードライバー  ホワイトペーパー
【発明の概要】
【発明が解決しようとする課題】
【０００６】
しかしながら、Ｖ４プリンタードライバーでは、標準搭載されているポートモニターしか使用できないので、ＵＳＢ接続においてＤＯＴ４のプロトコルを実現することができない。たとえば、ＵＳＢ接続のプリンターで、印刷データだけを送信する、もしくはプリンターからステータス情報を取得するといったように、片方だけの通信なら可能である。
【０００７】
しかし、ＤＯＴ４のプロトコルを実現できないので、双方向によって複数の機能を同時に使用する動作、たとえば、印刷しながらプリンターの情報を取得することができなくなった。
【課題を解決するための手段】
【０００８】
本発明のＵＳＢＪａｖａＳｃｒｉｐｔプログラムの構成は、
プロパティバッグにポートの情報とチャネルの情報とクレジットを保存する手段と、プロパティバッグからチャネルとクレジットを取得して、データを送受信したら更新する手段と、印刷ジョブを送信するときと構成情報を取得するときで重ならないように、プロパティバッグに別々の情報として保持しておく手段を持つことを特徴とする。
【発明の効果】
【０００９】
本発明により、ＵＳＢ接続のＶ４プリンタードライバーにおいてＤＯＴ４のプロトコルで通信できるので、印刷しながらステータス情報を取得するなど、複数の機能を同時に使用できるようになる。
【図面の簡単な説明】
【００１０】
【図１】コンピューターシステムのハードウェアとソフトウェアのブロック構成図の一例を示す図である。
【図２】クライアントとサーバーとプリンターが接続されたネットワークの構成図の一例を示す図である。
【図３】Ｖ４プリンタードライバー印刷処理システムのブロック構成図の一例を示す図である。
【図４】Ｖ４プリンタードライバーのＵＳＢ接続におけるＵＳＢＢｉｄｉＥｘｔｅｎｄｅｒの一例を示す図である。
【図５】ＩＥＥＥ１２８４．４のパケットの構成図の一例を示す図である。
【図６】Ｖ４プリンタードライバーのＩＥＥＥ１２８４．４のＪａｖａＳｃｒｉｐｔのプログラムの流れを示すシーケンス図である。
【発明を実施するための形態】
【００１１】
以下、本発明を実施するための最良の形態について図面を用いて説明する。
【実施例１】
【００１２】
図１は本発明の実施形態を示す一般的な情報処理装置（コンピューター）を用いたシステムのブロック構成図である。なお、特に断らない限り、本発明の機能が実行されるのであれば、単体の機能であっても、複数の機器からなるシステムであっても、ネットワークを介して接続がなされ処理が行われるシステムであっても、本発明を適用できることは言うまでもない。
【００１３】
ＣＰＵ１０１は主記憶装置１０２のＲＯＭ１０２１あるいはＲＡＭ１０２２あるいは補助記憶装置１０５に格納されたプログラムに従って情報処理装置全体を制御する。ＲＡＭ１０２２はＣＰＵ１０１が各種処理を行う際のワークエリアとしても使用される。補助記憶装置１０５はアプリケーション１０５１、プリンタードライバー１０５２、オペレーティングシステム（ＯＳ）１０５３等を記録する。
【００１４】
キーボード１０３１やマウス・タッチパネルなどに代表されるポインティングデバイス１０３２などの入力機器は、入力Ｉ／Ｆ１０３を通じて、ユーザーがコンピューターに対して各種指示を与えるためのデバイスである。
【００１５】
出力Ｉ／Ｆ１０４は、データを外部に出力するためのインターフェースであり、モニタ１０４１やプリンター１０４２などの出力機器に対してデータを出力する。プリンター１０４２とは直接接続されるローカルＩ／Ｏのみならず、通信Ｉ／Ｆ１０６を通じて接続されるネットワーク１０６１を通して接続されていてもよい。
【００１６】
また、１０７は共通データシステムバスで、Ｉ／Ｆやモジュール間でデータのやりとりを行う。なお、本発明のフローチャートの各ステップは、ＣＰＵが、フローチャートに関するプログラムをメモリから読み出して実行することで実現される。
【００１７】
また、プリンター１０４２は、印刷機能のみを持った印刷装置でも良いし、ＦＡＸ機能のみを持ったＦＡＸ装置でも良いし、スキャン機能および印刷機能など複数の機能を持った複合機でも良い。なお、プリンター１０４２を画像処理装置と呼ぶこともある。
【００１８】
図２は本発明の実施の形態におけるネットワーク１０６１の環境を簡略した図である。印刷される文書や画像を作成する単体もしくは複数のクライアントコンピュータ２０１／２０２がネットワークに接続されている。また、クライアントコンピュータのユーザーやプリンターを管理するサーバーコンピュータ２０３が接続されていることもある。単体もしくは複数のプリンター２０５および１０４２がネットワークに接続されている。
【００１９】
なお、プリンター２０５は、物理的にネットワークに接続されているが、実際には使用できないオフライン状態であることを示している。ネットワークにはＰＡＮ（Ｐｅｒｓｏｎａｌ  Ａｒｅａ  Ｎｅｔｗｏｒｋ）、ＬＡＮ（Ｌｏｃａｌ  ＡｒｅａＮｅｔｗｏｒｋ）、ＭＡＮ（Ｍｅｔｒｏｐｏｌｉｔａｎ  Ａｒｅａ Ｎｅｔｗｏｒｋ）、ＷＡＮ（Ｗｉｄｅ  Ａｒｅａ Ｎｅｔｗｏｒｋ）などの小規模から大規模までのネットワークがあり、これらの機器がすべてのネットワークに接続されている。クラウドなど、サーバーやプリンターがインターネットを越えて接続されていてもかまわない。つまり、情報処理装置とは、他の装置（例えば、プリンター等の画像処理装置）と接続可能な装置である。
【００２０】
図３はＶ４プリンタードライバー印刷システムのブロック構成図である。Ｖ４プリンタードライバー印刷システムはＸＭＬＰａｐｅｒ Ｓｐｅｃｉｆｉｃａｔｉｏｎ（以下、ＸＰＳと略す）と呼ばれるファイルフォーマットをスプールデータとして使用し、印刷を行うシステムである。図３におけるソフトウェアコンポーネントである１０５１、１０５２、１０５３は、補助記憶装置１０５に記憶されており、実行時にＲＡＭ１０２２にロードされ、ＣＰＵ１０１によって実行される。Ｖ４プリンタードライバー印刷システムは、オペレーティングシステム１０５３上で動作している。
【００２１】
プリントマネージャ３１８、ＧＤＩｔｏＸＰＳ変換モジュール３０７、フィルターパイプラインマネージャ３１２はオペレーティングシステム１０５３に含まれるモジュールである。ＧＤＩｔｏＸＰＳ変換モジュール３０７、フィルターパイプラインマネージャ３１２は、プリンタードライバー１０５２に含まれているが、オペレーティングシステム１０５３から提供されているプリンタードライバー１０５２専用のモジュールである。
【００２２】
プリンタードライバー１０５２とフィルターパイプラインマネージャ３１２の各フィルターである３１３から３１５と、デバイスアプリ３１０は、図１の補助記憶装置１０５にプリンタードライバー１０５２として格納されている。なお、デバイスアプリ３１０は、プリンタードライバーのインストールに連動して、ネットワークを介して自動的にインストールされる。ＧＤＩ印刷アプリケーション３０１（以下、ＧＤＩアプリと略す）とＸＰＳ印刷アプリケーション３０２（以下、ＸＰＳアプリと略す）は、図１の補助記憶装置１０５にアプリケーション１０５１として格納されている。
【００２３】
ＧＤＩアプリ３０１は、オペレーティングシステム１０５３が用意するＧＤＩ（Ｇｒａｐｈｉｃｓ  Ｄｅｖｉｃｅ  Ｉｎｔｅｒｆａｃｅ）を使用して印刷を行うアプリである。ＸＰＳアプリ３０２は、ＸＰＳスプールファイル３０６を直接使用して印刷を行うアプリである。ユーザーはキーボード１０３１やタッチパネル／マウス１０３２などといった入力装置を使用して、出力装置のモニタ１０４１に映し出されたＧＤＩアプリ３０１、もしくはＸＰＳアプリ３０２から印刷処理を指示する。なお、印刷処理は「プリンターの選択」、「印刷設定データの作成」、「描画データの変換」と３つの処理を順番に行うことで実現される。以下、印刷処理の流れを説明する。
【００２４】
まず、ユーザーは、印刷したいプリンター１０４２を選択する。ユーザーから見るとプリンター１０４２の選択は、印刷を実行するプリンター１０４２に対応したプリンタードライバー１０５２を選択することと同じである。
【００２５】
次に印刷設定データが作成される。印刷設定データが作成される場合、アプリケーション１０５１が印刷設定データ用のメモリ領域をＲＡＭ１０２２に確保する。そして、アプリケーション１０５１は、プリンタードライバー１０５２のコンフィグモジュール３０８を呼び出して、印刷設定データを作成して格納する。ＧＤＩアプリ３０１では印刷設定データとしてバイナリのＤＥＶＭＯＤＥ構造体３０３を用い、ＸＰＳアプリ３０２ではマークアップ言語のＸＭＬ（ｅＸｔｅｎｓｉｖｅ  ＭａｒｋｕｐＬａｎｇｕａｇｅ）で記載されたプリントチケット３０４を用いる。
【００２６】
ＤＥＶＭＯＤＥ構造体３０３は、オペレーティングシステムが定義する標準領域とプリンタードライバーが独自で定義する拡張領域を持つ。プリントチケット３０４は、ＸＭＬ形式で記述された印刷設定データで、標準領域と拡張領域は名前空間によって記載が分かれている。印刷設定データには機種固有の情報も含まれているので、コンフィグモジュール３０８は、機種依存ファイル３０９を利用して、印刷設定データを作成する。
【００２７】
アプリケーション１０５１は、ＤＥＶＭＯＤＥ構造体３０３もしくはプリントチケット３０４の内容を書き換えることで印刷設定を変更する。印刷設定データには、出力する用紙サイズの情報（例えば「Ａ４」）、両面印刷の指定、カラーと白黒、給紙段の指定など、印刷に必要な設定値が含まれる。印刷設定データの標準領域は仕様が外部に公開されているので、アプリケーションが直接変更できる。
【００２８】
プリンター１０４２に依存した拡張領域は、プリンタードライバー１０５２だけが詳細な仕様を把握しているので、通常はデバイスアプリ３１０が持つユーザーインターフェースを使ってユーザーが設定する。ＤＥＶＭＯＤＥ構造体３０３と異なり、プリントチケット３０４は設定値がＸＭＬ形式で記述されているので、ＸＰＳアプリ３０２がすべての設定値を直接変更して書き換えることは可能ではある。しかし拡張領域は、プリンタードライバーごとの独自定義であるため詳細な仕様まではわからず、プリンター１０４２によって仕様が異なることから、デバイスアプリ３１０のユーザーインターフェースを使って設定値を変更するほうが容易である。
【００２９】
デバイスアプリ３１０は、プリンタードライバー１０５２の一種ではあるが、プリンタードライバーとは別のプログラムとしてインストールされる。デバイスアプリ３１０は、プリンタードライバーと、プリントチケット３０４を使用して印刷設定データをやりとりする。プリンタードライバー１０５２は、ユーザーインターフェースの設定に従い、ＤＥＶＭＯＤＥ構造体３０３もしくはプリントチケット３０４のプリンター１０４２に依存した設定を変更する。印刷設定データは文書を印刷するたびに必要となるので、印刷を実行するたびに作成される。
【００３０】
プリンター１０４２のオプション機器や、ユーザーごとの環境設定などは、プリンタードライバーがオペレーティングシステムのレジストリデータベース３０５、もしくはプロパティバッグ３１７に保存される。印刷設定データのデフォルト値はオペレーティングシステムのプリントマネージャ３１８が、レジストリデータベース３０５に保存する。レジストリデータベース３０５や、プロパティバッグ３１７は、補助記憶装置１０５に保存される。
【００３１】
最後に描画データの変換を行う。印刷設定データが確定したら、ユーザーはアプリケーションから印刷処理を指示する。ＧＤＩアプリ３０１から印刷する場合は、ＧＤＩｔｏＸＰＳ変換モジュール３０７に描画データが送られ、ＸＰＳスプールファイル３０６が作成される。このときＧＤＩｔｏＸＰＳ変換モジュール３０７はコンフィグモジュール３０８を呼び出し、印刷設定データをＤＥＶＭＯＤＥ構造体３０３からプリントチケット３０４に変換する。変換する際には、ＤＥＶＭＯＤＥ−プリントチケット変換モジュール３０８１が使用される。
【００３２】
ＤＥＶＭＯＤＥ−プリントチケット変換モジュール３０８１は、プロパティバッグ３１７の情報を読み書きできるので、デバイスアプリ３１０が保存したデータを参照して変換することもできる。ＸＰＳアプリ３０２から印刷する場合は、ＸＰＳスプールファイル３０６をＸＰＳアプリ自身が生成するのと、オペレーティングシステムがＸＰＳアプリからの描画命令に応じてＸＰＳスプールファイル３０６を生成するのと２通りの方法がある。どちらの方法であっても、印刷の途中でＸＰＳスプールファイル３０６が生成される。このようにＶ４プリンタードライバー印刷システムは、印刷時に必ず描画データに基づくＸＰＳスプールファイル３０６を生成する点が特徴である。
【００３３】
ＸＰＳスプールファイル３０６が生成されたら、プリントフィルタパイプライン３１１に処理が渡される。プリントフィルタパイプライン３１１は複数のフィルターを通すことで印刷が行われる仕組みで、フィルタコンフィギュレーションファイル３１６がフィルターの数や順番を制御する。
【００３４】
プリントフィルタパイプライン３１１で動作するフィルターパイプラインマネージャ３１２はフィルタコンフィギュレーションファイル３１６に従って、この実施例では通信フィルター３１３、レイアウトフィルター３１４、レンダラーフィルター３１５の順に処理を行う。フィルターはプリンタードライバー１０５２の構成によって、数や種類が異なる。例えば、図３の３つのフィルターに加えて、更に、印刷ジョブを暗号化するための暗号化フィルターが含まれていても良い。
【００３５】
印刷処理はＸＰＳスプールファイル３０６をフィルターに受け渡すことで行われ、フィルターがそれぞれＸＰＳスプールファイル３０６を加工し次のフィルターに渡していくことで処理が進む。最終的にはＸＰＳスプールファイル３０６が印刷ジョブに変換されて、プリンターに送信される。なお、プリンターは、印刷ジョブを解釈可能であり、送信された印刷ジョブを解釈することで印刷ジョブに基づく印刷処理を実行する。
【００３６】
それぞれのフィルターは独自のデータをプロパティバッグ３１７に保存することができる。また、それぞれのフィルターは、プロパティバッグ３１７から、オペレーティングシステム１０５３の情報や、他のフィルターのデータを取得することもできる。通信フィルター３１３では、印刷時に必要な情報をプリンターから取得し、プロパティバッグ３１７に保存しておく。
【００３７】
レイアウトフィルター３１４では倍率の変更や製本の面付けレイアウトやスタンプなどレイアウトに関する処理を行う。レイアウトフィルター３１４では、ＸＰＳスプールファイル３０６に含まれるプリントチケット３０４に従って動作する。そのため、たとえば面付けの設定がプリントチケット３０４に存在しないときは、レイアウトフィルター３１４は何も動作せずスルーして、次のフィルターにＸＰＳスプールファイル３０６をそのまま渡す。
【００３８】
レンダラーフィルター３１５は、入力されたＸＰＳスプールファイル３０６をレンダリングしてページ記述言語（以下、ＰＤＬ（ＰａｇｅＤｅｓｃｒｉｐｔｉｏｎ Ｌａｎｇｕａｇｅ）と略す）に変換する。また、レンダラーフィルター３１５は、プリントチケット３０４の印刷設定データのうち標準領域の設定を、プリンタジョブ言語（以下、ＰＪＬ（Ｐｒｉｎｔｅｒ  ＪｏｂＬａｎｇｕａｇｅ）と略する）に変換する。レンダラーフィルター３１５は、ＰＤＬとＰＪＬをまとめて印刷ジョブとして、次のフィルターにストリームデータとして渡す。レンダラーフィルター３１５は、変換したＰＪＬと渡されたＰＤＬとＰＪＬをまとめて印刷ジョブとして、フィルターパイプラインマネージャ３１２に渡す。
【００３９】
印刷ジョブは、印刷処理のスケジュール管理を行うプリントマネージャ３１８に送られ、キュー（待ち行列）に印刷ジョブが次々と登録される。プリントマネージャ３１８は、プリンター１０４２と通信してプリンター１０４２が印刷できる状態になり次第、キューに登録した順にポートモニター３１９を通して印刷ジョブを送信する。
【００４０】
このようにして、描画データをおよび印刷設定データを印刷ジョブに変換することがプリンタードライバーの主な役目であり、情報処理装置での印刷が実施される。
【００４１】
なお、本実施例では、印刷処理を用いて説明しているためプリンタードライバーと記載しているが、ＦＡＸ送信処理の際にも本発明を適用できる。この場合は、レンダラーフィルター３１５の代わりにＦＡＸデータを作成するＦＡＸフィルターを搭載して、ＦＡＸ機器に送信するＦＡＸドライバーとなる。
【００４２】
図４は、ポートモニター３１９のうち、ＵＳＢ接続時のポートモニターを表した図である。ＵＳＢ  ＢｉｄｉＥｘｔｅｎｄｅｒ４０１と、ＵＳＢポートモニター４０２の２つによって、ＵＳＢ接続時のポートモニターの機能が実現されている。ＵＳＢ  ＢｉｄｉＥｘｔｅｎｄｅｒ４０１は、ＪａｖａＳｃｒｉｐｔのプログラム４０１１と、Ｂｉｄｉ  ＸＭＬファイル４０１２の２つからなる。ＵＳＢ  ＢｉｄｉＥｘｔｅｎｄｅｒ４０１が呼び出されるのは、フィルターによって生成された印刷ジョブがプリントマネージャ３１８を通してプリンター１０４２に送信されるときと、デバイスアプリ３１０などからプリントマネージャ３１８を通してプリンター１０４２から情報を取得するときである。印刷ジョブが送信されるときは、Ｖ４プリンタードライバー１０５２で“ＨｏｓｔＢａｓｅｄＤｅｖｉｃｅ”のフラグを立てることで、ＪａｖａＳｃｒｉｐｔのプログラム４０１１にある以下の３つのインターフェースが呼び出される。
【００４３】
ｓｔａｒｔＰｒｉｎｔＪｏｂ（）
ｗｒｉｔｅＰｒｉｎｔＤａｔａ（）
ｅｎｄＰｒｉｎｔＪｏｂ（）
また、プリンター１０４２から情報を取得するときは、アプリケーションが、Ｂｉｄｉインターフェースを使って、ＢｉｄｉＸＭＬファイル４０１２に記載されたスキーマに基づき、ＪａｖａＳｃｒｉｐｔのプログラム４０１１にある以下のインターフェースが呼び出される。
【００４４】
ｇｅｔＳｃｈｅｍａｓ（）
ＪａｖａＳｃｒｉｐｔのプログラム４０１１は、呼び出されたインターフェースを通じて、ＵＳＢポートモニター４０２に命令を送り、ＵＳＢＰＲＩＮＴカーネルドライバー４０３を使ってプリンター１０４２と通信する。このように、Ｖ４プリンタードライバー１０５２は、ＵＳＢ  ＢｉｄｉＥｘｔｅｎｄｅｒ４０１を通じて、プリンター１０４２と双方向のデータをやりとりする通信を行う。
【００４５】
Ｖ４プリンタードライバー１０５２のＵＳＢ  Ｂｉｄｉ Ｅｘｔｅｎｄｅｒ４０１を使用したＵＳＢ通信の転送モードは、大容量データの一括転送を行うバルク転送モードしか使用できない。プリンター１０４２のステータス情報や設定値などは、半二重通信が可能なコントロール転送モードを使用して通信したいが、ＵＳＢ  ＢｉｄｉＥｘｔｅｎｄｅｒ４０１では、エンドポイント０のコントロール転送モードはサポートされていない。したがって、巨大なＰＤＬのデータを送信する印刷を行いながら、プリンター１０４２のステータス情報を取得するためのコマンドを発行することはできず、印刷データの転送が終了するまで、ステータス情報を取得できなくなっている。また、複数のアプリやドライバーがＢｉｄｉインターフェースを同時に使用すると、印刷データの途中にステータス情報コマンドが挿入されてしまい、プリンター１０４２がエラーになってしまう。そこで、ＤＯＴ４のチャネルをＪａｖａＳｃｒｉｐｔのプログラム４０１１で確立して、双方向で同時に通信する手段を実現する。
【００４６】
図５はＤＯＴ４のパケットを示したものである。ＤＯＴ４では、送信側機器をＰｒｉｍａｒｙＳｏｃｋｅｔＩＤ（ＰＳＩＤ）というＳｏｃｋｅｔＩＤで示し、受信側機器をＳｅｃｏｎｄａｒｙＳｏｃｋｅｔＩＤ（ＳＳＩＤ）というＳｏｃｋｅｔＩＤで示す。パケットは、基本的には６バイトのヘッダ部と、ヘッダ部に続くデータ部から構成されている。ヘッダ部は、先頭から、チャネルをオープンした側を認識するＰＳＩＤ、オープンされたチャネルの相手先を認識するＳＳＩＤ、パケットのデータサイズ（バイト数）を示す長さ（Ｌｅｎｇｔｈ）、相手に渡すクレジット数コントロール(ノーマルパケットは「０」）を示すクレジット部、当該パケットのコマンドを示す制御部（Ｃｏｎｔｒｏｌ）より構成される。
【００４７】
それぞれのＳｏｃｋｅｔＩＤを接続したデータの経路がチャネルとなる。チャネルは複数確立することもでき、それぞれで異なるデータを送受信することができる。本実施例では、コマンド用、ジョブ用、管理用という３つのチャネルを確立する。チャネルの通信路上には、パケットを１つのまとまりとして送受信する。チャネルを分離し、パケット単位でデータのやりとりをすることで、同時に複数の機能の双方向通信を行う。
【００４８】
また、ＤＯＴ４ではＣｒｅｄｉｔＢａｓｅｄＴｒａｓｎｆｅｒ（ＣＢＴ）と呼ばれるクレジットベースの転送方式が用いられる。通常の転送方式では、データを受け取る受信側機器が持つ受信バッファのデータがあふれたら、送信側機器に対してフロー信号を送信して、一時的に送信を停止する。その後、受信バッファがクリアされたら、再度送信を開始することで、通信を途切れないように行う。この方式は、フロー信号のやりとりが発生し、パフォーマンスが落ちることと、フロー信号が間に合わずデータの取りこぼしが発生する可能性がある。
【００４９】
クレジットベースの転送方式では、送信側機器は、まず受信側機器に送信できるデータのサイズを問い合わせて、データ量に対応したクレジットと呼ばれる権利を受け取る。受信したクレジットに対するサイズについては送信できることが保障されているので、データの送信を行い、クレジットがなくなったら再度クレジットを要求する。このクレジット方式では、送信側から制御しているので、安定した通信が可能になる。
【００５０】
以下、印刷ジョブを送信するための開始処理からシーケンスを説明する。全体のシーケンスは図５にある。なお、本実施例のプログラム上では、エラー処理をほとんど記載していないが、実際は、すべてのメソッドをコールするたびにエラー処理を行う必要がある。また、変数定義も記述スペースの都合により省略しているが、実際には正しく定義する必要がある。
【００５１】
ＵＳＢポートモニター４０２は、フィルターによって生成された印刷ジョブが、プリントマネージャ３１８を通じて到達すると、ＪａｖａＳｃｒｉｐｔのプログラム４０１１のｓｔａｒｔＰｒｉｎｔＪｏｂ（）を呼び出す。ｓｔａｒｔＰｒｉｎｔＪｏｂでは、ＣｒｅｄｉｔＢａｓｅｄＴｒａｎｓｆｅｒＭａｎａｇｅｒ（ＣＢＴＭｇｒ）のＯｐｅｎを呼び出して、初期化を行う。成功したら、プロパティバッグ３１７にＰＳＩＤを詰めておく。
【００５２】
function startPrintJob(usbJobContext, printerStream, printerBidiSchemaResponses)
{
ret = cbtMgr.Open(cbtDef.CBT_JOB_SSID);
if (ret.ret === cbtDef.CBT_SUCCESS) {
properties = cbtMgr.GetProperty();
properties.openedPsid = ret.psid;
usbJobContext.jobPropertyBag.SetString("Dot4Cache", json.stringify(properties));
}
}
ＣＢＴＭｇｒのＯｐｅｎでは、Ｉｎｉｔを呼び出してＤＯＴ４の初期化を行った後、データを送信するために、ＯｐｅｎＣｈａｎｎｅｌでチャネルをオープンする。チャネルのオープンに成功したら、クレジットとともにキャッシュ用バッファの最大サイズをＳＳＩＤから取得するので、保存しておく。
【００５３】
that.Open = function () {
ret = my.CbtInit();
ret1 = my.CbtOpenChannel(ssid);
if ((ret1.ret === my.def.CBT_SUCCESS) {
ret2 = my.m_CbtChannelList.GetPktSizeFromChannel(ret1.psid);
my.m_JobCacheSize = ret2.priToSecPktSize - my.def.CBT_HEADER_SIZE;         my.m_JobCacheDataSize = 0;
}
}
Ｉｎｉｔでは実際にプリンター１０４２に対してＩｎｉｔコマンドを送信し、プリンター１０４２がＤＯＴ４モードに移行できるようにする。Ｉｎｉｔコマンドは、コマンドチャネルを使って送信するので、チャネルを作成する際に、ＳＳＩＤにコマンドチャネル（ＣＢＴ＿ＣＭＤ＿ＳＳＩＤ）を指定する。通常、ＤＯＴ４のチャネルを使用するには、ＯｐｅｎＣｈａｎｎｅｌコマンドを発行する必要があるが、コマンドチャネルは特別なチャネルで、発行しなくても使用することができる。Ｉｎｉｔコマンドを送信し終わったら、コマンドチャネルを閉じる。
【００５４】
my.CbtInit = function () {
ret = my.m_CbtChannelList.CreateChannel(my.def.CBT_CMD_SSID);
my.m_CbtChannelList.MngCredit(ret.psid, my.def.CBT_READ_DIRECTION, my.CBT_START_CMD_CREDIT);
ret2 = my.m_CbtPkt.CmdInit();
if (ret2.ret !== my.def.CBT_REPLY_SUCCESS) {
my.m_CbtChannelList.DestroyChannel(ret.psid);
}
}
ＣｍｄＩｎｉｔでは、実際にパケットを生成してＩｎｉｔコマンドを送信する。パケットは図５のＤＯＴ４の構成にした構造体を元にエンコードすることで作成し、データを送信する。応答のパケット（ＩｎｉｔＲｅｐｌｙ）が返ってくるまでＷａｉｔで待った後、受信したパケットのデータをデコードすることで、プリンター１０４２が返してきたデータを取得する。
【００５５】
that.CmdInit = function () {
param = {
header: {
psid: my.def.CBT_CMD_PSID,
ssid: my.def.CBT_CMD_SSID,
length: my.CBT_CMD_INIT_SIZE,
credit: 1,
control: my.CBT_CONTROL_CMD
},
data: {
command: my.def.IEEE1284_4_CMD_INIT,
revision: revision
}
}
cmdPkt = my.m_CbtCodec.Encode(param);
my.SendPkt(my.def.CBT_CMD_PSID, cmdPkt);
ret = my.WaitReplyPkt(my.def.CBT_CMD_PSID, my.def.IEEE1284_4_CMD_INIT);
data = my.m_CbtCodec.DecodeData(ret.packet.slice(my.def.CBT_HEADER_SIZE));
}
Ｅｎｃｏｄｅでは、入力された構造体にしたがって、パケットを生成する。ＤＯＴ４のコマンドの場合は、それぞれ専用のメソッドでパケットを生成する。コマンド以外の場合は、データとしてパケットを作成する。パケットの作成の仕方は、１ｂｙｔｅもしくは数ｂｙｔｅずつ値を詰めていく方式をとっており、ＥｎｃｏｄｅＢｙｔｅなどの関数で詰めていく。
【００５６】
CNBIDI.cbt.std.Codec.Encode = function (param) {
var packet = [];
my.codec.EncodeByte(param.header.psid, packet);
my.codec.EncodeByte(param.header.ssid, packet);
my.codec.EncodeShort(param.header.length, packet);
my.codec.EncodeByte(param.header.credit, packet);
my.codec.EncodeByte(param.header.control, packet);
if ((param.header.psid === my.def.CBT_CMD_PSID) && (param.header.ssid === my.def.CBT_CMD_SSID)) {
// command packet
my.codec.EncodeByte(param.data.command, packet);
switch (param.data.command) {
case my.def.IEEE1284_4_CMD_INIT:
my.EncodeCmdInit(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_OPENCHANNEL:
my.EncodeCmdOpenChannel(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_CLOSECHANNEL:
my.EncodeCmdCloseChannel(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_CREDIT:
my.EncodeCmdCredit(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_CREDITREQUEST:
my.EncodeCmdCreditRequest(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_EXIT:
break;
case my.def.IEEE1284_4_CMD_GET_SOCKETID:
break;
//case IEEE1284_4_CMD_GET_SERVICENAME:
//   break;
case my.def.IEEE1284_4_CMD_ERROR:
my.EncodeCmdError(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_INIT_REPLY:
my.EncodeCmdInitReply(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_OPENCHANNEL_REPLY:
my.EncodeCmdOpenChannelReply(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_CLOSECHANNEL_REPLY:
my.EncodeCmdCloseChannelReply(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_CREDIT_REPLY:
my.EncodeCmdCreditReply(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_CREDITREQUEST_REPLY:
my.EncodeCmdCreditRequestReply(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_EXIT_REPLY:
my.EncodeCmdExitReply(param.data, packet);
break;
case my.def.IEEE1284_4_CMD_GET_SOCKETID_REPLY:
break;
// case my.def.IEEE1284_4_CMD_GET_SERVICENAME_REPLY:
//   break;
default:
break;
}
} else {
// data packet
my.codec.EncodeBinary(param.data, Math.min(param.header.length - my.def.CBT_HEADER_SIZE, param.data.length), packet);
}
return packet;
}
return that;
}
that.EncodeByte = function (data, packet) {
packet.push(data);
}
ＳｅｎｄＰｋｔではエンコードで作成したパケットを、送信する。まず、ｗｒｉｔｅ用のクレジットの数を確認して、クレジットがなければ送信しない。つぎに、ＵＳＢポート４０２に対して、パケットを送信する。パケットにはＳＳＩＤに対してｒｅａｄ用のクレジットを付与して送信する。パケットを送信したら、ｒｅａｄ用のクレジットは付与した分だけ追加しｗｒｉｔｅ用のクレジットは１つ減らす。
【００５７】
CNBIDI.cbt.cn.Pkt.SendPkt = function (psid, packet) {
var ret,
header;
ret = my.m_CbtChannelList.GetNumCredit(psid, my.def.CBT_WRITE_DIRECTION);
if (ret.credit === 0) {
return my.def.CBT_ERROR_NO_CREDIT;
}
if (!my.m_UsbIo.Send(packet)) {
return my.def.CBT_ERROR_WRITE_FAILD;
}
my.m_CbtChannelList.MngCredit(psid, my.def.CBT_READ_DIRECTION, header.credit);
my.m_CbtChannelList.MngCredit(psid, my.def.CBT_WRITE_DIRECTION, -1);
return my.def.CBT_SUCCESS;
}
ＵＳＢポート４０２に対してのＳｅｎｄは、プリンターストリームにデータを書き込むことで行われる。
【００５８】
UsbIo.Send = function (sendBuff) {
var result = false,
totalWrittenSize = 0,
time = Time(),
writtenSize;
while ((totalWrittenSize < sendBuff.length) && (time.GetTime() < m_WriteTimeout)) {
try {
writtenSize = printerStream.Write(sendBuff.slice(totalWrittenSize));
} catch (e) {
}
totalWrittenSize += writtenSize;
if (totalWrittenSize === sendBuff.length) {
result = true;
break;
} else if (totalWrittenSize === 0) {
break;
}
return result;
}
ＷａｉｔＲｅｐｌｙＰｋｔは、応答のパケットが返ってくるまで待つ。タイムアウトするか、規定回数パケットが取得できるまでリトライを行う。
【００５９】
my.WaitReplyPkt = function (psid, cmdId) {
while ((time.GetTime() < timeout) ||  (strike < my.STRIKEOUT))                {
ret1 = that.RecvPkt(psid);
if (ret1 < 0) {
if (ret1 === my.def.CBT_ERROR_NO_PKT) {
strike += 1;
}
else if (ret1 === my.def.CBT_ERROR_INVALID_PARAM) {
return { ret: my.def.CBT_ERROR_FATAL, packet: packet };
}
else {
return { ret: ret1, packet: packet };
}
}
ret2 = my.m_CbtChannelList.GetNumPktInChannel(psid);
if (ret2.numPkt < 0) {
return { ret: my.def.CBT_ERROR_FATAL, packet: packet };
}
found = false;
for (i = ret2.numPkt; i > 0; i--) {
ret3 = my.m_CbtChannelList.GetPktFromChannel(psid);
if (ret3.ret >= 0) {
data = my.m_CbtCodec.DecodeData(ret3.packet.slice(my.def.CBT_HEADER_SIZE));
if (data.command & 0x80) {
if (data.command === (cmdId | 0x80)) {
found = true;
packet = ret3.packet;
break;
}
}
}
}
if (found) {
return { ret: my.def.CBT_SUCCESS, packet: packet };
}
if (ret1 === 0) {
time.Start();
}
}
return { ret: my.def.CBT_ERROR_TIMEOUT, packet: packet };
}
ＲｅｃｖＰｋｔではポートから１パケット取得して、パケットに含まれているｗｒｉｔｅクレジットを更新し、データをデコードする。ｒｅａｄクレジットは読み込んだので１つ減らす。コマンドパケットだったら、それぞれのコマンドパケットを読み込む。
【００６０】
that.RecvPkt = function (psid) {
ret1 = my.Recv1PktFromPort();
header = my.m_CbtCodec.DecodeHeader(ret1.packet.slice(0, my.def.CBT_HEADER_SIZE));
my.m_CbtChannelList.MngCredit(header.psid, my.def.CBT_WRITE_DIRECTION, header.credit);
my.m_CbtChannelList.MngCredit(header.psid, my.def.CBT_READ_DIRECTION, -1);
if ((header.psid === my.def.CBT_CMD_PSID) && (header.ssid === my.def.CBT_CMD_SSID)) {
data = my.m_CbtCodec.DecodeData(ret1.packet.slice(my.def.CBT_HEADER_SIZE));
if (!(data.command & 0x80)) {
ret2 = my.RecvCmdPkt(ret1.packet);
if ((ret2 === my.def.CBT_SUCCESS)
return my.def.CBT_SUCCESS;
}
}
ret3 = my.m_CbtChannelList.PutPktToChannel(header.psid, ret1.packet);
desiredDataLength = 0;
if (psid === header.psid) {
desiredDataLength = header.length;
}
return desiredDataLength;
}
Ｒｅｃｖ１ＰｋｔＦｒｏｍＰｏｒｔでは、ポートから１パケット取得するために、ヘッダーサイズと、データサイズをストリームから読み込む。
【００６１】
my.Recv1PktFromPort = function () {
packet = my.m_UsbIo.Recv(my.def.CBT_HEADER_SIZE);
header = my.m_CbtCodec.DecodeHeader(packet.slice(0, my.def.CBT_HEADER_SIZE));
if (header.length > my.def.CBT_HEADER_SIZE) {
Array.prototype.push.apply(packet, my.m_UsbIo.Recv(header.length - my.def.CBT_HEADER_SIZE));
}
return { ret: my.def.CBT_SUCCESS, packet: packet };
}
ＵＳＢポート４０２に対してのＲｅｃｖは、プリンターストリームから読み込むことで行われる。
【００６２】
that.Recv = function (size) {
reqSize = size - totalReadBuff.length;
readSize = 0;
do {
isReadSuccess = false;
readBuff = [];
if (readSize > 0) {
readBuff = printerStream.Read(readSize);
if (readBuff.length > 0) {
isReadSuccess = true;
}
}
if (isReadSuccess) {
Array.prototype.push.apply(totalReadBuff, GetCacheData(reqSize));
Array.prototype.push.apply(totalReadBuff, readBuff.splice(0, Math.min(size - totalReadBuff.length, readBuff.length)));
SetCacheData(readBuff);
}
} while (isReadSuccess &&
(size > totalReadBuff.length) &&
(totalReadBuff.length > 0) &&
(time.GetTime() < m_ReadTimeout));
return totalReadBuff;
}
ＲｅｃｖＣｍｄＰｋｔはコマンドパケットを取得して、コマンドに従った処理を実行する。クレジットコマンドであれば、ＳＳＩＤから受け取ったｗｒｉｔｅのクレジットを増やし、クレジットリクエストコマンドであれば、ＳＳＩＤにｒｅａｄのクレジットを渡す。
【００６３】
my.RecvCmdPkt = function (packet) {
header = my.m_CbtCodec.DecodeHeader(packet.slice(0, my.def.CBT_HEADER_SIZE));
if ((header.psid !== my.def.CBT_CMD_PSID) || (header.ssid !== my.def.CBT_CMD_SSID)) {
return my.def.CBT_SUCCESS;
}
data = my.m_CbtCodec.DecodeData(packet.slice(my.def.CBT_HEADER_SIZE));
switch (data.command) {
case my.def.IEEE1284_4_CMD_CREDIT:
my.m_CbtChannelList.MngCredit(data.psid, my.def.CBT_WRITE_DIRECTION, data.credit);
my.CmdCreditReply(result, data.psid, data.ssid);
break;
case my.def.IEEE1284_4_CMD_CREDITREQUEST:
creditReq = data.creditRequested;
ret = my.m_CbtChannelList.GetNumPktInChannel(data.psid);
if (ret.numPkt >= 0) {
maxPkt = ret.maxPkt;
numPkt = ret.numPkt;
ret = my.m_CbtChannelList.GetNumCredit(data.psid, my.def.CBT_READ_DIRECTION);
if (ret.ret === my.def.CBT_SUCCESS) {
remainCredit = maxPkt - numPkt - ret.curCredit;
if (remainCredit > 0) {
creditGra = Math.min(creditReq, remainCredit);
}
}
}
my.CmdCreditRequestReply(result, data.psid, data.ssid, creditGra);
my.m_CbtChannelList.MngCredit(data.psid, my.def.CBT_READ_DIRECTION, creditGra);
break;
case my.def.IEEE1284_4_CMD_INIT_REPLY:
case my.def.IEEE1284_4_CMD_OPENCHANNEL_REPLY:
case my.def.IEEE1284_4_CMD_CLOSECHANNEL_REPLY:
case my.def.IEEE1284_4_CMD_CREDIT_REPLY:
case my.def.IEEE1284_4_CMD_CREDITREQUEST_REPLY:
case my.def.IEEE1284_4_CMD_GET_SOCKETID_REPLY:
my.m_CbtChannelList.PutPktToChannel(header.psid, packet);
break;
case my.def.IEEE1284_4_CMD_ERROR:
my.m_CbtChannelList.MngCredit(header.psid, my.def.CBT_READ_DIRECTION, 1);
my.m_CbtChannelList.MngCredit(header.psid, my.def.CBT_WRITE_DIRECTION, 2);
my.WaitExitCmd();
return my.def.CBT_ERROR_EXIT_DOT4;
break;
case my.def.IEEE1284_4_CMD_EXIT:
my.CmdExitReply();
return my.def.CBT_ERROR_EXIT_DOT4;
break;
case my.def.IEEE1284_4_CMD_INIT:
my.CmdInitReply(my.def.CBT_REPLY_ERR_NOT_AVAILABLE, 0x10);
break;
case my.def.IEEE1284_4_CMD_OPENCHANNEL:
my.CmdOpenChannelReply(my.def.CBT_REPLY_ERR_NOT_AVAILABLE, data.psid, data.ssid, 0, 0, 0, 0);
break;
case my.def.IEEE1284_4_CMD_CLOSECHANNEL:
my.CmdCloseChannelReply(my.def.CBT_REPLY_ERR_NOT_AVAILABLE, data.psid, data.ssid);
break;
}
return my.def.CBT_SUCCESS;
}
Ｉｎｉｔコマンドを送信すると、ＩｎｉｔＲｅｐｌｙが返答として返ってくるので、ＰｕｔＰｋｔＴｏＣｈａｎｎｅｌでパケットを読み込む。
【００６４】
that.PutPktToChannel = function (packet) {
header = m_CbtCodec.DecodeHeader(packet.slice(0, def.CBT_HEADER_SIZE));
if (m_ReadNumPkt === m_ReadMaxPkt) {
ret = that.DeleteOldPacket();
}
Array.prototype.push.apply(m_ReadBuffer, packet.slice(0, header.length));
m_ReadNumPkt += 1;
return header.length;
}
このようにして、Ｉｎｉｔコマンドは送信される。次に、ＯｐｅｎＣｈａｎｎｅｌが行われ、印刷ジョブデータの送受信を行うためのチャネルを確立する。これはＣｂｔＯｐｅｎＣｈａｎｎｅｌで行われる。ＣｂｔＯｐｅｎＣｈａｎｎｅｌでは、実際にＣｍｄＯｐｅｎＣｈａｎｎｅｌを呼び出してチャネルを確立するのと同時に、どのくらいのチャネルが確立されているかを管理するリストに対して、追加する。
【００６５】
my.CbtOpenChannel = function (ssid) {
switch (ssid) {
case my.def.CBT_JOB_SSID:
priToSecPktSize = my.CBT_JOB_PKTSIZE;
secToPriPktSize = my.CBT_JOB_PKTSIZE;
creditRequested = my.CBT_JOB_CREDITREQUEST;
maxOutstandingCredit = my.CBT_JOB_MAX_CREDIT;
break;
case my.def.CBT_ADMIN_SSID:
priToSecPktSize = my.CBT_ADMIN_PKTSIZE;
secToPriPktSize = my.CBT_ADMIN_PKTSIZE;
creditRequested = my.CBT_ADMIN_CREDITREQUEST;
maxOutstandingCredit = my.CBT_ADMIN_MAX_CREDIT;
break;
case my.def.CBT_RESOURCE_SSID:
priToSecPktSize = my.CBT_RESOURCE_PKTSIZE;
secToPriPktSize = my.CBT_RESOURCE_PKTSIZE;
creditRequested = my.CBT_RESOURCE_CREDITREQUEST;
maxOutstandingCredit = my.CBT_RESOURCE_MAX_CREDIT;
break;
default:
return { ret: my.def.CBT_ERROR_INVALID_PARAM, psid: psid };
break;
}
credit = creditRequested;
ret1 = my.m_CbtChannelList.CreateChannel(ssid);
ret2 = my.m_CbtPkt.CmdOpenChannel(ret1.psid, ssid, priToSecPktSize, secToPriPktSize, creditRequested, maxOutstandingCredit);
ret3 = my.m_CbtChannelList.OpenChannel(ret1.psid, ret2.priToSecPktSize, ret2.secToPriPktSize, credit, ret2.maxOutstandingCredit, ret2.credit);
return { ret: my.def.CBT_SUCCESS, psid: ret1.psid };
}
ＣｍｄＯｐｅｎＣｈａｎｎｅｌではＯｐｅｎＣｈａｎｎｅｌのコマンドパケットを生成して、エンコードし、パケットを送信する。応答（ＯｐｅｎＣｈａｎｎｅｌＲｅｐｌｙ）を待って、デコードして反映させる。ＯｐｅｎＣｈａｎｎｅｌＲｅｐｌｙでは、目的（ここでは印刷ジョブデータの送信）に応じて、ＳＳＩＤからクレジットが発行される。
【００６６】
that.CmdOpenChannel = function (psid, ssid, priToSecPktSize, secToPriPktSize, creditRequested, maxOutstandingCredit) {
param = {
header: {
psid: my.def.CBT_CMD_PSID,
ssid: my.def.CBT_CMD_SSID,
length: my.CBT_CMD_OPENCHANNEL_SIZE_STD,
credit: 1,
control: my.CBT_CONTROL_CMD
},
data: {
command: my.def.IEEE1284_4_CMD_OPENCHANNEL,
psid: psid,
ssid: ssid,
priToSecPktSize: priToSecPktSize,
secToPriPktSize: secToPriPktSize,
maxOutstandingCredit: maxOutstandingCredit
}
}
cmdPkt = my.m_CbtCodec.Encode(param);
my.SendPkt(my.def.CBT_CMD_PSID, cmdPkt);
ret = my.WaitReplyPkt(my.def.CBT_CMD_PSID, my.def.IEEE1284_4_CMD_OPENCHANNEL);
data = my.m_CbtCodec.DecodeData(ret.packet.slice(my.def.CBT_HEADER_SIZE));
return {
ret: data.result,
priToSecPktSize: data.priToSecPktSize,
secToPriPktSize: data.secToPriPktSize,
maxOutstandingCredit: data.maxOutstandingCredit,
credit: data.credit
};
}
このようにして、ｓｔａｒｔＰｒｉｎｔＪｏｂでは、ＤＯＴ４のＩｎｉｔコマンドを発行し、ＤＯＴ４モードに移行したのち、ＯｐｅｎＣｈａｎｎｅｌコマンドでチャネルを確立する。
【００６７】
次にＵＳＢポートモニター４０２は、ＪａｖａＳｃｒｉｐｔのプログラム４０１１のｗｒｉｔｅＰｒｉｎｔＤａｔａを呼び出す。ｗｒｉｔｅＰｒｉｎｔＤａｔａでは、ｓｔａｒｔＰｒｉｎｔＪｏｂでオープンしたチャネルに対して、印刷ジョブのデータを送信していく。まずは、プロパティバッグ３１７からＰＳＩＤを取得し、ｓｔａｒｔＰｒｉｎｔＪｏｂで確立したチャンネルのデータを取得する。そして、ＣｒｅｄｉｔＢａｓｅｄＴｒａｓｎｆｅｒＭａｎａｇｅｒ（ＣＢＴＭｇｒ）のＳｅｎｄを呼び出して、印刷ジョブの送信を行う。送信したら、プロパティバッグ３１７のＰＳＩＤを更新しておく。
【００６８】
function writePrintData(usbJobContext, printData, printerStream, writePrintDataProgress, printerBidiSchemaResponses) {
dot4Cache = json.parse(usbJobContext.jobPropertyBag.GetString("Dot4Cache"));
cbtMgr = CbtFac("2000", usbIo);
if (cbtMgr !== undefined) {
cbtMgr.SetProperty(dot4Cache);
ret = cbtMgr.Send(dot4Cache.openedPsid, printData);
newProperties = cbtMgr.GetProperty();
newProperties.openedPsid = dot4Cache.openedPsid;
usbJobContext.jobPropertyBag.SetString("Dot4Cache", json.stringify(newProperties));
}
return retVal;
}
Ｓｅｎｄでは、ＷｒｉｔｅＤａｔａを使用してデータを送信する。
that.Send = function (psid, data) {
var ret = my.WriteData(psid, data, true);
return ret;
}
ＷｒｉｔｅＤａｔａでは、印刷ジョブデータの送信を行うため、タイムアウトを設定し、キャッシュデータに保存して、ｗｒｉｔｅ用のクレジットの残り数を確認し、パケットを送信する。ＷｒｉｔｅＰｒｉｎｔＤａｔａは、印刷ジョブがすべて一度の呼び出しで渡されるわけではなく、いろいろなデータサイズで多数呼び出される。ＤＯＴ４では、クレジットで指定されたバッファの最大値のデータを送付したほうが、通信速度としても有利なので、バッファの値までデータをキャッシュしてから、送信するようにしている。
【００６９】
my.WriteData = function (psid, data, isJobDataCaching) {
ret1 = my.m_CbtChannelList.GetChannelTimeout(psid);
writeTimeout = ret1.writeTimeout;
ret2 = my.m_CbtChannelList.GetNumCredit(psid, my.def.CBT_WRITE_DIRECTION);
curCredit = ret2.curCredit;
isJobChannel = my.m_CbtChannelList.IsJobChannel(psid);
isWriteJobCache = false;
while ((time.GetTime() < writeTimeout) && (remainDataSize > 0)) {
isWriteJobCache = false;
if (isJobDataCaching && isJobChannel) {
if (my.m_JobCacheDataSize === my.m_JobCacheSize) {
isWriteJobCache = true;
}
else if (my.m_JobCacheDataSize > 0) {
if (remainDataSize + my.m_JobCacheDataSize >= my.m_JobCacheSize) {
Array.prototype.push.apply(my.m_JobCacheBuff, data.slice(writtenDataSize, writtenDataSize + my.m_JobCacheSize - my.m_JobCacheDataSize));
writtenDataSize += (my.m_JobCacheSize - my.m_JobCacheDataSize);
remainDataSize -= (my.m_JobCacheSize - my.m_JobCacheDataSize);
my.m_JobCacheDataSize = my.m_JobCacheSize;
isWriteJobCache = true;
} else {
Array.prototype.push.apply(my.m_JobCacheBuff, data.slice(writtenDataSize, writtenDataSize + remainDataSize));
my.m_JobCacheDataSize += remainDataSize;
writtenDataSize += remainDataSize;
remainDataSize = 0;
}
} else {
if (remainDataSize >= my.m_JobCacheSize) {
} else {
Array.prototype.push.apply(my.m_JobCacheBuff, data.slice(writtenDataSize, writtenDataSize + remainDataSize));
my.m_JobCacheDataSize = remainDataSize;
writtenDataSize += remainDataSize;
remainDataSize = 0;
}
}
}
if ((remainDataSize <= 0) && !isWriteJobCache) {
break;
}
if (curCredit === 0) {
if (sleepTime === 0) {
sleepTime = my.CREDIT_ZERO_TIMEOUT_CONSTANT;
} else {
sleepTime *= 2;
if (sleepTime > my.CREDIT_ZERO_TIMEOUT_LIMITER) {
sleepTime = my.CREDIT_ZERO_TIMEOUT_LIMITER;
}
Sleep(sleepTime);
}
creditRequestTime = Time();
ret = my.CbtCreditRequest(psid, 0xFFFF, 0xFFFF);
ret3 = my.m_CbtChannelList.GetNumCredit(psid, my.def.CBT_WRITE_DIRECTION);
ret = ret3.ret;
curCredit = ret3.curCredit;
if (curCredit === 0) {
if (++zeroCreditCount > my.CREDIT_REQUEST_LIMIT) {
break;
} else {
continue;
}
} else {
sleepTime = 0;
writeTimeout += creditRequestTime.GetTime();
}
}
if (isWriteJobCache) {
ret = my.m_CbtPkt.SendDataPkt(psid, my.m_JobCacheBuff.slice(0, my.m_JobCacheDataSize));
} else {
ret = my.m_CbtPkt.SendDataPkt(psid, data.slice(writtenDataSize, writtenDataSize + remainDataSize));
}
if (ret > 0) {
--curCredit;
if (isWriteJobCache) {
if (ret === my.m_JobCacheDataSize) {
my.m_JobCacheBuff.length = 0;
my.m_JobCacheDataSize = 0;
} else {
my.m_JobCacheBuff.splice(0, ret);
my.m_JobCacheDataSize -= ret;
}
} else {
writtenDataSize += ret;
remainDataSize -= ret;
}
} else {
break;
}
}
if ((writtenDataSize > 0) || (ret >= 0)) {
ret = writtenDataSize;
}
return ret;
}
ＣｂｔＣｒｅｄｉｔＲｅｑｕｅｓｔでは、クレジットをＳＳＩＤに対して要求する。クレジットを受け取ったら、ｗｒｉｔｅ用クレジットを更新する。
【００７０】
my.CbtCreditRequest = function (psid, creditRequest, maxOutstandingCredit) {
ret1 = my.m_CbtChannelList.GetSsid(psid);
ret2 = my.m_CbtPkt.CmdCreditRequest(psid, ret1.ssid, creditRequest, maxOutstandingCredit);
my.m_CbtChannelList.MngCredit(psid, my.def.CBT_WRITE_DIRECTION, ret2.credit);
return my.def.CBT_SUCCESS;
}
ＣｍｄＣｒｅｄｉｔＲｅｑｅｕｓｔでは、クレジットリクエストのパケットを生成し、送信する。
【００７１】
that.CmdCreditRequest = function (psid, ssid, creditRequest, maxOutstandingCredit) {
param = {
header: {
psid: my.def.CBT_CMD_PSID,
ssid: my.def.CBT_CMD_SSID,
length: my.CBT_CMD_CREDITREQUEST_SIZE_STD,
credit: 1,
control: my.CBT_CONTROL_CMD
},
data: {
command: my.def.IEEE1284_4_CMD_CREDITREQUEST,
psid: psid,
ssid: ssid,
maxOutstandingCredit: maxOutstandingCredit
}
}
cmdPkt = my.m_CbtCodec.Encode(param);
my.SendPkt(my.def.CBT_CMD_PSID, cmdPkt);
ret = my.WaitReplyPkt(my.def.CBT_CMD_PSID, my.def.IEEE1284_4_CMD_CREDITREQUEST);
data = my.m_CbtCodec.DecodeData(ret.packet.slice(my.def.CBT_HEADER_SIZE));
return {
ret: data.result,
credit: data.credit
};
}
クレジットが正しく受け取れたら、印刷ジョブのデータを送信するために、ＳｅｎｄＤａｔａＰｋｔで、パケットを作成して送信する。
【００７２】
that.SendDataPkt = function (psid, data) {
ret1 = my.CreateDataPkt(psid, data);
ret2 = my.SendPkt(psid, ret1.packet);
}
ＣｒｅａｔｅＤａｔａＰｋｔでは、印刷ジョブのためのパケットを生成して、エンコードする。
【００７３】
my.CreateDataPkt = function (psid, data) {
ret1 = my.m_CbtChannelList.GetPktSizeFromChannel(psid);
ret2 = my.m_CbtChannelList.GetSsid(psid);
dataPktSize = Math.min(ret1.priToSecPktSize, data.length + my.def.CBT_HEADER_SIZE);
param = {
header: {
psid: psid,
ssid: ret2.ssid,
length: dataPktSize,
credit: piggy,
control: my.CBT_CONTROL_DATA_PKT
},
data: data.slice(0, dataPktSize - my.def.CBT_HEADER_SIZE)
}
packet = my.m_CbtCodec.Encode(param);
return { ret: dataPktSize - my.def.CBT_HEADER_SIZE, packet: packet };
}
以上のように、ｗｒｉｔｅＰｒｉｎｔＤａｔａでは、プリントマネージャ３１８から渡される印刷データをキャッシュしながら、印刷ジョブデータの送信を行う。
【００７４】
最後に、ＵＳＢポートモニター４０２は、ＪａｖａＳｃｒｉｐｔのプログラム４０１１のｅｎｄＰｒｉｎｔＪｏｂを呼び出す。ｅｎｄＰｒｉｎｔＪｏｂでは、プロパティバッグ３１７からＰＳＩＤを取得し、ｓｔａｒｔＰｒｉｎｔＪｏｂで確立したチャンネルのデータを取得する。そして、必要であればキャッシュをクリアし、ｃｂｔＭｇｒのＣｌｏｓｅを呼び出すことで、印刷ジョブデータの送信とＤＯＴ４を終了する。
【００７５】
function endPrintJob(usbJobContext, printerStream, printerBidiSchemaResponses) {
dot4Cache = json.parse(usbJobContext.jobPropertyBag.GetString("Dot4Cache"));
cbtMgr = CbtFac("2000", usbIo);
if (cbtMgr !== undefined) {
cbtMgr.SetProperty(dot4Cache);
if (!dot4Cache.isAborted) {
for (; ;) {
ret = cbtMgr.FlushJobCache(dot4Cache.openedPsid);
if (ret === 0) {
break;
} else if (ret > 0) {
} else {
break;
}
}
}
cbtMgr.Close(dot4Cache.openedPsid);
}
return retVal;
}
Ｃｌｏｓｅでは、ＣｌｏｓｅＣｈａｎｎｅｌのコマンドを送信してチャンネルをクローズした後、Ｅｘｉｔコマンドを送信してＤＯＴ４モードを終了する。
【００７６】
that.Close = function (psid) {
if (my.m_CbtChannelList.IsJobChannel(psid)) {
my.m_JobCacheBuff.length = 0;
my.m_JobCacheSize = 0;
my.m_JobCacheDataSize = 0;
}
var ret = my.CbtCloseChannel(psid);
if (ret === my.def.CBT_SUCCESS) {
if (!my.m_CbtChannelList.IsExistOpenedChannel()) {
if (my.ExitDot4() !== my.def.CBT_SUCCESS) {
}
my.m_CbtChannelList.DestroyAllChannel();
}
}
my.m_CbtChannelList.DestroyChannel(psid);
my.ExitDot4();
my.m_CbtChannelList.DestroyAllChannel();
return ret;
}
ＣｂｔＣｌｏｓｅＣｈａｎｎｅｌではＣｍｄＣｌｏｓｅＣｈａｎｎｅｌを呼び出して、ＣｌｏｓｅＣｈａｎｎｅｌを行う。
my.CbtCloseChannel = function (psid) {
ret1 = my.m_CbtChannelList.GetSsid(psid);
ret2 = my.m_CbtPkt.CmdCloseChannel(psid, ret1.ssid);
}
ＣｍｄＣｌｏｓｅＣｈａｎｎｅｌでは、ＣｌｏｓｅＣｈａｎｎｅｌのパケットを生成して、パケットを送信する。
【００７７】
that.CmdCloseChannel = function (psid, ssid) {
param = {
header: {
psid: my.def.CBT_CMD_PSID,
ssid: my.def.CBT_CMD_SSID,
length: my.CBT_CMD_CLOSECHANNEL_SIZE,
credit: 1,
control: my.CBT_CONTROL_CMD
},
data: {
command: my.def.IEEE1284_4_CMD_CLOSECHANNEL,
psid: psid,
ssid: ssid
}
}
cmdPkt = my.m_CbtCodec.Encode(param);
my.SendPkt(my.def.CBT_CMD_PSID, cmdPkt);
ret = my.WaitReplyPkt(my.def.CBT_CMD_PSID, my.def.IEEE1284_4_CMD_CLOSECHANNEL);
data = my.m_CbtCodec.DecodeData(ret.packet.slice(my.def.CBT_HEADER_SIZE));
return data.result;
}
ＥｘｉｔＤｏｔ４では、ＣｂｔＥｘｉｔを。ＣｂｔＥｘｉｔでは、ＣｍｄＥｘｉｔをそれぞれ呼び出して、Ｅｘｉｔコマンドを送信する。
【００７８】
my.ExitDot4 = function () {
var ret = my.CbtExit();
return ret;
}
my.CbtExit = function () {
var ret = my.m_CbtPkt.CmdExit();
return ret;
}
ＣｍｄＥｘｉｔでは、Ｅｘｉｔコマンドのパケットを生成して、パケットを送信する。
【００７９】
that.CmdExit = function () {
param = {
header: {
psid: my.def.CBT_CMD_PSID,
ssid: my.def.CBT_CMD_SSID,
length: my.CBT_CMD_EXIT_SIZE,
credit: 1,
control: my.CBT_CONTROL_CMD
},
data: {
command: my.def.IEEE1284_4_CMD_EXIT
}
}
cmdPkt = my.m_CbtCodec.Encode(param);
my.SendPkt(my.def.CBT_CMD_PSID, cmdPkt);
ret = my.WaitReplyPkt(my.def.CBT_CMD_PSID, my.def.IEEE1284_4_CMD_EXIT);data = my.m_CbtCodec.DecodeData(ret.packet.slice(my.def.CBT_HEADER_SIZE));
return data.result;
}
以上のように、ｅｎｄＰｒｉｎｔＪｏｂ（）では、ＣｌｏｓｅＣｈａｎｎｅｌとＥｘｉｔコマンドを使用して、ＤＯＴ４の印刷を終了する。
【００８０】
次に、プリンター１０４２に対して、構成情報取得でオプションの装着状態やステータスを取得するシーケンスを説明する。
【００８１】
ＵＳＢポートモニター４０２は、Ｂｉｄｉインターフェースが呼び出されると、ＪａｖａＳｃｒｉｐｔのプログラム４０１１のｇｅｔＳｃｈｅｍａｓを呼び出す。ｇｅｔＳｃｈｅｍａｓでは、ＣＢＴＭｇｒのＯｐｅｎを管理チャネルでオープンし、Ｓｅｎｄを呼び出した後、Ｒｅｃｖを呼び出して情報を取得する。取得し終われば、Ｃｌｏｓｅする。
【００８２】
function getSchemas(driverProperties, printerStream, schemaRequests, printerBidiSchemaResponses) {
cbtOpenRet = cbtMgr.Open(cbtDef.CBT_ADMIN_SSID);
// create data
m_CbtMgr.Send(m_Psid, data);
ret = m_CbtMgr.Recv(m_Psid, readSize);  
cbtMgr.Close(adminPsid);
return result;
}
Ｓｅｎｄは前述の印刷ジョブの部分に出てきたので、ここではＲｅｃｖのシーケンスを説明する。ＲｅｃｖはＲｅａｄＤａｔａを呼び出す。
【００８３】
that.Recv = function (psid, readSize) {
var ret = my.ReadData(psid, readSize);
return ret;
}
ＲｅａｄＤａｔａは、チャネルのタイムアウトを指定した後、パケット数を取得して、データがなければ、ＲｅｃｖＰｋｔでパケットからデータを取得する。その後、ＧｅｔＤａｔａＦｒｏｍＣｈａｎｎｅｌでチャネルからデータを読み込んでキャッシュに保存する。
【００８４】
my.ReadData = function (psid, readSize) {
ret1 = my.m_CbtChannelList.GetChannelTimeout(psid);
readTimeout = ret1.readTimeout;
while ((time.GetTime() < readTimeout) && (remainDataSize > 0)) {
ret2 = my.m_CbtChannelList.GetNumPktInChannel(psid);
if (ret2.numPkt > 0) {
ret3 = my.m_CbtChannelList.GetDataFromChannel(psid, remainDataSize);
if (ret3.ret >= 0) {
Array.prototype.push.apply(data, ret3.data);
readDataSize += ret3.data.length;
remainDataSize -= ret3.data.length;
} else {
return { ret: my.def.CBT_ERROR_FATAL, data: data };
}
} else if (ret2.numPkt === 0) {
ret4 = my.m_CbtPkt.RecvPkt(psid);
if ((ret4 === my.def.CBT_ERROR_EXIT_DOT4) || (ret4 === my.def.CBT_ERROR_FATAL)) {
return { ret: ret4, data: data };
}
} else {
return { ret: my.def.CBT_ERROR_FATAL, data: data };
}
}
return { ret: readDataSize, data: data };
}
return that;
}
ＧｅｔＤａｔａＦｒｏｍＣｈａｎｎｅｌでは、読み込んだデータのデコードを行って、バッファを取得する。
【００８５】
that.GetDataFromChannel = function (dataSize) {
while (totalReadSize < dataSize) {
if (m_ReadNumPkt === 0) {
break;
}
if (m_ReadPktRemainSize === 0) {
header = m_CbtCodec.DecodeHeader(m_ReadBuffer.splice(0, def.CBT_HEADER_SIZE));
dataReadSize = 0;
if (header.length - def.CBT_HEADER_SIZE > dataSize - totalReadSize) {
dataReadSize = dataSize - totalReadSize;
Array.prototype.push.apply(data, m_ReadBuffer.splice(0, dataReadSize));
m_ReadPktRemainSize = header.length - def.CBT_HEADER_SIZE - dataReadSize;
} else {
dataReadSize = header.length - def.CBT_HEADER_SIZE;
Array.prototype.push.apply(data, m_ReadBuffer.splice(0, dataReadSize));
m_ReadNumPkt -= 1;
}
totalReadSize += dataReadSize;
}
} else {
dataReadSize = 0;
if (m_ReadPktRemainSize > dataSize - totalReadSize) {
dataReadSize = dataSize - totalReadSize;
Array.prototype.push.apply(data, m_ReadBuffer.splice(0, dataReadSize));
} else {
dataReadSize = m_ReadPktRemainSize;
Array.prototype.push.apply(data, m_ReadBuffer.splice(0, dataReadSize));
m_ReadNumPkt -= 1;
}
totalReadSize += dataReadSize;
m_ReadPktRemainSize -= dataReadSize;
}
}
return { ret: def.CBT_SUCCESS, data: data };
}
以上のようにＪａｖａＳｃｒｉｐｔのプログラム４０１１を作成することで、Ｖ４プリンタードライバー１０５２において、ＵＳＢポートで、ＤＯＴ４をサポートすることができる。ＤＯＴ４をサポートすることで、印刷ジョブを送信しながら、構成情報を取得するなどの双方向で同時に通信することができる。
【符号の説明】
【００８６】
１０４２  プリンター、１０５２  プリンタードライバー、３１７  プロパティバッグ、
３１８  プリントマネージャ（スプーラ）、３１９  ポートモニター、
４０１  ＵＳＢＢｉｄｉ Ｅｘｔｅｎｄｅｒ、４０１１  ＪａｖａＳｃｒｉｐｔ、
４０２  ＵＳＢポートモニター、４０３  ＵＳＢＰＲＩＮＴ
【図１】
2016021157.tif 000003
【図２】
2016021157.tif 000004
【図３】
2016021157.tif 000005
【図４】
2016021157.tif 000006
【図５】
2016021157.tif 000007
【図６】
2016021157.tif 000008