b-PAC SDKをPython + pywin32(win32com)で操作してみた

前回pythonnetを使って .NET + b-PACを扱いました。

 
ただ、いろいろと辛かったため、今回はpywin32を使ったCOM経由でb-PACを操作してみました。

結論から言うと、イベント処理も含めて、以下のようなC#でできたことはPython + pywin32でも実装できました。
ブラザーのラベルプリンタまわりを操作する b-PAC SDK を使ってみた - メモ的な思考的な

 
今回の目次です。

 

環境

 
virtualenv環境は前回と同じものを使っていますが、再掲しておきます。

D:\sandbox>mkdir bpac_python
D:\sandbox>cd bpac_python
D:\sandbox\bpac_python>virtualenv -p c:\python35-32\python.exe env
D:\sandbox\bpac_python>env\Scripts\activate

# pipだとエラーになるので、easy_installを使う
(env) D:\sandbox\bpac_python>easy_install https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/pywin32-220.win32-py3.5.exe/download

 

b-PAC SDKのCOMオブジェクトを作る

同じくCOM経由となるVB Scriptのサンプルを見ると、ProgIDはbpac.Documentでした。

pywin32ではCOMオブジェクトを作る方法として、

  • win32com.client.Dispatch("ProgID")
  • win32com.client.DispatchEx("ProgID")
  • win32com.client.gencache.EnsureDispatch ("ProgID")

の3つの方法がありました。

 
Dispatch()とDispatchEx()の違いは以下にあり、DispatchEx()を使うのが良さそうでした。

 
また、DispatchEx()とEnsureDispatch()の違いは以下にありました。

 
今回、DispatchEx()とEnsureDispatch()のどちらで書こうかと思いましたが、

doc = win32com.client.gencache.EnsureDispatch ("bpac.Document")
#=> TypeError: This COM object can not automate the makepy process - please run makepy manually for this object

EnsureDispatch()ではエラーとなりました。

よって、今回はDispatchEx()を使って

doc = win32com.client.DispatchEx("bpac.Document")

COMオブジェクトを生成しました。

 

プリンタ情報の取得

C#では、doc.Printer.GetInstalledPrinters()を使ったため、今回もこれを使います。

ただ、GetInstalledPrintersの後ろに()を付けると、

printers = doc.Printer.GetInstalledPrinters();
#=> TypeError: 'tuple' object is not callable

のようなエラーとなりました。

pywin32版では、メソッドに引数がない場合

printers = doc.Printer.GetInstalledPrinters
#=> エラーなし

()無しにすれば良いようです。

 
あとはC#版と同じようにして、

printers = doc.Printer.GetInstalledPrinters
for p in printers:
    support = "Yes" if self.doc.Printer.IsPrinterSupported(p) else "No"
    status = "Online" if self.doc.Printer.IsPrinterOnline(p) else "Offline"
    print("{name} - Support: {support}, Status: {status}"
        .format(name=p, support=support, status=status))

#=> Brother QL-720NW - Support: Yes, Status: Online

プリンタ情報を取得できました。

 

メディア(ラベル)情報の取得

使うものはC#版と同じです。

printers = doc.Printer.GetInstalledPrinters
for p in printers:
    doc.SetPrinter(p, False)

    id = doc.Printer.GetMediaId
    name = doc.Printer.GetMediaName
    msg = "Label - {id} : {name}".format(id=id, name=name) if name else "No Media"
    print(msg)

#=> Label - 259 : 62mm

 

印刷できるかどうか

印刷できるかどうかは、

  • サポートされているプリンタがあること
  • プリンタがオンラインであること
  • メディア(ラベル)がセットされていること

をすべて満たせば良いと考えました。

printers = doc.Printer.GetInstalledPrinters
for p in printers:
    doc.SetPrinter(p, False)
    
    if  doc.Printer.IsPrinterSupported(p) \
    and doc.Printer.IsPrinterOnline(p) \
    and doc.Printer.GetMediaName:
        return p
    
