vendredi 31 juillet 2015

NPyScreen: interface en mode texte (console) pour Python

Dans l'article "urwid: interface en mode texte (console) pour Python" je regrettais surtout la lourdeur du Framework et la difficulté d'utilisation de celui-ci.

Je me penche donc sur une autre alternative "NPyScreen" en espérant que celle-ci ne soit pas tout aussi inapprochable qu'Urwid.

Basé sur nCurse, nPyScreen permet de réaliser des interfaces au look rudimentaires mais efficace.
NB: Depuis, je sais qu'il y a des thèmes et j'ai vraiment apprécié le temps (et investissement) très court pour la prise en main. I <3 nPyScreen.


Il faut avouer que le code correspondant est assez explicite.
Pour ma part, je préfère un code un peu plus long mais plus explicite.
La lisibilité (et le sens des instruction) est limpide, c'est déjà une bonne choses.

import npyscreen
class TestApp(npyscreen.NPSApp):
    def main(self):
        # These lines create the form and populate it with widgets.
        # A fairly complex screen in only 8 or so lines of code - a line for each control.
        F  = npyscreen.Form(name = "Welcome to Npyscreen",)
        t  = F.add(npyscreen.TitleText, name = "Text:",)
        fn = F.add(npyscreen.TitleFilename, name = "Filename:")
        fn2 = F.add(npyscreen.TitleFilenameCombo, name="Filename2:")
        dt = F.add(npyscreen.TitleDateCombo, name = "Date:")
        s  = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider")
        ml = F.add(npyscreen.MultiLineEdit,
               value = """try typing here!\nMutiline text, press ^R to reformat.\n""",
               max_height=5, rely=9)
        ms = F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One",
                values = ["Option1","Option2","Option3"], scroll_exit=True)
        ms2= F.add(npyscreen.TitleMultiSelect, max_height =-2, value = [1,], name="Pick Several",
                values = ["Option1","Option2","Option3"], scroll_exit=True)

        # This lets the user interact with the Form.
        F.edit()

        print(ms.get_selected_objects())

if __name__ == "__main__":
    App = TestApp()
    App.run()

Voyez également la vidéo suivante présentant les différentes fonctionnalités de NPyScreen.

_curses.error: must call initscr() first

L'exemple le plus simple de npyscreen est celui-ci
MyForm = Form()

usrn_box = MyForm.add_widget(TitleText, name="Your name:")
internet = MyForm.add_widget(TitleText, name="Your favourite internet page:")

MyForm.edit()

et débouche inévitablement sur l'erreur:

_curses.error: must call initscr() first

La raison est simple, npyscreen se base sur la bibliothèque curse. Comme l'exemple n'initialise pas un objet "PyScreen Application" qui lui initialise Curse, nous devrons donc faire l'initialisation de Curse nous même.

Dans ce cas bien précis, il faudra faire l'initialisation de Curse nous même (voyez l'autre version de l'exemple.

Autre chose: une fois que curse est initialisé, il prend le contrôle totale de l'écran et de tout ce qui s'affiche.
Par conséquent, hors de question de taper le code de l'exemple dans un Python Interactif... dès que vous aurez initialisé Curse, vous ne verrez plus rien s'afficher à l'écran.
Il faut donc taper votre code dans un script Python puis exécuter le script Python.

#!/usr/bin/python
# encoding: utf8

import curses
from npyscreen import *

# Initialisation de curse
#   Non nécessaire si vous employez l'objet "PyScreen Application" pour
#   gérer l'application
curses.initscr()

# Exemple nPyScreen
MyForm = Form()

usrn_box = MyForm.add_widget( TitleText, name="Votre Nom" )
internet = MyForm.add_widget( TitleText, name="Your favorit internet page:" )

MyForm.edit()

# Remettre le terminal dans son état d'origine
curses.nocbreak()
curses.echo()
curses.curs_set( 1 )
curses.endwin()

# Imprimer les données collectées
print( 'Votre nom est %s' % usrn_box.value )
print( 'L adresse URI est %s' % internet.value )
 

Voici donc l'exemple complet et fonctionnel que nous avons placé dans le fichier 00_form.py que vous pouvez exécuter.

NPyScreen vs Urwid
Pour moi, il n'y a pas photo. 2H pour mon premier écran NPyScreen (avec le message d'erreur) contre 8H pour un résultat non satisfaisant (tellement il a été compliqué d'y arriver).

Pour mes petits outils de gestion journalier ce sera NPyScreen.

Ressources

mercredi 29 juillet 2015

ZPL - Impression d'image sur l'étiquette avec une Zebra LP2824 Plus

Erratum
L'article mentionne à plusieurs reprises que l'instruction Field Origin ^FO ne fonctionne pas avec l'affichage des images.
Ne tenez pas compte de cette remarque erronée. Il ne faut pas confondre la lettre O avec le chiffre zéro. En fonction de la font utilisée, cette distinction est parfois impossible et l'erreur de frappe si vite arrivée :-/ ! Voila pourquoi j'aime fonts avec le zéro barré ;-)

