mardi 27 janvier 2015

Python, imprimer en PCL directement sur une HP 3015 en réseau

Introduction
Nous avons plusieurs imprimantes HP dans notre société, dont une HP 3015 en réseau.
Le bonheur avec une HP3015, c'est qu'il est possible d'ouvrir un socket sur le port 9100 et commencer à envoyer un flux texte à imprimer.
Il est également possible d'inclure des séquence d'échappement pour modifier la font, la taille, ect. C'est ce que qu'offre PCL, qui utilise des séquences de "caractères" particulier pour opérer ces changement.

L'avantage de cette approche est d'être totalement indépendante de la plateforme, du langage de programmation et de l'OS. Si vous le vouliez, vous pourriez imprimer depuis un Arduino + Ethernet, Raspberry, etc! Si cela à l'avantage de ne pas surcharger l'OS avec des composants logiciel (pilotes et soft annexés qui font parfois 100Mb+), votre code sera plus long et plus complexe (il devra injecter les séquence d'échappement dans le texte à imprimer, faire gaffe à ne pas déborder de la page, etc).

Pour ceux qui doute du bien fondé de cette approche un peu extrême, je leur propose de prendre un bon vieux soft Clipper en production (20 ans d'ages) et de le recompiler sous Win32.
C'est là que l'on apprécie les bonne vielles casseroles et le gain de temps en R&D.  Une bonne vielle poule au pot... humm que c'est bon!

HP 3015 + PCL + Python
Voulant introduire du graphique dans le documents imprimés (hé oui, PCL le supporte), je me suis dit "pourquoi ne pas faire un prototype en Python".
Simple et facile à mettre en oeuvre, c'est un langage idéal pour faire quelques tests.

Seulement voila, j'ai chié des boulons (Metric 60 les gars!). Pas a cause du l'imprimante IP, pas à cause du socket, par a cause de Python mais à cause de l'encoding!
Entre explosion pour erreur d'encodage, affichage de mes accentués à la va te faire f...., j'en ai vu de toutes les couleurs.

En suivant les recommandations sur l'article "L’encoding en Python, une bonne fois pour toute" de Sam & Max, j'ai fait très attention à mon encoding (UTF8 pour les sources), utilisé des chaines de caractère Unicode (Python 2.7) et réencoder vers le bon code page pour l'envoi des chaines de caractères vers l'imprimante (cp850, il a aussi fallut le trouver celui-là).

Par contre, il m'a fallut un temps considérable pour remarquer que les séquences d'échappement devaient être envoyéez en UTF-8 (sinon "encoding error") et le texte avec accentués en cp850!!!
J'en ai mangé mon clavier (j'en ai encore la barre d'espacement en travers de la gorge!).

Un bout de code qui marche
Voici finalement un petit bout de code simplissime en état de fonctionnement. Les séquences d'échappement modifies seulement la taille du texte. Le but étant d'avoir un résultat cohérent sur l'imprimante sans explosion Python!

#!/usr/bin/env python
# -*- coding: utf8 -*-

import socket
import sys
import encodings

PRINTER_IP = '192.168.1.206'
PRINTER_PORT = 9100
PRINTER_ENCODING = 'cp850'

s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
s.connect( (PRINTER_IP, PRINTER_PORT) )
  
s.sendall( bytes( (u"Chère Mère à la peau tanée.\r\n").encode( PRINTER_ENCODING )) )
s.sendall( bytes( (u""+chr(27)+"(s5H").encode( 'UTF-8' ) ))
s.sendall( bytes( (u"Cet énorme titre\r\n").encode( PRINTER_ENCODING ) ))
s.sendall( bytes( (u""+chr(27)+"(s11H").encode( 'UTF-8' ) ))
s.sendall( bytes( (u"a t'il attiré ton regard\r\n").encode( PRINTER_ENCODING ) ))
s.sendall( bytes( (u""+chr(27)+"(s17H").encode( 'UTF-8' ) ))
s.sendall( bytes( (u"à moins que cela ne soit impossible?").encode( PRINTER_ENCODING ) ))

s.close()


Il y a certainement moyen de rendre ce code plus lisible (le blinder ou faire en sorte qu'il soit "safe" sous Python 3) mais au moins, il fonctionne!

Quel encoding sur mon imprimante
Dans l'exemple précédent, j'utilise 'cp850' comme encoding sur l'imprimante HP.
J'ai eu beaucoup de mal a trouver le bon encodage car malheureusement, Google et le Net ne sont pas très bavard à ce propos!

Encore un fois, c'est un petit script Python qui à testé TOUTES les possibilités en imprimant une phrase contenant un 'é' et le codepage utilisé pour cette impression.
Il ne reste plus qu'a relever les lignes ou le "é" est imprimé correctement.

#!/usr/bin/env python
# -*- coding: utf8 -*-

import socket
import sys
import encodings

PRINTER_IP = '192.168.1.206'
PRINTER_PORT = 9100

s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
s.connect( (PRINTER_IP, PRINTER_PORT) )

for e in encodings.aliases.aliases.values():
  try:
    s.sendall( bytes( (u"Encode ecute é with %s\r\n" % e).encode( e ) ) )
  except:
    s.sendall( bytes( (u"Fails ecute encoding with %s\r\n" % e).encode( "UTF-8" ) ) )
    
s.sendall( bytes( (u"TIME 600\r\n").encode( "UTF-8")) )     # Pas d'accent -> pas de problème
s.sendall( bytes( (u""+chr(27)+"(s5H"+"Big Title").encode( "UTF-8" ) )) # Pas d'accent -> pas de problème

s.close()

Plusieurs options  valides
"é" est un caractère assez répandu dans les alphabets. Il y a donc plusieurs codes pages pouvant convenir.
J'ai finalement choisi 'cp850' car en Belgique, c'est le codepage 850 qui est également utilisé "sous DOS". Les imprimantes vendue dans le même pays sont forcement configuré avec un codepage similaire à celui utilisé par les OS locaux.
Python peut encore une fois vous aider à déterminer le code page de votre système d'exploitation (la console).
C:\temp\python-hp>python
Python 2.7.5 (default, May 15 2013, 22:44:16) [MSC v.1500 64 bit (AMD64)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print( sys.stdout.encoding )
cp850
>>>

Le mot de la fin
Je ne voudrais pas jouer mon stroumpf grognon mais...
"J'aime pas l'encoding!"

Bonne amusement

Aucun commentaire: