見出し画像

プリンターと会話したかった話

昔話ですみません

オフコンって知ってますか?

いや、私も使ったことはないんですけどね。
ずいぶん昔の話ですけど、印刷系のツールを開発するにあたって、「何か困ってることはありますか?」って聞いてまわったのです。

その頃はまだWindows95が出たばかりで、PCを業務に使う会社は多くなく、オフコン(オフィスコンピュータ)というものを使っていたらしいです。
調べたら、2018年くらいまでは売っていたようですが。
オフコンには専用のプリンターがつながっていて、専用品だけあってプリンターの状態(紙が無いとか、インクリボンが無いとか、紙が詰まったとか)がわかるので、とても便利だと。
PCだとプリンターの状態がわからないのでなんとかしてくれと。

ネットワーク黎明期

当時、ネットワーク接続はまだ一般的ではなく、インターネットも大学とか大企業でのみ使われていました。
プリンターをネットワークに接続するには別売りのネットワークカードか、プリントサーバーが必要でした。
しばらくして、一部メーカーのプリンターでは、ウェブブラウザでプリンターにアクセスすると、プリンターの状態がわかるような画面が用意され始めました。
この画面は人間が見るためのもので、PCで読み込んで解析/判定できるようには作られていませんが、某印刷管理サーバー(C版)では、HTTP通信を力技で解析/判定していました。

SNMP登場

ここでSNMP(Simple Network Management Protocol)の登場です。
SNMP自体はもう少し前からあったのですが、デバイス関連の仕様が整理されたのが1993年くらいからで、1995年から1997年頃だと思いますが、プリンターやプリントサーバーに実装され始めました。
HPのプリンターはSNMPが標準化される前から独自プロトコルでやりとりしていましたね。

SNMP通信

ライブラリなんてないよ?

いまならJava用のSNMPライブラリなんて普通にあると思いますが、当時はネットワーク機器を作っている会社以外は誰も知らないような状況だったので、汎用的なライブラリなんてありません。
というか、Java 1.1 の頃なので、Javaで書こうとしてもTCP/UDP通信は JNI(C言語)で書く必要がありました。
サーバープログラムでは、まだJavaは使用しづらい状況で、サーバーサイドではC言語を主に使用していましたが、今のようにOSSがすぐに見つかるわけもなく、なんでも自力で実装する必要がありました。

SNMP

SNMPは、現在まで v1 / v2c / v3 の3つのバージョンがあり、ここでは v1 をベースにお話しします。
SNMPv1のコマンドは、Get / GetNext / Set / Trap の4種類があります。
Get は指定したオブジェクトの情報を取得します。
GetNext は指定したオブジェクトの次のオブジェクトを取得します。
Set は、指定したオブジェクトに値を設定します。
Trap は、エージェント(管理対象機器)からの通知です。

MIB

管理情報は、MIB(Management Information Base)として定義されています。
これは、ASN.1(Abstract Syntax Notation One)という記法で書かれています。
データの構造を定義していますが、これが結構やっかいなのです。文字で形式を定義しているので、ドキュメントとしては理解しやすいのですが、実際にどのような構造になっているのかは、なかなかわかりにくいです。
私の理解力の問題かもしれませんが。
通信時はBER形式のバイナリデータになり、解釈するのは結構面倒でした。
フィールドの長さを示す項目が可変長だったり、値そのものはビッグエンディアンだったり。Javaともインテル系CPUとも相性がよくない気がしました。
プリンターメーカーもまだ実装に慣れていなかったためか、プリンターから情報を取得してみたらビット列の解釈が間違っていて、LSBとMSBが逆になっているなんてこともありました。

Host Resource-MIB

ホストリソースMIBは、rfc1514で定義され、rfc2790で改訂されています。
「ホストリソースMIBは、ホストコンピュータの管理に役立つ一様なオブジェクトのセットを定義します。ホストリソースMIBは、多くのコンピュータシステムアーキテクチャにわたって共通のオブジェクトを定義します。」
というわけで、すべてがプリンターのためのオブジェクトではないのですが、ここに

HrPrinterEntry ::= SEQUENCE {
    hrPrinterStatus             INTEGER,
    hrPrinterDetectedErrorState OCTET STRING
}

hrPrinterStatus OBJECT-TYPE
       SYNTAX     INTEGER {
                      other(1),
                      unknown(2),
                      idle(3),
                      printing(4),
                      warmup(5)
                  }
       MAX-ACCESS read-only
       STATUS     current
       DESCRIPTION
           "The current status of this printer device."
       ::= { hrPrinterEntry 1 }

hrPrinterDetectedErrorState OBJECT-TYPE
       SYNTAX     OCTET STRING
       MAX-ACCESS read-only
       STATUS     current
       DESCRIPTION
           "This object represents any error conditions detected
           by the printer.  The error conditions are encoded as
           bits in an octet string, with the following
           definitions:

                Condition         Bit #

                lowPaper              0
                noPaper               1
                lowToner              2
                noToner               3
                doorOpen              4
                jammed                5
                offline               6
                serviceRequested      7
                inputTrayMissing      8
                outputTrayMissing     9
                markerSupplyMissing  10
                outputNearFull       11
                outputFull           12
                inputTrayEmpty       13
                overduePreventMaint  14

           Bits are numbered starting with the most significant
           bit of the first byte being bit 0, the least
           significant bit of the first byte being bit 7, the
           most significant bit of the second byte being bit 8,
           and so on.  A one bit encodes that the condition was
           detected, while a zero bit encodes that the condition
           was not detected.

           This object is useful for alerting an operator to
           specific warning or error conditions that may occur,
           especially those requiring human intervention."
       ::= { hrPrinterEntry 2 }

