neděle 29. listopadu 2015

Print images on POS printer

For some unimportant reason I have mini thermal bluetooth printer IMP006A (ordered from alibaba.com). It is reliable piece of HW which I spent some time playing with.

Here you can see the result. Except receipts the printer can be used to print images. Here I share the piece of java code that does the magic.

The image to be printed must be in PBM format max. 373 px width. During the printing process the image is rotated 90 degrees clockwise.

You can use ImageMagick convert utility to prepare the image for printing.

convert  saturn.png \
        -rotate -90 \
        -scale x373  \
        -dither FloydSteinberg \
        -colors 2 \
         image.pbm

Where saturn.png is the input image of any kind ImageMagick can read. 
image.pbm is the resulting pbm image ready to be printed. 

The image is 
  • saturn.png - read from a file
  • -rotate -90 - rotated 90 degrees CCW
  • -scale x373 - scaled to full width of paper
  • - dither ... -colors 2 - color are reduced to 2 (BW) using dithering to render shades of gray on BW device
  • image.pbm - converson result is written to a file
Then if you have the code compiled just run:

java  printqr.PrintQR  image.pbm

And you will get image.prn containg a set of commands for the printer to print the image. Use linux command line to send the data to the printer.

Use USB to connect the printer to Linux box. When connected to linux box with recent distro (tested on Fedora 20), the printer will be accessible as /dev/usb/lp0. By default the device is accessible for root (and lp group members) only. Send the data to the printer usinf gollowing command:

sudo sh -c 'cat image.prn > /dev/usb.lp0'

And that's all. 

For illustration (click to enlarge):



Code:


package printqr;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;

/** Maximum image printed width (input height - the image is rotated) should be 373 px
  *
  * To prepare image for printing:
  * convert saturn.png  -rotate -90 -scale x373 -dither FloydSteinberg -colors 2 image.pbm
  *  or
  * convert saturn.png   -rotate -90 -scale x373 -dither Riemersma -colors 2 image.pbm
  *
  *
 * */

public class PrintQR {
    final static int[] wsChars=new int[] {' ','\n','\t'};
   
    public static void main(String[] args){
        FileInputStream fis=null;
        FileOutputStream fos=null;
        try {
           
            //prepare image name
            String inputImage="image.pbm";
            if (args.length>0)
                inputImage=args[0];
           
            if (! inputImage.endsWith(".pbm"))
                throw new IllegalArgumentException("Input image must be NetBPM PBM format with .pbm extension.");
           
            //open and read image
            fis=new FileInputStream(inputImage);
            //read header
            expectChar(fis,'P');           
            expectChar(fis,'4');
            expectWhitespace(fis);
            int width=readDecimal(fis);
            int height=readDecimal(fis);
           
            //read data
            int rowWidthBytes= width%8 == 0 ? width / 8 : width / 8 +1 ;
            int dataLength=rowWidthBytes*height;
            byte[] data=new byte[dataLength];
            int off=0;
            int remaining=dataLength;
            while (remaining>0){
                int cnt=fis.read(data, off, remaining);
                remaining-=cnt;
                off+=cnt;
            }
           
            System.out.printf("Loaded image %d x %d\n", width, height);
           
            fos=new FileOutputStream(inputImage.replace(".pbm", ".prn"));
           
            //reset printer
            fos.write(new byte[]{0x1b,0x40});
           
            //set linespacing 0
            fos.write(new byte[]{0x1b,0x33,0x00});
           
            //print image
            printBitmap24pinDoubleDensity(width, height, data, fos);
           
        }
        catch (Exception e){
            e.printStackTrace();
        }
        finally {
            if (fos!=null) try {fos.close();}catch (Exception e){};
            if (fis!=null) try {fos.close();}catch (Exception e){};
        }
    }

    /**
     * Sends image to the printer in the right format with the right commands
     * @param width
     * @param height
     * @param data
     * @param fos
     * @throws IOException
     */
    private static void printBitmap24pinDoubleDensity(int width, int height, byte[] data,
            FileOutputStream fos) throws IOException {
        int bWidth=width%8 == 0 ? width / 8 : width / 8 +1;
        int hLow=(height) & 0x000000ff;
        int hHigh= ( (height) >> 8 )& 0x000000ff;
       
        for (int x=0; x< bWidth; x+=3){
            // ESC * m nL nH d1... dk
            // m - 0x21 for 24 pin printer double density
            // nL/nH - low/high byte of number of "strokes", each stroke consists of 24 dots (8 bytes)
            // data should be (nL+nH*256)*3 bytes
            fos.write(new byte[]{0x1b,0x2a,0x21,(byte)hLow,(byte)hHigh});
           
            //get appropriate data from image - for the operation concider image rotated 90 deg CCW
            for (int y=height-1; y>=0; y--){
                fos.write(new byte[]{
                        get(data,x,y,bWidth, height),
                        get(data,x+1,y,bWidth, height),
                        get(data,x+2,y,bWidth, height),
                });
            }
            fos.write('\n');
        }
    }
   
    /**
     * Extracts byte of image data - the image is like this in mempory 
     *  ----------------X----------------------->
     * |          0        1        2        3        4        5
     * | 0 ........ ........ ........ ........ ........ ........
     * | 1 ........ ........ ........ ........ ........ ........
     * | 2 ........ ........ .####### ######## ........ ........
     * Y 3 ........ ........ .####### ####.... ........ ........
     * | 4 ........ ........ .######. ........ ........ ........
     * | 5 ........ ........ .#...... ........ ........ ........
     * | 6 ........ ........ .#...... ........ ........ ........
     * V 7 #.#.#.#. .#.#.#.# ....#... ........ ........ ........
     *
     * First triplet sent to printer is
     * this [0,7],[1,7],[2,7]
     * then [0,6],[1,6],[2,6]
     * then [0,5],[1,5],[2,5]
     * and so on
     *
     * This effectively rotates image CCW 90 deg.   
     *
     * @param data
     * @param x
     * @param y
     * @param bWidth
     * @param height
     * @return
     */
    public static byte get(byte[] data, int x, int y, int bWidth, int height){
        if( x>= bWidth ) return 0; //handle request beyond image for images not %24 print height
        int idx=y*bWidth+x;
        if (idx<0 || idx>=data.length)
            return 0;
        return data[idx];
    }
   
    public static int expectChar(InputStream is, int expectedChar)
        throws IOException
    {
        int readChar=is.read();
        if (readChar==expectedChar) return readChar;
       
        throw
            new IllegalStateException(String.format("Expected %c found %c",expectedChar, readChar));
    }
   
    public static int expectWhitespace(InputStream is)
            throws IOException
    {
        int readChar=is.read();
        if (isWhitespace(readChar)) return readChar;
        throw
            new IllegalStateException(String.format("Expected whitecpace found %c",readChar));
    }
   
    public static int readDecimal(InputStream is)
        throws IOException
    {
        int accumulator=0;
        while (true){
            int c=is.read();
            if (isWhitespace(c)) return accumulator;
            else
                if (c>='0' && c<='9') accumulator=accumulator*10+(c-'0');
                else
                    throw
                        new IllegalStateException(String.format("Expected digit or whitecpace found %c",c));
        }       
    }
   
    public static boolean isWhitespace(int c){
        for (int i=0; i<wsChars.length; i++){
            if (c==wsChars[i]) return true;
        }
        return false;
    }


    /** not tested */
    private static void printBitmap9pin(int width, int height, byte[] data,
            FileOutputStream fos) throws IOException {
        int bWidth=width%8 == 0 ? width / 8 : width / 8 +1;
        int hLow=height & 0x000000ff;
        int hHigh= ( height >> 8 )& 0x000000ff;
       
        for (int x=0; x< bWidth; x++){
            fos.write(new byte[]{0x1b,0x2a,0x00,(byte)hLow,(byte)hHigh});
            for (int y=height-1; y>=0; y--){
                fos.write(get(data,x,y,bWidth, height));
            }
            fos.write('\n');
        }
    }

    /** not tested */
    private static void printBitmap9pinDoubleDensity(int width, int height, byte[] data,
            FileOutputStream fos) throws IOException {
        int bWidth=width%8 == 0 ? width / 8 : width / 8 +1;
        int hLow=height & 0x000000ff;
        int hHigh= ( height >> 8 )& 0x000000ff;
       
        for (int x=0; x< bWidth; x++){
            fos.write(new byte[]{0x1b,0x2a,0x01,(byte)hLow,(byte)hHigh});
            for (int y=height-1; y>=0; y--){
                fos.write(get(data,x,y,bWidth, height));
            }
            fos.write('\n');
        }
    }
}








Licence Creative Commons
Graphical printing tool for POS printer, jehož autorem je LRA, podléhá licenci Creative Commons Uveďte původ-Zachovejte licenci 4.0 Mezinárodní .

čtvrtek 26. února 2015

.. on a trip to the C world again

Testing lua embedding and mongoose http server.
Lua is great to implement business logic in the C based app.
It lets you bee less woried about memory leaks, stack overruns, invalid pointer etc.

úterý 24. února 2015

online collaboration in realtime

Some projects for online collaboration:
http://etherpad.org/ - realtime colaboration for text editing
http://socket.io/  - bidirectional realtime communication platform
http://XMPP.org - realtime presence & message protocol

středa 18. února 2015

Tune SSL on Apache 2.4/Windows

Today I spent some time to tune Apache SSL settings to be Grade A at https://www.ssllabs.com/ssltest/.
Finding the equilibrium point between compatibility and transport security took some time. To save yours, I'm sharing the final configuration here.

Some notes at first. I favored security over backward compatibility and so some older (very old in fact) browsers will fail to establish connection. I tried to cope with all failed tests, but not succseeded. There stil are some Failed tests. Those tests are not affecting the main purpose of the server.

  1. Download and install the latest Apache 2.4 binaries to overcome known CVE
  2. I'm using http://www.startssl.com/ as the server certification authority.
  3. Tune SSL Protocol and Ciphersuites and some others at a server level (httpd.conf)
    1. SSLProtocol all -SSLv2 -SSLv3
    2. SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH EDH+aRSA !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
    3. SSLHonorCipherOrder on
    4. SSLUseStapling On
    5. SSLStaplingCache shmcb:logs/ssl_stapling(32768)
  4. Add HTTP Strict Transport Security
    1. Enable headers module: 
      1. LoadModule headers_module modules/mod_headers.so
         
    2. Set header to require HSTS at the VirtualHost level
      1. Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
And you are done.


pátek 13. února 2015

... DIY electronics

From time to time I learn about new DIY electronic project. Here I will sumarize all of them in a short notice:

littleBits

Creations are constructed by placing bits&pieces together glued by magnetic force and connected by contacts. Cloud enabled.
SmartHome Kit - $249 per 14 modules -  $17/module.

SAM

Standalone bits equiped with bluetooth low energy connectivity connected by SW configuration. Battery in a module lasts from 3 weeks of operation (button) to 1 hour (motor). MicroUSB charger.
Cloud enabled.
SAM Pro  - GBP 349 per 11 modules -  GBP31/module



úterý 13. ledna 2015

... backup with Areca

I'm giving try to Areca backup tool which seems as a good multiplatform (Java) tool for simple backup tasks on a workstation and small server.
It is featuring pretty usable GUI and command line interface for common backup tasks.