return ""

 

印刷

使うものはC#版と同じです。

# `enabled_printer`は、印刷可能なプリンタを指す
doc.SetPrinter(enabled_printer, False)

# 印刷で使うラベルテンプレート
# Pythonスクリプトと同じディレクトリに置く前提
dir = os.path.abspath(os.path.dirname(__file__))
lbx_path = os.path.join(dir, "test.lbx")

hasOpened = doc.Open(lbx_path)
if not hasOpened:
    print("指定されたラベルを開けませんでした")
    return

# デフォルトは各ラベルでカットなので、
# 最後のラベルでカットするように変更
doc.StartPrint("", 0x04000000)

       
for i in range(0, 3):
    # テンプレートのテキストオブジェクトへの値設定
    doc.GetObject("Content").Text = "No.{}".format(i)
    # テンプレートのバーコードオブジェクトへの値設定
    doc.SetBarcodeData(self.doc.GetBarcodeIndex("Barcode"), i);
    
    doc.PrintOut(1, 0x04000000);

doc.EndPrint
doc.Close

 

b-PACの定数や列挙型について

上記の印刷コード例では、C#ではPrintOptionConstants.bpoCutAtEndを使っていたところが、pywin32ではdoc.StartPrint("", 0x04000000)のような16進数表記を使っています。

pywin32でCOMの定数や列挙型を扱う時はwin32com.client.constantsを使います。

 
ただ、b-PACで試してみたところ、

print(win32com.client.constants.PrintOptionConstants.bpoCutAtEnd)
#=> AttributeError: PrintOptionConstants

エラーとなりました。

 
b-PACのヘルプで列挙型を見ると、列挙型の他に16進数の表記もありました。そのため、16進数表記で使ったところ正常に動作しました。

 

PrintedEventイベントについて

b-PAC SDKの更新情報を見たところ、

2014/10/15 スクリプト言語(VBScriptJScript等)でPrintEventメソッドに対応しました。

更新情報一覧|b-PACサポート|開発者ツール|ブラザー

とあり、また、b-PAC 3.1 APIのヘルプには

delegate void bpac::PrintedEvent (LONG status, VARIANT value) 印刷時、イベントハンドラー イベントハンドリングはVisualBasic6.0, VisualC++6.0, VB Script, JScriptには対応しておりません。 これらの言語ではコールバック設定にてイベントを処理してください。

と書かれていました。

他に、pywin32でイベントの存在を確認したところ、

print(win32com.client.getevents("bpac.Document"))
# => <class 'win32com.gen_py.90359D74-B7D9-467F-B938-3883F4CAB582x0x1x3.IPrintEvents'>

となるなど、b-PAC 3.1 + pywin32版でもイベントを扱えそうでした。

 
pywin32でのイベントハンドリングについて調べたところ、以下の記事を見つけました。とても参考になりました。
2007-09-13 [skype][python] イベントハンドリング - やっとむでぽん

今回は、そこで紹介されていた以下の2パターンを試してみます。

  • DispatchWithEvents
  • getevents

 

DispatchWithEventでは実装できず

以下のようなソースを書いてみたところ、

class PrintEvents(object):
    def OnPrinted(self, status, value):
        print("status:{s} / value{v}".format(s=status, v=value))
...
doc = win32com.client.DispatchWithEvents("bpac.Document", PrintEvents)
# => TypeError: This COM object can not automate the makepy process - please run makepy manually for this object

というエラーになりました。

エラーメッセージにあるmakepy.pyファイルは、D:\Sandbox\bpac_python\env\Lib\site-packages\pywin32-220-py3.5-win32.egg\win32com\client\ディレクトリの中にありました。

そこでmakepy.pyファイルを

(env) D:\Sandbox\bpac_python\env\Lib\site-packages\pywin32-220-py3.5-win32.egg\win32com\client>python makepy.py

