Friday, June 13, 2014
Pretending to be a Ptouch Printer in QBasic
I was always curious just exactly what the computer would actually send the PT-PC printer. So when I saw this webpage that stated that "the PTPC could only print 24 pixels wide" it piqued my interest.
"For arbitrary graphics, this printer can only print raster lines 24 pixels wide down the centre of the tape (and this way also arbitrary fonts in low resolution). It can print text using its built-in fonts, but that would require a special text-to-PT-PC driver."
I had to find some way to snoop the serial port and found this program:
Portmon for Windows v3.03 By Mark Russinovich
I fired it up, printed a couple of test labels, and then edited the output to make a binary file. I could see that it used only a few ESC commands, all of which were nicely documented in the PT-PC manual. It would be wonderful if every device that you bought came with complete technical documentation like that PT-PC. I remember my old Epson JX-80 printer included great technical documentation and tutorials. I really loved that old printer, horribly noisy but solid and most importantly, it had reinkable ribbons.
QBasic has always been kind of fun to hack with as it's very forgiving and gives you lots of feedback. I hacked up a little program that would open the binary file and draw the output on the screen.
Then I had an idea: Why not try to impersonate the PT-PC directly using the computer serial ports? All I have to do is to send a 32 byte sequence whenever the driver sends an "ESC i S" 1b 69 53 status request. QBasic supports up to 9600 baud serial communications which is great because the PT-PC uses 9600 baud.
The PT-PC uses a DB-9 serial port and I'm using a laplink cable to connect to it, so I've already got the cable.
So I wrote a little qbasic program to open the serial port and watch what it sent.
Unfortunately, it wouldn't send any bytes at all. I tried it over and over again and was totally stumped. I looked at the portmon snoop and how it opened the port. Somehow it was wanting to do a DSR handshake. I have a LED inline serial port display device and plugged that into the line. It looked exactly the same (the same leds were lit up) whether I plugged in the PT-PC or my laptop serial port. This was driving me crazy!!!
I left it for a day, came back to it, struggled again, left it for another day, came back, struggled again. It was horribly frustrating, like beating your head against the wall.
Then I had an insight: it's driving the PTCOM: port in the windows printer driver. Maybe I would specify the COM1: port instead! Once I selected COM1: it started sending bytes! WOO HOO!
The graphics looked funny because the MSB is not the lowest pixel of the byte. I got it to render the graphics properly by changing my renderdata routine to draw the ycoordinate with ypos+(7-bitpos) instead of ypos+bitpos.
There's something really cool and satisfying about watching it interpret the data and render the graphics to screen. Unfortunately, QBasic is terribly slow and it can't really keep up with 9600 baud data. For a decent sized PT-PC print job it can take many minutes (or more!!!). I suppose my junky 366 mhz laptop running win98 and qbasic could be swapped for something faster, but it works.
Then I said to myself, "hmmmm I've got other PTouch printers, why not try to simulate them?" Then after I got the PT2600 working I thought, "why not download the drivers for some of the PTouch printers I don't have?" So I got the PT-PC, PT2400, PT2600, PT9200, PT9500 and PT9700 drivers working with my QBasic program. The PT2600 is a USB printer and its USB printer drivers work fine with the COM1: port. But how to figure out what the printer code is that ESC i S is looking for?
First I snooped the USB port for the PT2600. Analyzing the print jobs gave the "B0H0" code.
Then I figured, why not just "try every combination". If I specify ptmodel$="UNKNOWN" in my code, every time it gets a status request it increments the ptunit$. So it tries "B000", then "B010"... If you keep pressing the "play" button in the windows print driver it cycles through and when it says "Wrong cassette inserted" instead of "Connected Ptouch is not the PT-2600" you know what the ptunit$ code is.
One thing to note is that QBasic can not keep up with the print job in realtime. So I've done a couple of things to compensate. I enlarged the serial receive buffer to the maximum 65535 bytes. Also, when it requests the status with (ESC i S) we reply with not just the 32 byte status block but also a few other 32 byte status blocks that it will expect later. Since it doesn't empty the receive buffer, those blocks will be there when it is ready to look for them. Otherwise it will timeout and give you a "Communication Error".
The sequence it looks for is
printstatus 0,0,0,0
printstatus 0,0,0,0
printstatus 6,1,0,0
printstatus 1,1,0,0
printstatus 6,0,0,0
A couple of the printer drivers will clear the receive buffer so we just put a little delay to wait until after it's done that. So the code looks like
printstatus 0,0,0,0
sleep 2 'wait for the buffer clear
printstatus 0,0,0,0
printstatus 6,1,0,0
printstatus 1,1,0,0
printstatus 6,0,0,0
Before I was able to get the timing right, I made it send the printstatus on hitting a "1" keystroke. It watches the INKEY$ and sends printstatus when inkey$="1". So I'd let the program respond to the initial ESC i S with a normal printstatus 0,0,0,0 and then midway through the print job I would type the "1" key.
You can send as many printstatus 0,0,0,0 as you like and the printer driver will gobble them without complaining.
So for the 7 people in the world who would be interested in this, here's my PTSIM.BAS qbasic program.
What good is this program? It could be very useful to someone developing printer drivers to check the output without burning up expensive Ptouch cartridges on test prints. It's also useful to "dump" the binary data coming over the COM1: port. Just set logfile$ to a valid filename and it will record every byte to a file. You may also set the inputfile$ to a filename if you want to take the input from a file on disk instead of the COM1: port. If you want the "perfect" printout without wasting a lot of ptouch cassettes, you can print to this simulator to see exactly what output will be generated, so you can try all of the different dithering styles.
For example, to take the input from a file:
ptmodel$ = "ql500"
ptunit$ = "O" 'uppercase Oscar
inputfile$ = "ql500.bin"
outputfile$ = ""
dorender = 1
tapewidth = 62 'for dk-1202
tapewidth = 29 'for dk-1201
mediatype = 10
To take the input from the COM1: port and just save it quickly to ql500.bin without rendering:
ptmodel$ = "ql500"
ptunit$ = "O" 'uppercase Oscar
inputfile$ = "com"
outputfile$ = "ql500.bin"
dorender = 0
tapewidth = 62 'for dk-1202
tapewidth = 29 'for dk-1201
mediatype = 10
My qbasic code isn't pretty and there's a lot of vestigial stuff, but it's not too complex to understand. Mostly I hate getting the indents right as the qbasic editor is worthless in that regard.
Oh, and the instructions for the "user interface": m to set the model, up and down arrow to change the selected item, t to set tape width, i to set input file, o to set output file, d to set the "dorender mode" (0, 1 or 2 to delay the render which makes two passes, first dorender=0 then dorender=1) and g to "go". Please watch the caps lock as it's not very clever about matching uppercase M.
PTSIM90.BAS
Subscribe to:
Post Comments (Atom)
Wow this is incredibly awesome! Emulating a printer in Qbasic. Have you ever tried compiling your basic program? It should be significantly faster, especially Visual Basic 1.0 for MS-DOS (Yes there was such a thing).
ReplyDeleteHi there
ReplyDeletetrying to contact you somehow but sadly can't find any methods and hoping this blog along with the addresses etc are still working
Den