Introduction

Cette article est relativement technique mais passe en revue un certain nombre d'informations nécessaires permettant d'imprimer des images sur une étiquette Zebra.
J'y suis arrivé, vous pourrez donc le faire aussi.

Pour ma part, je dois réaliser cela à partir d'un programme en Clipper (Harbour Project) et n'ai donc pas d'autres choix que de me taper les spécifications.

Vous pouvez également vous référer à ces quelques articles d'introduction:
L'exemple selon Zebra à revoir!
Pour imprimer une image, il faut utiliser la commande ~DG pour définir l'image et stocker l'image en RAM.
Ensuite, l'image est imprimée à l'aide de la commande ^XG

L'exemple Zebra se base sur un petit damier codé assez simplement.
Cet exemple à deux défauts:
  • L'image est petite. Si vous utilisez une étiquette de 1", il est fort probable que vous ne voyez jamais rien. L'image est simplement imprimée hors de l'étiquette!
  • L'image est toujours imprimée depuis la coordonnée 0,0.
    Lu ailleurs dans la doc technique de 500 pages, la coordonnée par défaut de l'image est 0,0.
    Malgré les commandes de positionnement ^FO (field origin), l'origine d'impression sur ma Zebra est toujours 0,0!!!
    Il y a même l'exemple ^XG prétendant imprimer une image a différentes positions sur l'étiquette Zebra LP2824... cet exemple ne fonctionne simplement pas. L'origine est et reste 0,0 et rien d'autre. Il faudra donc prévoir son image en fonction de cette caractéristique.
Pour contourner les défauts de cet exemple, j'ai utilisé un "magnification factor" (agrandissement) de 3 sur une étiquette Zebra de 2" de large.

~DGR:SAMPLE.GRF,00080,010,
FFFFFFFFFFFFFFFFFFFF
8000FFFF0000FFFF0001
8000FFFF0000FFFF0001
8000FFFF0000FFFF0001
FFFF0000FFFF0000FFFF
FFFF0000FFFF0000FFFF
FFFF0000FFFF0000FFFF
FFFFFFFFFFFFFFFFFFFF
^XA
^F020,20^XGR:SAMPLE.GRF,3,3^FS
^XZ

Vous pouvez clairement voir que sur notre Zebra LP2824 la partie supérieure gauche du damier est imprimé "hors" de l'étiquette (depuis le point d'origine 0,0).

Il est donc très important de tenir compte de cette caractéristique lors de la création de notre image.

Encodage de l'image et notes de calculs

Encodage Hexadécimal
Ce point est probablement l'un des plus simple (cfr la doc).
Chaque point/pixel est représenté par un bit dans un octet/byte (il y a 8 bits dans un octet).

Et chaque octet est codé en Hexadécimal pour le définition de l'image.
 
FF en Hexadécimal --> 255 en décimal --> 11111111 en binaire --> donc 8 points nous d'affilés
80 en Hexadécimal --> 128 en décimal --> 10000000 en binaire --> un point noir + 7 point blancs.

Une ligne contenant FF FF FF représente donc une ligne d'une image avec 24 points noir. 

Nous avons donc un ligne ZPL par ligne dans l'image ET chaque ligne doit contenir un multiple de 8 points.

Si l'image fait 203 pixels (lignes) de haut, le bloc ZPL de l'image fera 203 lignes.
Si l'image fait 120 pixels de large, il faudra 120 / 8bits = 15 octets pour coder chaque ligne.

Comme la représentation d'un octet se fait avec 2 caractères Hexadécimal (FF = 255 en décimal), chaque ligne du bloc ZPL définissant l'image contiendra 30 caractères alphanumériques.

Question de point de vue
Là aussi je pense que la doc Zebra est un peu confuse.
Je ne sais pas pour vous mais moi je ne pars pas d'une image en mm pour ensuite savoir comment la plaquer sur une étiquette.
Je pars plutôt de l'espace disponible sur mon étiquette... puis détermine la taille de l'image à y placer... et je forge l'image en PIXELS en fonction de ce que j'ai besoin!
Pas besoin de se prendre la tête avec le nombre de dpi de l'imprimante et les mm disponibles.

La définition du bloc ~DG ci-dessous reprend donc les mensuration et caclul à partir de la taille en pixels.

Définition de l'image

Le bloc ~DG est défini comme suit:

~DGd:o.x,t,w,data

  • d = l'endroit où l'on stocke l'image... c'est en R (la ram)
  • o.x = Nom de l'image et son extension. Nom court, sans fioriture, moins de 8 caractères. optez pour un truc come LOGO.GRF
  • t = Taille de l'image en byte/octet. hauteur_en_pixels * largeur_en_pixels / 8.
    N'oubliez pas de prendre une image dont la largeur est un multiple de 8.
    Pour une image 120 x 203 --> 120 x 203 / 8 = 3045 octets
  • w = Nombre de bytes/octets par ligne. Dépend uniquement de la largeur de l'image.
    Largeur_en_Pixel / 8 --> 120 / 8 = 15 octets sur chaque ligne.
  • data = donnée de l'image au format hexadécimal (comme décrit ci-dessus).
Voici donc l'entête (et une partie du corps) ZPL de mon étiquette contenant une image (120 x 203 pixels) en ZPL.
Cette étiquette étant destiné à une imprimante Zebra LP2824 Plus avec une étiquette Z-Select 2000D de 50.8 x 25.4mm  (2" x 1").

~DGR:GGLOGO.GRF,03045,015,
800000000000000000000000000000
000000000000000000000000000000
...
...
000000000000000000000000000000
000000000000000000000000000000
^XA
^F00,0^XGR:GGLOGO.GRF,1,1^FS
^FX--FAIRE ETIQUETTE LARGE--^FS
^FX--Print Qty--^FS
^PQ@@QTY@@,0,0,Y
^FX--FR LABEL--^FS
^FO20,25^AR,40,30^FB405,1,0,C,0^FD@@LIBEEN@@^FS
^FX--PRODUCT CODE--^FS
^FO120,75^AT,40,30^FB300,1,0,C,0^FD@@ARTI@@^FS
^FX--BOX LOGO--^FS
^FO20,65^GB100,130,2^FS
^FX--EAN - CODE128--^FS
^FO230,130^BY1^AD,18,10
^BCN,40,Y,N,
^FD@@EAN@@^FS
^XZ

Au final, nous obtenons ceci.

Cas pratique
Voici comment faire pour créer l'image à afficher sur une étiquette.
Pour commencer, j'ai essayer de produire le code ZPL de l'étiquette avec un cadre à l'emplacement où je voulais placer l'image.
Cela permet:
  1. De connaître les coordonnées utiles.
  2. d'avoir la dimension de l'image à produire.
Je me suis donc efforcé d'écrire un tel fichier ZPL (voyez nos autres articles repris dans l'intro pour l'impression).
Au final, j'ai produit l'étiquette suivante.

^XA
^FX--FAIRE ETIQUETTE LARGE--^FS
^FX--Print Qty--^FS
^PQ1,0,0,Y
^FX--FR LABEL--^FS
^FO20,25^AR,40,30^FB405,1,0,C,0^FD@@LIBEEN@@^FS
^FX--PRODUCT CODE--^FS
^FO120,75^AT,40,30^FB300,1,0,C,0^FD@@ARTI@@^FS
^FX--BOX LOGO--^FS
^FO20,65^GB100,130,2^FS
^FX--EAN - CODE128--^FS
^FO230,130^BY1^AD,18,10
^BCN,40,Y,N,
^FD@@EAN@@^FS
^XZ

Les valeurs avec @@code@@ sont destinés à être remplacés au vol par un programme.
Ce qui nous intéresse, c'est la BOX pour la future position du logo.

^FO20,65^GB100,130,2^FS

Cette boite se trouve à l'origine 20,65 (gauche,top) avec une largeur de 100 et hauteur de 130 points.
Mais souvenez vous, une image est toujours affichée depuis l'origine 0,0.
Pour remplir le cadre, il faudra une image de:
  • 20 + 100 = 120 point de large.
    On veillera à ne pas placer de graphique sur une bande de 20 pixels sur la gauche de l'image).
    La largeur est aussi un multiple de 8 (pour le codage Hexadécimal) --> 15 octets par ligne.
  • 65 + 103 = 195 Pixels de haut.
    On veillera à ne pas placer de graphique sur la bande supérieure de 65 pixels (afin de ne dessiner que dans le cadre).
Nous avons donc la dimension idéale de notre image 120 x 195 pixels (avec une partie de celle-ci sur laquelle il ne faut pas dessiner).

Créer l'image
J'ai utiliser la méthode décrite dans le précédent article Transformer une image en BMP 2 couleurs pour une impression PCL pour créer l'image ci-dessous.

 Bon, j'ai fait un petit excès de zèle sur la hauteur de l'image, elle fait 203 pixels de haut (au lieu des 195 prévu).
La taille en octet de mon image sera donc de Hauteur_en_pixel x Largeur_en_Pixel / 8 = 3045 octets!
Le nombre d'octet par ligne pour l'image est de largeur_en_pixel / 8 = 15 octets!

Voici l'image pour ceux qui voudraient répéter l'opération (l'image doit être en 2 couleurs, 120x203 pixels)


Blogger n'aime pas les BMP 2 couleurs
du coup c'est un PNG qu'il vous faudra certainement
la retransformé en BMP 2 couleurs

Comme indiqué à la fin de l'article Transformer une image en BMP 2 couleurs pour une impression PCL , j'ai sauvé ce fichier en format SUNRAS 1 bit (im1).

Cela permet d'obtenir un fichier brute qui peut être rechargé en Python (sous format binaire).

Il faut sauter les 32 premiers octets de l'entête et chaque ligne est codée sur 16 octets de large au lieu de 15 octets (il faut donc se débarrasser du dernier octet de chaque ligne qui est toujours à 00).

Voici un petit script python (iPython) sans prétention qui permet de faire les manipulations nécessaires à la création du bloc de donnée de l'image en HexaDécimal.
file = open( 'GGLOGO.im1', 'rb' ) # Read BINARY file
fileContent = file.read()
file.close()
withoutHeader = fileContent[32:]

# Get the first byte as Hex
# "{:02x}".format( withoutHeader[0] )

# Image Size in Pixels (or dots). Look for Image property in Gimp
#    Width must be a multiple of 8
image_width = 120 
image_height = 203

byte_per_line = int( image_width / 8 )
byte_to_read  = int( (image_width * image_height)/8 )

# Test the size of array of data with image size
len( withoutHeader ) # 3248 (inclus le 16ieme octet 00 à virer sur chaque ligne!)
byte_to_read         # 3045 (n'inclus pas le CR dans le calcul)

# Result = List with one line per image line.
#          Each image line is a list of 15 byte under HexaDecimal
#            representation (we throw the 16th byte)
#
# result = [ [ '00', '00', '00', '00', '00', '00', '00' , '00' , '00' , '00' , '00' , '00', '00', '00' ,'00' ],
#            [ '00', '00', '00', '00', '00', '00', '00' , '00' , '00' , '00' , '00' , '00', '00', '00' ,'00' ],
#            ...
#          ] 
iCount = 0
result = []
sublist = []

for c in withoutHeader:
    iCount += 1
    # Garder que les 15 premiers bytes 
    if iCount < 16:
        sublist.append( "{:02x}".format( c ) )
    # Tous les 16 bytes c est une nouvelle ligne
    if iCount == 16:
        # skip the 16th byte
        # Start a new sublist
        result.append( sublist )
        sublist = []
        iCount = 0
if len( sublist )>0:
    result.append( sublist )

# View the result of the resultLines transformation done here under
#for l in result
#  print( ''.join(l) )
  
# Joindre les représentations Hexa Decimal en ligne (une string par ligne de l'image) )
#
# resultLines = [ '000000000000000000000000000000' ,
#                 '000000000000000000000000000000' ,
#                 ...
#               ] 
resultLines = [ ''.join(l) for l in result ]

outFile = open( 'GGLOGO.GRF' , 'w', encoding='utf8' )
for l in resultLines:
    outFile.write( l+'\n' )
outfile.close()

Je vous laisse décortiquer le script qui génère un fichier 'GGLOGO.GRF'. C'est un peu brute de décoffrage mais vous devriez pouvoir reproduire toutes ces étapes par vous même.

Le fichier GGLogo.GRF contient uniquement les données Hexadécimales de l'image à placer dans le bloc data de la commande ZPL ~DG .
Comme déjà calculé précédement, le paramètre T faut 3045 octets et le paramètre w vaut 15 octets.
Au final, nous avons une belle étiquette en ZPL dont voici le contenu complet.

~DGR:GGLOGO.GRF,03045,015,
800000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000020
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000000000000000020
000000000000000000000000000010
000000000000000017fffff8000020
0000000000000000e0000007000010
000000000000000300000000400010
000000000000000c00000000300020
00000000000000101e0fff400c0010
0000000000000060e230003f030020
000000000000004304200000e08010
000000000000010c002000001c4010
000000000000023004200000039010
00000000000004400420000000d820
000000000000088004400000003410
000000000000130004400000000b10
000000000000220004400000000490
000000000000cc0004400000000360
000000000001900004600000000130
000000000006600004400000000110
000000000018800004400000000120
00000003c0e3000000400000000190
00000007f90600000a640000005e20
0000001ffe01f017f8017ffffd0010
0000003ffe000de800000000000020
0000007fde000003002000e0000010
000000fc0300fc0f007fc1e000f020
000000fc0103ff0f503ff1ff0fff10
000001f80807ff8ffc7ff1ff9fffe0
000001f80fe7878ffffff9ffffeff0
000001f807ff1f0fe3f1f9f87f01f0
000001f003feff0f878079f07c0070
000001f003ee000f078039e07c0070
000001f0e3ce001e07003de0781830
000001f1b3ce001f0f003de0783c38
000001f363cf000e07001de0786438
000001f3e3cf01df07803de0f83c38
000001f9c7cf83df07c07de07c183c
000001fc07cfffdf03fffde0fc007c
000000fc0fc7ff9f03fffdf07e00fe
000000ffffc1ff3f00ffbff07f01fe
000000ffffc0700b002f3ff87ffffe
0000007fffc00000000004007fffff
0000003fff800000000000003fffff
0000000fff000000000000001fff9c
00000003fc000000000000000fff00
000000103c0000000000000003fc00
000000103c00000000000000000000
000000103c00000000000000000000
000000103800000000000000000000
000000103c00000000000000000000
000000187c00000000000000000000
0000001cfc00000000000000000000
0000001ffe00000000000000000000
0000000ff000000000000000000000
00000007e000000000000000000000
000000010400000000000000000000
000000001c00000000000000000000
000000301c00000000000000000000
000000383c00000000000000000000
000000381c00000000000000000000
000000383c00000000000000000000
000000383c00000000000000000000
0000003c3c00000000000000000000
0000003cfc00000000000000000000
0000001ffc00000000000000000000
0000001ffc00000000000000000000
0000001ffc00000000000000000000
0000000ffc00000000000000000000
000000073c00000000000000000000
000000003c00000000000000000000
000000003c00000000000000000000
000000603c00000000000000000000
000000203c00000000000000000000
000000307c00000000000000000000
000000707c00000000000000000000
00000039f800000000000000000000
0000003ff800000000000000000000
0000001ff000000000000000000000
0000001fe000000000000000000000
00000007c000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000
^XA
^F00,0^XGR:GGLOGO.GRF,1,1^FS
^FX--FAIRE ETIQUETTE LARGE--^FS
^FX--Print Qty--^FS
^PQ1,0,0,Y
^FX--FR LABEL--^FS
^FO20,25^AR,40,30^FB405,1,0,C,0^FD@@LIBEEN@@^FS
^FX--PRODUCT CODE--^FS
^FO120,75^AT,40,30^FB300,1,0,C,0^FD@@ARTI@@^FS
^FX--BOX LOGO--^FS
^FO20,65^GB100,130,2^FS
^FX--EAN - CODE128--^FS
^FO230,130^BY1^AD,18,10
^BCN,40,Y,N,
^FD@@EAN@@^FS
^XZ
N'oubliez pas de remplacer les @@code@@ par des valeurs pertinentes... et surtout de sauver votre fichier en UTF8. En espérant que cela puisse vous être utile.

dimanche 26 juillet 2015

urwid: interface en mode texte (console) pour Python

Tkinter + megawidget offre de belles possibilités d'interfaces graphique d'autant que le projet pygubu (designer Tkinter pour python) semble encore très actifs.
Il n'empêche que de telles interfaces consommes pas mal d'énergie pour une première mise en oeuvre.

Nos petits projets n'ont pas toujours besoin d'autant de raffinement et dans de nombreux cas, une petite interface en mode texte (console) serait bien suffisante à notre confort.

NCusrse
NCurse l'outil le plus connu pour dessiner des interfaces en mode texte sous Python. Il est assez facile de trouver de la documentation sur le Net.
C'est pas mal mais un peu barbare. La aussi il y a un certain investissement avant d'être productif.

Snack
J'ai déniché snack en m'intéressant à Grub. Il s'agit d'une interface en mode texte pour Python dont le but est de pouvoir facilement réaliser des outils de configuration système. Il va de soi, pas question de compter sur XWindows lorsque l'on fait de l'administration a distance en ssh. Snack peut donc faciliter la vie dans certains cas.
Snack vaut peut être la peine de s'y pencher, voici quelques liens pour référence:
urwid
Urwid semble être un projet très intéressant car il permet la réalisation d'interface texte de façon "Pythonique".
Ce fut une découverte un peu fortuite sur une vidéo YouTube traitant des interfaces en mode texte.

Le code est agréable à lire et assez facile à comprendre (voir exemple dans le vidéo ci-dessus). Cela en fait un outil de choix à étudier d'autant qu'il ne manque pas d'exemples sur le site urwid.org.

Note complémentaire:
Mon but étant de construire rapidement une petite interface de gestion avec un interface acceptable. Je me suis heurté à la complexité du Framework de Urwid. Il m'aura fallut 8 heures pour réaliser un menu et un premier écran (dont je ne suis vraiment pas satisfait).

En fin de compte, la lourdeur du Framework d'Urwid (composant de type Block et les autres de type Flow ainsi que le mix Block + Flow) aura raison de ma patiente. 8 Heures plus tard je suis toujours au point de départ (ou presque).
Résultat: J'abandonne Urwid.

Cela ne signifie pas qu'Urwid ne conviennent pas pour des projets de plus grandes envergures mais quitte à faire un tel investissement d'apprentissage alors je préfère le faire dans Tkinter pour Python.


L'installation passe par un simple:

apt get install python-urwid


Voici quelques ressources complémentaires:
S'y retrouver dans urwid ne coule pas de source (surtout à cause des "décorations" widget). Le liens suivant permet de fixer les idées plus facilement après les premiers tuto.
A voir aussi

vendredi 10 juillet 2015

Zebra + ZPL sous Clipper Windows - Harbour for beginner, une reference documentaire Clipper sour Windows

Déjà évoqué plusieurs fois, Harbour-Project permet de compiler des logiciels Clipper sous Win32 et ainsi de profiter pleinement des avantages de la plateforme Windows a partir d'un vieux logiciel (appel COM, impression, Mutex, WinSock, etc).

Harbour for Beginner
Je viens de trouver un chouette document "Harbour for Beginner" d'alexender Kresin (anglais) où il aborde des points très intéressantes:
  • Some language extensions
  • Classes and objects
  • Lang API (the language of the program)
  • Codepage API
  • Working with hrb - files
  • The built-in C
  • Hash arrays
  • Regular expressions
  • INET subsystem
  • Multithreading
  • Miscellaneous functions
Le point  "Lang API" et sa fonction hb_StrToUtf8() tombe bien, je dois justement créer des fichiers en UTF-8 pour envoyer un flux ZPL à ma petite zébra ... voyez l'article "Imprimante Zebra USB, ZPL et Spooler Windows - Sacré Windows!!!" .

C'est que la Zebra n'apprécie pas l'ANSI, elle fait sauter tous les accentués si le flux est ANSI mais en UTF-8 c'est impeccable.


Création d'un fichier ZPL et envoi Sur Zebra
Voici un petit programme clipper compilé avec Harbour Project qui crée un fichier ZPL puis l'envoi sur l'imprimante Zebra LP 2824 nommée ZEBRA_RAW (voyez l'article "Imprimante Zebra USB, ZPL et Spooler Windows - Sacré Windows!!!").
Pas le code le plus pure... mais le vilain proto marche très bien :-)

&& #include "mediator.ch"  Mediator - PostgreSql middleware
#include "fileio.ch"

LOCAL hDst := NIL 
LOCAL sOutFilename := 'demo.zpl'
LOCAL sPrinterName := 'ZEBRA_RAW' 

&& Mediator - PostgreSql middleware
&& request medntx
 
REQUEST HB_CODEPAGE_FRWIN
SET( _SET_CODEPAGE, "FRWIN" ) 

&& Mediator - PostgreSql middleware
&& RDDSETDEFAULT("MEDNTX")  && set default data source to MEDNTX RDD driver


* -- initialisation
set date british
set delete on
set confirm on
set escape on
set wrap on
set epoch to 1920

? 'Create a '+sOutFilename+' file encoded with UTF-8'

&& Open Destination Filename 
hDst := hb_FCreate( sOutFilename, FC_NORMAL, FO_READWRITE + FO_SHARED )
IF hDst == F_ERROR 
     sMsg = "Impossible de recréer FAReprint.txt!" 
     ALERT( HB_AnsiToOem(sMsg) ) && convert to "DOS String" 
     RETURN NIL
ENDIF

&& Surprenant, pas besoin de hb_StrToUtf8(), le fichier est déjà
&&    encodé en UTF8! Impression sur Zebra Parfaite
cBuf = '^XA'
fwrite( hDst, cBuf + hb_eol() )
cBuf = '^FO100,50^ADN,36,20^FDxyz*é&êà*^FS'
fwrite( hDst, cBuf + hb_eol() )
cBuf = '^XZ'
fwrite( hDst, cBuf + hb_eol() )

FCLOSE( hDst )

? 'WIN_PrintFileRaw: Send ZPL to Printer '+sPrinterName 
retCode = hb_ntos( WIN_PrintFileRaw( sPrinterName, (sOutFilename) ))
? "print retCode for "+sPrinterName+" = "+retCode
 

Ressources

jeudi 9 juillet 2015

Imprimante Zebra USB, ZPL et Spooler Windows - Sacré Windows!!!

Introduction
Par le passé, j'avais déjà écrit l'article "Imprimante Zebra USB, ZPL et CUPS - Damned CUPS!!!" déjà source de nombreuses informations.
Cette fois, je reprends le savoir acquis, ma petite Zebra LP 2824 plus en USB et j'essaye d'imprimer le fichier demo.zpl depuis ma machine Windows 7 et sur la Zebra qui y est branché via USB.

Mr Gates, je voudrais envoyer un fichier Raw à ma petite printer zébrée... mais pourquoi est-ce si tortueux?

Comme d'habitude avec W!nd0w$ il faut gratter un peu car rien n'est plus compliqué que de vouloir faire simple! C'est que derrière, je dois faire fonctionner le tout avec un bon vieux soft Clipper compilé en Win32 (vive HarbourProject).

NB: Moi qui me plaignait de CUPS la dernière fois, j'en était presque arrivé à considérer l'usage d'un Raspberry-Pi pour mettre ma Zebra sur le réseau!

Le but rechercher 
Envoyer le contenu du fichier demo.zpl (contenant du code ZPL) directement à l'imprimante Zebra

Installer l'imprimante Zebra
Hé bien, pour commencer, nous allons simplement utiliser les pilotes Windows livrés avec l'imprimante (sur un CD) et installer l'imprimante Zebra "LP 2824 Plus".
Sélectionnez le pilote d'imprimante avec le support ZPL (pas celle avec l'EPL).
Il faut être patient, Windows n'est pas forcement très rapide... cela m'aura pris pas moins de 15 minutes et l'imprimante installée deux fois ?!?!.

Faites bien attention durant l'installation, dans la configuration des ports, vous verrez l'utilisation d'un port du type USB00x (dans mon cas, ce fut USB001)

Faites une impression de "page de test", cela doit marcher, j'ai un morceau de logo Windows sur une étiquette.

Installer une imprimante Text / Generic
Ensuite, nous allons installation d'une imprimante générique de type texte et nous allons utiliser le port USB00x attribué à notre Zebra à l'étape précédente.
Durant l'installation, nous allons:
  • Donner un nom simple à cette imprimante (ex: ZEBRA_RAW) pour pouvoir y accéder facilement.
  • Partager cette imprimante afin qu'elle soit disponible sur le réseau (c'est vraiment essentiel pour imprimer un fichier depuis un ligne de commande!)
Au final, nous nous retrouvons avec une imprimante ZEBRA-RAW permettant l'envoi de commandes ZPL directement à l'imprimante.

Vous trouverez également plus d'information sur ce sujet dans l'article "Setting up a Raw Printer in Windows" (qz.io, anglais)

Préparer notre fichier demo.zpl

Ouvrez un notepad puis collez le code suivant;
^XA
^FO100,50^ADN,36,20^FDxxx^FS
^XZ

En sauvegardant votre fichier, faite bien attention à utiliser l'encodage UTF8 (je ne sais pas trop comment serait digéré de l'ANSI)

