dimanche 8 février 2015

Imprimante Zebra USB, ZPL, CUPS et Python

Dans l'article précédent "Imprimante Zebra USB, ZPL et CUPS", nous avions vu comment:
  1. Créer un fichier de démonstration demo.zpl (cfr le projet PythonPcl de MCHobby)
  2. Comment configurer une "Printer Queue" en RAW.
  3. Utilise l'utilitaire "lp" pour envoyer le contenu du fichier zpl vers la Zebra.
Si vous n'avez pas encore lu cette article, je vous conseille vivement d'aller y faire un tour. Car la configuration de la queue en RAW est capital pour le bon fonctionnement.

Maintenant, ce qui serait bien, c'est de pouvoir déclencher cette tâche d'impression à partir de Python :-)

Rappelons que le but est de contrôler l'impression des étiquettes à coller à l'aide du langage de contrôle d'impression ZPL (Zebra Print Language) 

Python et Cups
Normalement, vous trouverez CUPS installer sur la plupart des distributions Linux.
Si cela n'était pas le cas, vous pouvez encoder la commande suivante:

sudo apt-get install python-cups python-pip python-dev

L'installation de python-pip n'est vraiment pas indispensable mais c'est un utilitaire très utile.

Lister les imprimantes Cups
Le petit script suivant permet de lister les imprimantes CUPS disponible sur le système.

import cups
conn = cups.Connection()
printers = conn.getPrinters()

for printer in printers:
     print printer, printers[printer]["device-uri"]

ce qui produit le résultat suivant:
GG-P3010 socket://192.168.1.225:9116
zebra-raw usb://Zebra%20Technologies/ZTC%20LP%202824%20Plus?serial=36J141701319
HP-P3010 hp:/net/HP_LaserJet_P3010_Series?zc=NPI188F5A

Comme vous pouvez le noter, nous retrouvons la file d'impression (printer queue) zebra-raw que nous avons créé dans l'article précédent.

Imprimer le fichier demo.zpl
Cette fois, utilisons le cups sous Python pour envoyer le fichier zpl sur la Zebra.

import cups
conn = cups.Connection()

conn.printFile( "zebra-raw", '/home/dom/PythonPcl/test/test-printer/zebra/demo.zpl', 'zebra demo', {} )

et voila! Pour information, la fonction printFile retourne un entier identifiant la job d'impression.

Imprimante Zebra USB, ZPL et CUPS - Damned CUPS!!!

Voici un petit bout d'histoire autour d'un imprimante Zebra USB branchée sur une machine Linux (Linux Mint Mate 17) et d'un acharné qui voulait envoyer du ZPL sur sa Zebra.

L'imprimante à étiquette Zebra, ZPL et Linux
Chez MCHobby (shop.mchobby.be) nous utilisons Linux Mint pour toutes nos tâches et développements.
Ainsi, pour améliorer et accélérer notre étiquetage à partir des infos de notre PrestaShop, nous avons branché une Zebra LP 2824 USB sur notre ordinateur.

Comme nous développons nos propres petits outils PrestaShop en Python (cfr Lcd Order Track et PrestaConsole), nous pourrions facilement créer nos étiquettes à la demande. 

Revenons à nos moutons...

Ensuite, nous avons essayer d'envoyer des commandes ZPL vers l'imprimante. Les commandes ZPL permettent de contrôler directement l'imprimante et le contenu à imprimer.

Fichier de démo et agaceries CUPS
Comme suggéré dans la documentation technique de Zebra, nous avons créé un petit petit fichier de démonstration demo.zpl

^XA
^FO100,50^ADN,36,20^FDxxx^FS
^XZ
 
Ce fichier fût créé à l'aide de Geany (un excellent éditeur de code), avec LF (LineFeed) comme séparateur de ligne et un encodage de fichier en UTF-8.

Ensuite, j'ai essayé d'envoyer le fichier directement vers l'imprimante à l'aide de:

  cat demo.zpl > /dev/usb/lp0

pour continuellement recevoir le message d'erreur

  /dev/usb/lp0: Permission denied

Cette erreur étant provoquée par le système de gestion des impressions (CUPS).

Je ne pouvais pas adresser directement le périphérique à cause de CUPS... mais je ne pouvais pas retirer CUPS car nous l'utilisons pour d'autres impressions (par exemple notre imprimante réseau HP3015).

Sacré nom de Dieu de CUPS!
Cette simple tâche d'envoyer un fichier brute (RAW comme disent les anglais) sur notre Zebra fût l'une des tâches les plus complexes à réaliser.
CUPS n'était pas vraiment en cause... c'est plutôt mon manque de connaissance de la gestion des imprimantes CUPS qui est en cause.

Ce que nous devons faire, c'est installer la Zebra comme une imprimante RAW  et non comme une Zebra! 

Installer la Zebra en RAW dans CUPS
Pour commencer,
Branchez votre Zebra USB puis ouvrez le gestionnaire d'imprimante.
Sur Linux Mint, il ressemble à ceci...

Cliquez sur le bouton "+" pour ajouter une imprimante.

A ce stade, vous pouvez sélectionner votre Zebra USB et passer à l'étape suivant.
Attention, c'est à partir ICI qu'il faudra faire le bon choix!!
Pour commencer, sélectionnez l'imprimante Generique (même si Zebra et la bonne imprimante existe dans la liste, c'est une imprimante générique qu'il faut installer)

