views:

308

answers:

2

I've stumbled across a problem way beyond my area of expertise, and I don't have a mentor to turn to for help with this.

I have a receipt printer I need to interface with through an iOS app. The printer is located on the same network as the device(s), so I can address it through supported "Line Mode commands"

What I'd like to do is keep the code I have already that works cross-platform – i.e. it's a UIView/NSView, and if you're not familiar with OS X/iOS, it's just a standard vanilla view that I can render into PDF/PNG formats. Thankfully, the printer has a "raster graphics" mode that seems to be what I need.

Unfortunately, be it the broken English of the command spec, or my complete lack of knowledge of anything beyond basic C, or my complete lack of knowledge regarding graphics, I have no idea how to even get started from the command specifications I have. I know the printer and my networking works because I can address it over the network and send it basic feed commands. But, I have no idea how to go from a PNG -> whatever the printer needs to make it's 'raster mode' work.

The specification is available at http://www.star-m.jp/eng/service/usermanual/linemode_cm_en.pdf , and the page you'd want to start reading it if you want to help is 3-68, and the specific commands I'm having trouble even getting started with are on 3-78/3-79.

I can give you nothing but a checkmark but I assure you, you'll have my undying gratitude if you can even provide me even a point in the right direction.

+4  A: 
Amigable Clark Kant
hmm, it's very close, they definitely use the same basic ESCP based language, but ESCP/ESCP2 don't seem to have a 'raster mode' baked in, and another 45 min of searching based on that didn't turn up anything. have an upvote though! it's close.
refulgentis
@refulgentis, you could try run the codes anyway and see what happens. Sometimes hardware understands old commands even though it is not advertised in any manual. Yes, even if it works, it is considered bad to use undocumented features, but things like this seldom change anyway, so it might be OK.
Amigable Clark Kant
aye, but the ESC/POS commands I found were based on sending ASCII strings and getting them printed. I need to be able to send arbitrary image data. That PHP class looks fascinating, will look through more.
refulgentis
Hm, it seems from the manual that you could update the font data of the "blank code page". This could be a way to write graphics. Update fonts to your graphic data. (I don't know if this is insane or not. This was how you programmed graphics on the Sinclair Spectrum.)
Amigable Clark Kant
a little too insane for my blood. :P you might want to look at my last comment on the other question, since you've stuck with this and it's close.
refulgentis
Yes, I did. I will look at this more closely at home. I think it's pretty interesting. I have one of the old label writers at home somewhere. I will dig out and play with it.
Amigable Clark Kant
hrmm...I don't think your sample code will work because the second and third bytes of sendRasterData[] should be the values that fit the equation (n1 + n2 * 256) = number of bytes being sent. Or is that wrong?
refulgentis
As I understand it, these are not proper equations. They just say that you must define an area to where on the paper you want to print. (n1, n2) Then d are the data rows, up to k rows, where k must a least one.
Amigable Clark Kant
From reading the documents over and over, I'm pretty certain that in raster mode the data is in X Y format rather than Y X, which is for line mode.
Richard Harrison
@Richard Harrison, either way, wouldn't that "just" result in the image printed flipped over? Or totally garbled output?
Amigable Clark Kant
@Amigable Clark Kant - for a square image it would be flipped, but otherwise it only the first column would be flipped, and the second column would start with the overspill or underspill from the first column etc.... that would probably look garbled...
Richard Harrison
@Richard Harrison, ah, yes of course - when you say it like that it seems so obvious. (Though it wasn't to me.) So in my (non-working, anyways) it would look "OK", only flipped. (Since I used a 256x256 image.)
Amigable Clark Kant
+6  A: 

Having written a few printer drivers I can confirm that generally the documentation is confusing because of the way printers work. The document that you refer to doesn't actually seem to bad to me.

I think you're right to be printing in raster mode and that overall this is going to give the best results.

From the Star documentation I reckon you'll need to send :

1. \x1b*rR  Initialize raster mode
2. \x1b*rA  Enter raster mode
3. \x1b*rC  Clear raster data
4. \x1b*rml
4. b\x##\x##\xAA\xAA\xAA....<DATA>..........
5. \x1b\x0C\x00 Raster Form feed(??) - should spit out the data.
6. \x1b*rB  Clear raster data

Obv. in the above \x1b is the C encoding of ESC (i.e. character 27 0x1b).

From all of the documentation that I've been reading the following is how the images should be formatted in raster mode. When in line mode it is completely different as the vertical & horizontal are swapped. From THERMAL PRINTER PROGRAMMER'S MANUAL (TSP552,TSP552II,TSP2000)

Star Raster Data

This equates to the following bytes stream.

Star Raster bytes

On the 4th command line it is effectively 'b' followed by two bytes definining the size. This size is computed as the number of pixels contained in the stream % 256 and / 256. So for 320x1 that'd 0x40,0x01

So, taking the above and plugging it into a simple test program you should test with this:

char rasterImage [] = {
0x1b, '*', 'r', 'R',       //  Initialize raster mode
0x1b, '*', 'r', 'A',       // Enter raster mode
0x1b, '*', 'r', 'C',       // Clear raster data
//          n1  n2 d1    d2..
0x1b, 'b', 0x2, 0, 0x00, 0x00, // data
0x1b, 'b', 0x2, 0, 0x1F, 0xF8,
0x1b, 'b', 0x2, 0, 0x3F, 0xFC,
0x1b, 'b', 0x2, 0, 0x77, 0xEE,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0x0F, 0xF0,
0x1b, 'b', 0x2, 0, 0x1F, 0xF8,
0x1b, 'b', 0x2, 0, 0x1F, 0xF8,
0x1b, 'b', 0x2, 0, 0x3E, 0x7C,
0x1b, 'b', 0x2, 0, 0x38, 0x1C,
0x1b, 'b', 0x2, 0, 0x79, 0x9E,
0x1b, 'b', 0x2, 0, 0x73, 0xCE,
0x1b, 'b', 0x2, 0, 0x73, 0xCE,
0x1b, 'b', 0x2, 0, 0xF9, 0x9F,
0x1b, 'b', 0x2, 0, 0xF8, 0x1F,
0x1b, 'b', 0x2, 0, 0xFE, 0x7F,
0x1b, 'b', 0x2, 0, 0xFF, 0xFF,
0x1b, 'b', 0x2, 0, 0xFF, 0xFF,
0x1b, 'b', 0x2, 0, 0x00, 0x00,
0x1b, 'b', 0x2, 0, 0x00, 0x00,
0x1b, 'b', 0x2, 0, 0x00, 0x00,
0x1b, 'b', 0x2, 0, 0x00, 0x00};

[self.currentDataBeingSent appendBytes:rasterImage length:sizeof(rasterImage)];

Simply squirt that out to the printer and you should get a picture as above. This is where you can easily tweak and play about with the exact commands to get something that's working. Often this is the only way I've ever managed to figure out what should be done.

rev.3

Ref. comments.

If you have a byte per pixel then you will need to merge these into a series of bits; the following should do the job based on your pastebin code. I've also changed the char* to be unsigned as it is signed can cause problems when bit manipulating.

NSUInteger bitmapBytePerRow = width/8;
NSUInteger bytesPerRow = 3 + bitmapBytePerRow;

[self.currentDataBeingSent = [NSMutableData dataWithLength:bytesPerRow * height];
[self.currentDataBeingSent appendBytes:initializeRaster length:sizeof(initializeRaster)];
[self.currentDataBeingSent appendBytes:enterRaster length:sizeof(enterRaster)];

NSUInteger byteOffset = 0;
for (NSUInteger y = 0; y < height; y++)
{
    unsigned char *rasterCommandForRow = (unsigned char *)calloc(bytesPerRow, sizeof(char));
    unsigned char *current_raster = rasterCommandForRow; 
    *current_raster++ = '\x6B';     
    *current_raster++ = (width*height) % 256;
    *current_raster++ = (width*height) / 256;

    unsigned char mask = '\x80' ;
    unsigned char out = 0 ;
    for (NSUInteger x = 0; x < width; x++)
    {
        if (*(data + (byteOffset * sizeof(char))))
            out |= mask ;
        byteOffset++;
        mask >>= 1 ;
        if( 0 == mask )
        {
            mask = '\x80' ;
            *current_raster++ = out ;
            if( out )
        lastDot = nextOut ;
            out = 0 ;
        }

    }

    // handle partially finished byte .
    if( ( '\x80' != mask ) && ( 0 != out ) )
        *current_raster++ = out ;

    [self.currentDataBeingSent appendBytes:rasterCommandForRow length:bytesPerRow];
}

rev.3a

Looking at the Mac CUPS support from Star it's got the source code for the driver which contains a lot of clues about how this should be done. Sometimes code is so much easier to read than documentation.

starcupsdrv-3.1.1_mac_20100423.zip\starcupsdrv-3.1.1_mac\SourceCode\

contains starcupsdrv-src-3.1.1.tar.gz\ sub folder starcupsdrv\src\

View rastertostar.c, the important bit is the calculation of the n1 / n2 values. These aren't at all X & Y but based on the pixel count, lastBlackPixel is the count of pixels from the source.

putchar('b');
putchar((char) ((lastBlackPixel > 0)?(lastBlackPixel % 256):1));
putchar((char) (lastBlackPixel / 256));

I've modified the code above to include the fixes, hopefully that'll be closer. If not post a scan of what comes out of the printer, it will be useful to diagnose what's happening.

For reference The code between 580:650 from jsStarUSB.cpp seems to me to be along the lines of what you need to produce a buffer (stored in nextOut) that contains the raster data in the format to be sent directly to the printer.

Richard Harrison
Looks promising!
Amigable Clark Kant
very, very, close...I was able to implement your test and I also took a stab at what I need. here's the (very naive) code I have so far: http://pastebin.com/8g8CGFBp . Assume that data is an argument that contains bitmap data with 1 byte per pixel. It gets something to the printer, but the printer just prints out garbage over what looks like 4-6 lines. (I should note the pixels per row is hardcoded at 320 because that's a given too) Any ideas?
refulgentis
refulgentis, is that 320 per row hardcoded in your app, or do you mean the printer is hardcoded for 320? And the 1 byte per pixel, is that gray level?
Amigable Clark Kant
hardcoded in my app, and yes
refulgentis
same issue with the new code :/ and now my data grows to exactly 2 times the length it should. I'm sorry I suck at C and can't debug this myself. current code: http://pastebin.com/sxeBbVzs
refulgentis
Hm,oh, the part about the bits is important. :-/
Amigable Clark Kant
There are a couple of bugs in http://pastebin.com/sxeBbVzs, firstly bytesPerRow should be `bitmapBytePerRow = width/8`, secondly you're allocating rasterCommandForRow and then using it as a pointer thus losing the original address. Revision posted.
Richard Harrison
Still doesn't work at all. :/ I know this is beyond the scope of anything possible at this point, so thanks for your help. I'll just switch to line mode. :/
refulgentis
Don't give up.. I've found some more code and will post a revision. Need to know model number.
Richard Harrison
Very, very interesting. I upvoted early on. I also thought about checking out the CUPS driver if there was anything there, but couldn't muster the willpower and time right now. But suggesting him to post a scan of the print-out - EPIC! ++ for thinking about it and also be bothered to suggest it. Nice touch!
Amigable Clark Kant
@Richard Harrison, @refulgentis: couldn't you (refulgentis) put your printer on the Internet and give Richard H. the IP address to it? A bit unorthodox, but you could have a little debug session with each other. @refulgentis, if you could rig a webcam via Skype, MSN or something, pointing at the printer, So Richard H. could see what he prints, would be awesome.
Amigable Clark Kant
@Richard Harrison: TPS650 @Clark Kant: not the worst idea I've ever heard! Let me know if you're interested Richard.
refulgentis
I'd be happy to try to help debug this directly.
Richard Harrison