hrDeviceStatus OBJECT-TYPE
       SYNTAX     INTEGER {
                      unknown(1),
                      running(2),
                      warning(3),
                      testing(4),
                      down(5)
                  }
       MAX-ACCESS read-only
       STATUS     current
       DESCRIPTION
           "The current operational state of the device described
           by this row of the table.  A value unknown(1)
           indicates that the current state of the device is
           unknown.  running(2) indicates that the device is up
           and running and that no unusual error conditions are
           known.  The warning(3) state indicates that agent has
           been informed of an unusual error condition by the
           operational software (e.g., a disk device driver) but
           that the device is still 'operational'.  An example
           would be a high number of soft errors on a disk.  A
           value of testing(4), indicates that the device is not
           available for use because it is in the testing state.
           The state of down(5) is used only when the agent has
           been informed that the device is not available for any
           use."
       ::= { hrDeviceEntry 5 }

というようにプリンター関連のオブジェクトが定義されています。
hrPrinterStatus でプリンター自体の状態(アイドル、ウォームアップ中)などがわかり、hrPrinterDetectedErrorState で発生しているエラーや警告がわかり、hrDeviceStatus でプリンターが使用可能かどうかがわかるということです。
これらのオブジェクトを定期的に取得することで、プリンターの状態を画面に表示するということが行えるようになりました。

実際に取得してみよう

手元のプリンターから前述の3つのオブジェクトを取得してみました。
取得に使用したのは、mac の snmpget コマンドです。

$ snmpget -v1 -c public 192.168.0.21 1.3.6.1.2.1.25.3.5.1.1.1
HOST-RESOURCES-MIB::hrPrinterStatus.1 = INTEGER: idle(3)

$ snmpget -v1 -c public 192.168.0.21 1.3.6.1.2.1.25.3.5.1.2.1
HOST-RESOURCES-MIB::hrPrinterDetectedErrorState.1 = Hex-STRING: 00

$ snmpget -v1 -c public 192.168.0.21 1.3.6.1.2.1.25.3.5.1.5.1
Error in packet
Reason: (noSuchName) There is no such variable name in this MIB.
Failed object: HOST-RESOURCES-MIB::hrPrinterEntry.5.1

hrDeviceStatus は解釈できなかったようです。
オブジェクトIDを間違えたかな。
ちなみに、snmpwalk コマンドを使用すれば、横断的にオブジェクトを取得できますよ。
こちらでは HOST-RESOURCES-MIB::hrDeviceStatus.1 も取れてますね。

$ snmpwalk -v1 -c public 192.168.0.21 1.3.6.1
SNMPv2-MIB::sysDescr.0 = STRING: Canon TS3300 series /P
SNMPv2-MIB::sysObjectID.0 = OID: SNMPv2-SMI::enterprises.1602.4.7
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (71300) 0:11:53.00
SNMPv2-MIB::sysContact.0 = STRING:
SNMPv2-MIB::sysName.0 = STRING: TS3300 series
SNMPv2-MIB::sysLocation.0 = STRING:
IF-MIB::ifPhysAddress.1 = STRING: 34:9f:7b:36:76:70
IP-MIB::ip.21.1.7.0.0.0.0 = IpAddress: 192.168.0.1
HOST-RESOURCES-MIB::hrMemorySize.0 = INTEGER: 0 KBytes
HOST-RESOURCES-MIB::hrDeviceType.1 = OID: HOST-RESOURCES-TYPES::hrDevicePrinter
HOST-RESOURCES-MIB::hrDeviceDescr.1 = STRING: Canon TS3300 series 1.000
HOST-RESOURCES-MIB::hrDeviceStatus.1 = INTEGER: running(2)
HOST-RESOURCES-MIB::hrPrinterStatus.1 = INTEGER: idle(3)
HOST-RESOURCES-MIB::hrPrinterDetectedErrorState.1 = Hex-STRING: 00
SNMPv2-SMI::mib-2.43.5.1.1.1.1 = Counter32: 0
SNMPv2-SMI::mib-2.43.5.1.1.2.1 = INTEGER: 1
SNMPv2-SMI::mib-2.43.5.1.1.3.1 = INTEGER: 3
以下略、

ベンダー固有の情報

SNMPにはベンダーコードがあり、ベンダー固有のオブジェクトを定義することができます。
snmpwalk で、ベンダー固有の情報を見ることができますし、MIBを定義したファイルがあれば内容を理解して使用することもできます。
某印刷管理サーバーでは、ベンダーの協力を得て、機種固有のプリンター情報を取得したりしています。

最後に

技術的なことはほとんど触れていませんが、なんとなく感じは伝わったでしょうか?
ネットワークにつながる機器であればSNMPを利用して各種情報を取得することができるようになっています。
プリンター以外でも使い道はあると思うので、何かあったら思い出して使ってみてください。


この記事が気に入ったらサポートをしてみませんか?