Ensuite, vous sélectionnez le pilote "Raw Queue" (file d'impression Raw/brute):


De la sorte, tous les fichiers envoyés cette queue d'impression Zebra sera redirigé directement vers l'imprimante sans aucun traitement intermédiaire!

Point final très important... il faut nommer la file d'impression de façon intelligible!
Nous avons remplacé le nom proposé par "zebra-raw". Ce sera bien plus pratique par la suite. Nous vous conseillons vivement de simplifier le nom court.
 
Voila, quand c'est fini, vous vous trouvez vers avec une nouvelle imprimante (Printer queue / File d'impression) nommée zebra-raw et n'utilisant pas de pilote d'imprimante interprétant les données que vous voulez envoyer vers la Zebra


Chouette, c'est enfin prêt!
 
Envoyer le fichier demo.zpl vers la Zebra
Comme vous l'avez vu ci-dessus, nous avons crée une queue de traitement "zebra-raw" pour notre Zebra LP 2824 Plus.

A la suite de cela, il a été possible d'envoyer des données brutes (RAW) contenant des commandes ZPL directement sur l'imprimante à l'aide de l'utilitaire lp:

lp -d zebra-raw demo.zpl


Et voila, maintenant, cela fonctionne parfaitement!

Il nous reste plus qu'a attaquer la file d'impression/printer queue en Python et mon bonheur sera complet...

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

jeudi 22 janvier 2015

PostgresSql et Python avec psyco2

Nous avons une belle DB PostgreSql utilisé par notre programme de production (Clipper compilé avec Harbour Project, Mediator utiliser pour l'accès DB).

Voulant afficher les stats d'expéditions journalière le moniteur d'un portable de récup, je me suis demandé s'il était possible d'attaquer notre DB à partir d'un script Python.
Après quelques recherches sur le Net, je suis rapidement venu et revenu vers PsycoPg2.

Bibliothèque PsycoPG2 - Accéder à vos DB PostgresSql en Python.
Il vaut avouer qu'avec cette bibliothèque, cela devient un vrai jeu d'enfant.
Non seulement il est possible d'accéder facilement à une DB PostgreSql mais en plus, les données sont accessible dans le plus pure esprit Pythonien!
Il est possible:
  • D'exécuter des requêtes SQL
  • Lire un seul enregistrement (FetchOne)
  • Lire  tous les enregistrement (FetchAll)
  • Ou d'utiliser un curseur sur le serveur de base de donnée (pour ne pas charger massivement vos milliers de records en une fois).
  • Supporte les transactions (commit après plusieurs insertions).
  • etc.
Après avoir fait quelques tests (Linux Mint 17.1), je peux vous affirmer que c'est de la balle!

Installation
Sur Linux Mint (Ubuntu), l'installation se résume à:

sudo apt-get install python-Psycopg2

Il semblerait qu'il soit parfois compliqué de trouver les paquets pour certaines distribution. Sachez que la documentation officielle (crf liens ci-dessous) reprend quelques notes concernant la compilation de la bibliothèque.

Des tutoriels
Connaître une bonne bibliothèque c'est bien mais avec de la doc c'est mieux.
Voici donc quelques ressources incontournables.

vendredi 16 janvier 2015

L'encoding en Python... une bonne fois pour toute

Excellent article --incontournable-- disponible sur le blog de Sam & Max.
Unicode - un petit coup de gueule 
Tout le monde ne jure que par l'Unicode et je dois bien admettre que cela a facilité les échanges sur le net... mais a bien y penser, je ne suis pas certain que cela soit pour un mieux (globalement).
Cela à ajouté un sacré niveau de complexité sur un des éléments de base de la programmation (les chaines de caractères... et fichiers).
Dire que je me retrouve à se gratter la tête pour essayer de comprendre pourquoi je n'arrive pas à imprimer un "é" sur mon imprimante HP en la contactant par socket pour imprimer du PCL en direct.
A oui mais grâce à UNICODE je dois:
  • Penser à l'encodage de mon OS
  • Pense à l'encodage des code python avec mon éditeur de texte (afr!)
  • Et celui de tes strings (python 2.7 ou 3)... string ou unicode string?
  • etc
Bref, cela devient un usine à gaz pour les méninges et tout cela pour un simple "é".
C'est là que je trouve que nous n'avons pas gagné grand chose avec unicode... les choses simple et les fondations devraient rester "simple".

 

mardi 13 janvier 2015

PC Portables pour Linuxiens - Configuration à la demande et sans vente liée d'OS

C'est en lisant cet article sur loligrub.be que j'ai découvert le fabriquant Clevo en France (Clevo.fr).

Capable de vous concocter PC et PC Portable avec et SANS Système d'exploitation.

Il est enfin possible de se fournir des PC Portables pour nous Linuxiens sans passer par la case "Licence Microsoft"... de quoi investir la différence dans le Matos!

Livrable en Belgique et en France avec clavier Français ou Belge.
Que demander de plus !

Une référence à retenir:
  • Clevo le spécialiste du pc portable sur mesure.

Clévo propose d'ailleurs un chouette offre permettant de réaliser des micro-serveur Linux pour un prix de départ à 299Eur

dimanche 11 janvier 2015

Basic Authentication avec Python - authentification dans l'entête HTML

J'avais justement besoin de m'authentifier pour faire des requête d'API sur Spark Cloud dans le cadre du projet PyCall.

J'ai trouvé une ressource très intéressante:
Un grand merci à Michael Foord.

Quelques ressources complémentaires: