Java 實現 POS 印表機無驅列印

子非魚yy發表於2017-09-20

行業需求

我們是一家專業做酒店餐飲軟體的公司,餐飲軟體一個重要的功能就是後廚列印問題,前臺點菜完畢,後廚立刻列印出單子,這樣就減少人工遞單的麻煩,節省時間,提高翻檯率。這種資訊化解決方案對列印技術要求很高,理論上最好 100% 不丟單,也就是每次點菜後廚都會相應出單子,但是實際上行不通,為什麼呢?因為網線、印表機、網路卡等都有可能有問題,別說印表機等硬體因為廚房油煙問題損壞,我們甚至碰到過網線被老鼠咬斷的情況,總之硬體網路故障防不勝防,所以只能退而求其次,就是有問題不可怕,程式能夠判斷是否出了問題,並能給出提示,便於服務員處理,及時補單。

如果我們用安裝 Windows 驅動的方法來實現後廚列印,那麼肯定是不行的,因為我們只能單向向驅動程式拋包,不能從驅動程式獲得任何返回值,沒有辦法瞭解是否列印成功。而且更為嚴重的是,有時候因為後廚印表機過多,Windows 驅動甚至會因為網路堵塞自作主張將包丟棄,沒有任何提示。

這在行業應用中是不行的,會給使用者帶來損失,所以想到了繞過 Windows 驅動,直接寫埠的方法。

無驅列印的可行性

所謂直接寫埠的方法,就是不用安裝印表機驅動,不使用 PrinterJob 獲得印表機的名字的方法進行列印。

眾所周知,之所以安裝印表機驅動,一個重要的原因就是印表機廠商千差萬別,不同的印表機往往都有各自的驅動,很難實現萬能驅動。但是,在 POS 印表機行業卻有一條捷徑,就是現在市面上的 POS 印表機基本上都支援愛普生指令,也就是說,只要將程式和印表機聯通,直接向埠裡面寫愛普生指令就可以控制印表機。

印表機接受到愛普生指令以後,自行進行解析,然後列印出相應的內容。

愛普生指令

日本的 EPSON 公司在目前的 POS 印表機市場,尤其是針式印表機市場佔有很大一部分份額。它所推行的 ESC 列印控制命令 (EPSON StandardCode for Pr5nter) 已經成為了針式印表機控制語言事實上的工業標準,ESC/POS 列印命令集是 ESC 列印控制命令的簡化版本,現在大多數 POS 列印都採用 ESC/POS 指令集。絕大多數印表機都有 EPSON ESC 的軟體命令模擬功能,而且其它列印控制命令的格式和功能也都與 ESC 程式碼集類似。

由於早期的作業系統 DOS 與現在 Windows 的結構不同,在印表機內部軟體和應用軟體之間沒有由硬體廠商提供的列印驅動程式,必須由應用軟體直接通過硬體介面來控制印表機,所以從 ESC 指令出現開始,它就是公開的,否則沒有應用軟體可以使用它,而除了標準的 ESC 指令外,每種型號的印表機其指令又不太一樣,所以在 DOS 軟體中,你可以看到每個應用軟體都只是支援為數不多的幾種常用印表機。

ESC 指令在形式上分為兩種格式,一種是文字方式控制碼,一種是 Escape 轉義序列碼。文字方式控制碼由一位元組字元碼錶示,實現的是與印表機硬體操作有關的指令,Escape 序列碼由轉義字元和引數字元或列印資料組成。

建立列印連線

通過上面的介紹,瞭解了實現無驅列印原來只是一層窗戶紙,具體的方法就是首先建立印表機連線,然後寫入愛普生指令即可。那麼如何建立印表機連線?以網口 POS 印表機舉例。

第一步,首先要給網口印表機賦一個 IP 地址,例如叫做 192.168.0.18 。

第二步,編寫連線程式碼。

1
2
3
4
Socket client=new java.net.Socket();
PrintWriter socketWriter;
client.connect(new InetSocketAddress("192.168.0.18" , 9100),1000); // 建立一個 socket
socketWriter = new PrintWriter(client.getOutputStream());// 建立輸入輸出資料流

看起來跟一般的 socket 連線沒有很大的區別,就是賦一個 IP 地址和一個埠號,並設定一下超時時間即可,只需要說明的是,一般 POS 印表機的埠都是 9100 。

寫入列印內容

連線建立完畢,寫入內容就非常容易,只要使用 write 或者 println 方法寫入即可,其中 write 方法是寫入數字或字元,println 寫入一行字串。

例如:寫入數字 socketWriter.write(0);

寫入一行字串 socketWriter.println( “巧富餐飲軟體後廚單據” );

再入一行字串 socketWriter.println( “桌位 14 桌,人數 3 ” );

再入一行字串 socketWriter.println( “跺腳魚頭 1 份” );

您或許有疑問?內容已經成功寫入,好像我們還沒有用到愛普生指令。是的,如果只是普通的寫入內容,不需要用到愛普生指令,愛普生指令主要幫助實現放大字型,自動走紙,列印條形碼等功能。

放大字型

放大字型需要用到愛普生的 0x1c 指令,使用愛普生指令的方法很簡單,只要向埠寫入指令即可,例如:

1
socketWriter.write(0x1c);

注意 0x1c,是 16 進位制的數字,當然也可以轉換成 10 進位制來寫。需要說明的是,使用愛普生指令放大字型不能隨意放大,因為它不是圖形化列印,而是文字化列印,所以縱向或者橫向只能按照倍數放大,不能向量放大。例如在 POS58 印表機上將“巧富餐飲軟體”幾個字放大列印,可以有如下放大方法。

1
2
3
4
5
6
7
8
9
10
11
12
/* 橫向放大一倍 */
socketWriter.write(0x1c);
socketWriter.write(0x21);
socketWriter.write(4);
/* 縱向放大一倍 */
socketWriter.write(0x1c);
socketWriter.write(0x21);
socketWriter.write(8);
/* 橫向縱向都放大一倍 */
socketWriter.write(0x1c);
socketWriter.write(0x21);
socketWriter.write(12);

一般情況下,我們傾向採用縱向放大一倍的方法,放大後的字型看起來有點像仿宋體,視覺效果還不錯。

相容多種型別印表機

現在知道了使用愛普生指令的方法,所以只要有一本愛普生指令手冊在手裡,就可以用 Java 控制印表機進行無驅列印。但是現在問題是,同樣是愛普生指令,不同的 pos 印表機可能不一樣,就拿放大字型來說,pos58 印表機和 pos80 印表機指令就不盡相同。這時候怎麼辦呢?如何相容多種型別印表機?

比如說,有的印表機並不是使用 0x1c 作為放大指令,而是使用 0x1b 作為放大指令,怎麼辦?容易。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 橫向放大一倍 */
socketWriter.write(0x1c);
socketWriter.write(0x21);
socketWriter.write(4);
socketWriter.write(0x1b);
socketWriter.write(0x21);
socketWriter.write(4);
/* 縱向放大一倍 */
socketWriter.write(0x1c);
socketWriter.write(0x21);
socketWriter.write(8);
socketWriter.write(0x1b);
socketWriter.write(0x21);
socketWriter.write(8);
/* 橫向縱向都放大一倍 */
socketWriter.write(0x1c);
socketWriter.write(0x21);
socketWriter.write(12);
socketWriter.write(0x1b);
socketWriter.write(0x21);
socketWriter.write(12);

看明白了嗎?就是寫兩遍就行,因為如果 0x1b 指令若不存在,印表機自動將其拋棄。

實現自動走紙

POS 印表機因為出紙口有一些深度,列印完畢為了避免撕裂文字內容,一般需要適當走紙才行,當然可以使用愛普生指令來走紙,但是這樣並不穩妥,為什麼呢 ? 因為要考慮 POS 機的相容性,所以一般採用列印空行的方式實現走紙。

1
2
3
for(int i=0;i<10;i++){
    socketWriter.println(" ");// 列印完畢自動走紙
}

顯然,列印空行的方式有更好地相容性。

列印條形碼

條形碼在各個行業中現在有廣泛的應用,所以讓印表機列印條形碼是非常重要的功能,不過你不需要費好多精力去研究條形碼知識,因為愛普生指令中有一個列印條形碼指令,例如我們要列印條形碼“ 091955826335 ”,只要使用如下命令即可。

1
2
3
4
5
6
7
8
9
10
11
socketWriter.write(0x1d);
socketWriter.write(0x68);
socketWriter.write(120);
socketWriter.write(0x1d);
socketWriter.write(0x48);
socketWriter.write(0x01);
socketWriter.write(0x1d);
socketWriter.write(0x6B);
socketWriter.write(0x02);
socketWriter.println "091955826335" );
socketWriter.write(0x00);

完整的程式碼

好了,下面舉一個完整的例子,我們來建立一個叫做 print 的方法,向某個印表機列印一個字元和條形碼,並實現自動走紙,程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private boolean print(String ip, int port, String str,String code,int skip)
throws Exception{
    Socket client=new java.net.Socket();
    PrintWriter socketWriter;
    client.connect(new InetSocketAddress(ip,port),1000); // 建立一個 socket
    socketWriter = new PrintWriter(client.getOutputStream());// 建立輸入輸出資料流
    /* 縱向放大一倍 */
    socketWriter.write(0x1c);
    socketWriter.write(0x21);
    socketWriter.write(8);
    socketWriter.write(0x1b);
    socketWriter.write(0x21);
    socketWriter.write(8);
    socketWriter.println(str);
    // 列印條形碼
    socketWriter.write(0x1d);
    socketWriter.write(0x68);
    socketWriter.write(120);
    socketWriter.write(0x1d);
    socketWriter.write(0x48);
    socketWriter.write(0x01);
    socketWriter.write(0x1d);
    socketWriter.write(0x6B);
    socketWriter.write(0x02);
    socketWriter.println(code);
    socketWriter.write(0x00);
    for(int i=0;i<skip;i++){
        socketWriter.println(" ");// 列印完畢自動走紙
    }
 }

小結

本文雖然只是講述了網口印表機的直接寫埠方式,似乎對並口印表機無效,其實不是這樣,並口印表機只要接一個列印伺服器就可以用了,缺點就是非一體機,然後還要安裝列印伺服器驅動。

這種無驅列印在非常廣泛的範圍內可以得到應用,包括餐飲、超市、醫藥等等其他需要用到 POS 印表機的行業。

相關主題

  • Merlin 的魔力:在 JDK 1.4 中列印,第 1 部分”(developerWorks,2002 年 4 月):介紹 JDK 1.4 為 Java 平臺帶來了另一套列印功能和技術。
  • Java 列印程式設計”(developerWorks,2002 年 10 月):介紹 JDK 1.4 提供的一套完整的“Java 列印服務 API”(Java Print Service API)。
  • 用 Java 設計物件導向的列印程式”(developerWorks,2002 年 12 月):如果將列印內容進行物件化分析,設計出物件導向的列印程式,則會更出色地完成列印要求,並且程式碼很容易重用,事半功倍。
  • 您可以在 developerWorks 的 Java 技術專區 中找到有關 Java 程式設計各方面知識的文章。

相關文章