のように実行すると、Select Libraryウィンドウが表示され、Brother b-PAC 3.1 Type Library [1.3]などが含まれていました。

 
次に、以下を参考に-iオプションを使い、詳細な情報を取得します。
Generated Python COM Support

(env) D:\Sandbox\bpac_python\env\Lib\site-packages\pywin32-220-py3.5-win32.egg\win32com\client>python makepy.py -i "Brother b-PAC 3.1 Type Library"
Brother b-PAC 3.1 Type Library
 {90359D74-B7D9-467F-B938-3883F4CAB582}, lcid=0, major=1, minor=3
 >>> # Use these commands in Python code to auto generate .py support
 >>> from win32com.client import gencache
 >>> gencache.EnsureModule('{90359D74-B7D9-467F-B938-3883F4CAB582}', 0, 1, 3)

と表示されました。

画面の表示通りに変更を加えてみましたが、

from win32com.client import gencache
gencache.EnsureModule('{90359D74-B7D9-467F-B938-3883F4CAB582}', 0, 1, 1)
doc = win32com.client.DispatchWithEvents("bpac.Document", PrintEvents)
# => TypeError: This COM object can not automate the makepy process - please run makepy manually for this object

エラー内容は変わりませんでした。

そのため、DispatchWithEvent()による実装は諦めました。

 

geteventsでは実装できた

geteventsの場合は、

class PrintEvents(win32com.client.getevents('bpac.Document')):
    def OnPrinted(self, status, value):
    # def Printed(self, status, value): => 動作しない
        print("status:{s} / value{v}".format(s=status, v=value))

のようなイベントハンドリングのためのクラスを用意します。

なお、上記参考ページにもありましたが、イベントハンドラ用のメソッドOnを先頭に付けて作成します。b-PACの場合も、On無しだとイベントが動作しませんでした。

最終的なメソッド名は、b-PACのヘルプにある「印刷完了イベントハンドリング&コールバック」のVBScript/JScriptサンプルの関数名がPrintedだったことから、OnとPrintedを組み合わせたOnPrintedとしました。

 
あとは、以下を参考にイベントを待つようにtime.sleep()を使って
Consuming COM events in Python - Stack Overflow

...
# イベントハンドラの追加
handler = PrintEvents(doc)

self.doc.StartPrint("", 0x04000000)
...
self.doc.Close

# Printedイベントが動作するのを確認するため、少々待つ
import time
time.sleep(5)

としたところ、印刷完了後コマンドプロンプト上にstatus:0 / value128が表示されました。

 

ソースコード

GitHubに上げました。
thinkAmi-sandbox/bpac_python-sample

pywin32_ver.pyが、pywin32を使った場合のソースコードとなります。

 

その他参考

pywin32のドキュメントについて

探してみましたが、公式っぽいのは見当たりませんでした。

いくつかドキュメントへのリンクが示されていたので、それらを貼っておきます。

HowToっぽいのもありました。
Tim Golden's Python Stuff: Win32 How Do I...?

 

b-PACの説明にある、CCIという単語の意味について

手元のb-PAC環境を3.0から3.1に上げるにあたり、b-PACのFAQを読んでいたところ、CCIという単語が出てきました。

[Q] クライアントのb-PACをバージョンアップするにはどうしたらいいですか?

[A] 弊社が提供するCCIでクライアントのPCに上書きインストールするか、b-PAC SDK同梱のマージモジュールを組み込んだインストーラーを作成してクライアントのPCにインストールしてください。

b-PACの再配布|FAQ(よくあるご質問)|b-PACサポート|開発者ツール|ブラザー

 
特に説明もなくCCIと出てきたので、単語の意味について調べてみたところ、英語版に情報がありました。

[Q] How can I upgrade b-PAC on a client PC?

[A] Install the provided Client Ccmponent Installer (CCI) on the client PC. Or create an installer using the merge module included in the b-PAC SDK and install it on the client PC.

Operation | Labelling SDK (b-PAC) FAQ | FAQs | Developer Center | Brother

再配布用のクライアントインストーラーパッケージのようでした。