Envoyer le fichier demo.zpl à la Zebra
Comme le dit si bien Mitch dans son article "Send PRN File to Printer", il n'y a plus grand monde qui dispose d'une imprimante série.
Du coup les commandes COPY /B C:\FILENAME.PRN LPT1: ne sont plus d'actualité car elles ne fonctionne pas avec USB001 :-/

Par contre, notre monde over-connecté-sur-le-net permet d'utiliser une telle commande si lpt1: est remplacé par le nom d'une imprimante partagée sur le réseau (avec la structure \\nom_pc\nom_partage_imprimante )
Du coup, si l'on a partagé son imprimante Zebra sur le réseau (ce que j'avais chaudement conseillé au point précédent), il sera possible d'utiliser une commande comme celle-ci:

c:\temp> copy /B demo.zpl \\127.0.0.1\ZEBRA_RAW\
        1 fichier(s) copié(s).

Résultat:

Note: le /B indique une copie binaire tandis que 127.0.0.1 est l'adresse réseaux loopback (celle qui renvoi vers mon PC).
C'est un peu alambiqué mais cela fonctionne sans devoir installer Visual Studio ou n'importe quel autre soft.

Vous voila prêt à faire quelques essais en ZPL, il est très facile de trouver de l'information sur le NET. Le plus dure, a savoir un envoi RAW clean, vient d'être réaliser :-)

Happy coding :-)

lundi 6 juillet 2015

Identifiier les fonctions dans une DB PostgreSQL

Voici de petites requêtes pour PostGreSQL permettant d'identifier les fonctions déclarées dans une DB. Le petit truc à garder dans une boite à outil.

Saviez-vous que 3 caractères sont déjà très discriminatoires lors d'une recherche?
Ce SQL devient vachement pratique lorsqu'il y en beaucoup de fonction car elle permet de faire une recherche sur mot clé avec une "where" clause et un "like" :-) 

-- Lister les fonctions définies dans une DB
--   src: http://stackoverflow.com/questions/1559008/list-stored-functions-using-a-table-in-postgresql

SELECT  p.proname
FROM    pg_catalog.pg_namespace n
JOIN    pg_catalog.pg_proc p
ON      p.pronamespace = n.oid
WHERE   n.nspname = 'public';

-- Identifier sur base d'une partie du nom de la fonction
--    AND proname like '%ver%'




-- Lister les fonctions définies AVEC nom des arguments
--   Pratique lorsqu'il faut réaliser un DROP FUNCTION <name>(<args>);
--  

SELECT  proname, proargnames
FROM    pg_catalog.pg_namespace n
JOIN    pg_catalog.pg_proc p
ON      pronamespace = n.oid
WHERE   nspname = 'public';






jeudi 2 juillet 2015

Python - Fichier startup pour le shell

On en apprend un peu tout les jours...
Cet article de Sam&Max se penche sur la variable d'environnement PYTHONSTARTUP qui permet d'indiquer un fichier .py à exécuter au démarrage d'un shell Python.

Si cette option peut paraître farfelue de prime abord, elle est très pratique pour le manipulations et tests.
Avec ce fichier startup vous pouvez:
  • Déjà pré-importer les bibliothèques que vous utilisez au jour le jour.
  • Vous préparer un répertoire Temporaire prêt à l'emploit.
  • Afficher le détail de l'environnement virtuel (s'il est activé)
  • Créer des raccourcis pour les fonctions p = print et pp = pprint.
  • *** UNE PERLE *** Créer un objet Store pour garder des objets entre deux sessions shell!!.
Vous trouverez ci-dessous le petit bout de code de l'objet Store... voyez la source dans l'article de Sam&Max. L'idée est vraiment génial et l'utilisation vraiment fluide.

import sys
import os
import shelve
import atexit

... voyez le code complet dans 
... l'article de SAM & MAX

# avoir un dico persistant pour garder des objets entre deux sessions. Pratique quand
# on a un gros array numpy qu'on n'a pas envie de se faire chier à se recréer
class Store(object):
    def __init__(self, filename):
        object.__setattr__(self, 'DICT', shelve.DbfilenameShelf(filename))
        # cleaning the dict on the way out
        atexit.register(self._clean)
 
    def __getattribute__(self, name):
        if name not in ("DICT", '_clean'):
            try:
                return self.DICT[name]
            except:
                return None
        return object.__getattribute__(self, name)
 
    def __setattr__(self, name, value):
        if name in ("DICT", '_clean'):
            raise ValueError("'%s' is a reserved name for this store" % name)
        self.DICT[name] = value
 
    def _clean(self):
        self.DICT.sync()
        self.DICT.close()
 
# Ainsi on peut faire store.foo = 'bar' et récupérer store.foo à la session
# suivante. Moi je store tout dans un truc temporaire mais si vous voulez
# garder la persistance entre deux reboots, il suffit de choisir un autre
# dossier. 
python_version = "py%s" % sys.version_info.major
store = Store(os.path.join(TEMP_DIR, 'store.%s.db') % python_version)



Ressource

mercredi 1 juillet 2015

Des outils de backup en Python

Je suis assez fan de Cobian Backup... logiciel librement accessible mais uniquement le soft et sous Windows.

C'est en me faisant cette réflexion, et aussi parce que je suis mordu de Python, que je me suis demandé s'il existait des solutions à bases de Python.
Je ne suis pas déçu de ma petite recherche, j'ai trouvé des choses très intéressantes.
Mes résultats se séparent en deux groupes:
  • Recettes - du code, simple et efficace. Il est toujours intéressant de savoir comment les choses fonctionnent... cela peut vous servir dans vos propres logiciels.
  • La grosse artillerie - J'ai été franchement bluffé de trouver des logiciel très avancés qui font de l'encryption, copie distante (sur Amazon S3 Glacier), backup différentiel, etc.
Ce qu'il y a de vraiment bien, c'est que ces solutions sont -- pour la plupart du temps -- intégrable dans vos propres projets Python.
Allez, assez parlé, faisons un petit tour des découvertes


Backup your files (Python recipe)
Recette Python - probablement la meilleure source pour une mise en oeuvre rapide.
Sans se prendre la tête, ce code permet de faire une duplication de fichier en maintenant un nombre maximum de copies. Il accepte également le backup de plusieurs répertoires.

100 lignes de code d'une très-très-très grande utilité avec un minimum de dépendance.

Disponible sur code.activestate.com/recipes/191017-backup-your-files

Python-S3-Backup
Recette Python - déniché sur GitHub.
Ce script permet de copier des répertoires et des dumps MySql sur Amazon S3. Vous pouvez d'ailleurs configurer une tâche cron (renseigné par l'auteur) pour démarrer le backup automatiquement.

Le code fait 108 lignes et les dépendances sont réduites à boto qu'il faudra installer avec un pip.

Disponible sur github.com/psugand/Python-S3-Backup

Attic 
Artillerie lourde - ma préférence pour le moment car il est ni trop simpliste, ni trop complexe. Ce logiciel est disponible en standard sur Debian, Ubuntu, Arch Linux et Slackware.

Attic est un programme de duplication écrit en Python. L'objectif principal de Attic est d'offrir un approche efficace et fiable pour la copie de données. Cette technique de duplication de fichier fait de Attic un bon candidat  pour les backups journaliers puisqu'il ne stocke que les fichiers modifiés.

Disponible sur attic-backup.org

Cedar Backup
Artillerie lourde - Ce logiciel sort clairement du jouet. S'il n'est pas destiné à gérer les backups d'envergure, il conviendra (selon l'auteur) à de petites entreprises.
Cedar backup est développer sous forme d'une bibliothèque de backup + Interface logicielle de backup. Hormis la copie sur support physique (CD-ROM), Cedar backup offre également des possibilités de stockages sur Amazon S3

Ce projet, hébergé sur source forge, propose une documentation fournie, ce qui permet de vraiment envisager l'utilisation de Cedar Backup comme solution d'entreprise.

Disponible sur cedar-backup.sourceforge.net

BakThat - Python backup framework and command line
Artillerie lourde - là nous sortons clairement de la zone logiciel pour mettre le pied dans un Framework.
Bakthat est un framework de backup écrit en Python et sous licence MIT. Il est composé d'un outil en ligne de commande et d'un module Python qui peut vous aider à gérer vos backups sur Amazon S3/Glacier et OpenStack Swift. Il compresse et encrypte automatiquement vos données (encryption symétrique) et téléverse vos fichiers vers l'espace de stockage.
BalThat héberge son propre serveur Python, cela signifie qu'il est possible de configurer plusieurs clients BakThat qui se synchronisent avec le serveur.
Pour finir, vous pouvez accéder directement à vos backup sur l'espace de stockage, seul le logiciel de décryptage BeeFish et untar seront nécessaire pour accéder à vos fichiers. 

La documentation est fournie mais il y a pas mal de dépendance, BakThat est a considérer que si vous avez des besoins importants.

Plus d'information ici.