Ya he escrito varias veces sobre mis (des)aventuras tratando de utilizar una impresora con mi Sinclair Spectrum 128k.
Bien, pero... ¿cómo se imprimía en papel en aquella época? ¿Se puede seguir imprimiendo así? Bueno... Es una historia muy larga. ¿Alguna vez te has fijado en los códigos ASCII por debajo del espacio en blanco (el #32)?
| DEC | HEX | Símbolo | Descripción |
|---|---|---|---|
| 0 | 0 | NUL | Carácter Nulo |
| 1 | 1 | SOH | Inicio de Encabezado |
| 2 | 2 | STX | Inicio de Texto |
| 3 | 3 | ETX | Fin de Texto |
| 4 | 4 | EOT | Fin de Transmisión |
| 5 | 5 | ENQ | Consulta |
| 6 | 6 | ACK | Acuse de recibo |
| 7 | 7 | BEL | Timbre |
| 8 | 8 | BS | Retroceso (borrar) |
| 9 | 9 | HT | Tabulación |
| 10 | 0A | LF | Salto de línea |
| 11 | 0B | VT | Tabulación Vertical |
| 12 | 0C | FF | Avance de página |
| 13 | 0D | CR | Retorno de carro |
| 14 | 0E | SO | Desactivar mayúsculas |
| 15 | 0F | SI | Activar mayúsculas |
| 16 | 10 | DLE | Escape enlace datos |
| 17 | 11 | DC1 | Ctrl. dispositivo 1 |
| 18 | 12 | DC2 | Ctrl. dispositivo 2 |
| 19 | 13 | DC3 | Ctrl. dispositivo 3 |
| 20 | 14 | DC4 | Ctrl. dispositivo 4 |
| 21 | 15 | NAK | ACK negativo |
| 22 | 16 | SYN | Síncronía en espera |
| 23 | 17 | ETB | Fin bloque |
| 24 | 18 | CAN | Cancelar |
| 25 | 19 | Fin del medio | |
| 26 | 1A | Substitución | |
| 27 | 1B | ESC | Escape |
| 28 | 1C | FS | Separador de archivos |
| 29 | 1D | GS | Separador de grupo |
| 30 | 1E | RS | Separador de registro |
| 31 | 1F | US | Separador de unidad |
Los primeros ordendores no tenían pantallas, sino que se comunicaban con el usuario a través de una impresora, que funcionaba como una especie de teletipo. La conexión se realizaba a través de la interfaz serie RS-232. Es cierto; no existía USB en aquella época. Esta conexión serie es la responsable de la necesidad de códigos de caracteres como #4 (fin de la transmisión), #6 (ACK o acuse de recibo).
Para el manejo de la impresora son los códigos #10 (salto de línea), #13 (retorno de carro), o #7 (la campana). ¿En qué se inspiraron para estas impresoras? Pues en las máquinas de escribir de la época.
Mención aparte merecen los códigos #13 y #10. Si vemos un archivo de texto en Windows o MS-DOS con un visor hexadecimal, veremos cada cambio de línea representado con los códigos 0A0D, es decir LF y CR. Y es que, aquellas impresoras funcionaban con un carro que llevaba una cabecera de matriz de puntos (de ahí que se las llamara impresoras matriciales o dot matrix printers). Y, sí, en este tipo de impresoras no solo era necesario hacer avanzar el papel (cosa que se hacía con un rodillo, como en las máquinas de escribir, solo que automatizado con un motor), sino también llevar el carro hacia la izquierda. ¿Por qué no es así en UNIX/LINUX? Los desarrolladores decidieron que dedicar dos caracteres por cada cambio de línea era excesivo, una vez descartados aquellos teletipos y asumidos los terminales, así que lo simplificaron a solo LF.
Por cierto, es importante tener en cuenta que los terminales no eran ordenadores, sino solo proporcionaban una pantalla para ver la salida del ordenador, y un teclado integrado para introducir datos en él.
Hoy por hoy, la necesidad de un protocolo serie ha sida eliminada de cuajo al tener USB, que permite conexiones y desconexiones en caliente, mucha más velocidad, y otras ventajas, como la autoconfiguración, también llamada Plug & Play. Además las impresoras ya no utilizan estos caracteres especiales para saber cómo imprimir texto, sino que utilizan PostScript, por lo que, hoy por hoy, estos 31 caracteres (con algunas excepciones, como el #27 (escape), o el #8 (borrado a la izquierda), no se utilizan.
Así, imprimir un archivo podía hacerse tan fácil como volcar un archivo en un archivo "especial". Y es que, todos los sistemas operativos actuales siguen la metáfora todo es un archivo. Es decir, en lugar de tratar cada dispositivo como un caso especial, se trata de obtener datos de él haciendo como que se lee de un archivo, y enviarle datos haciendo como que se escribe en un archivo. Todos los programadores sabemos que, en un programa en C, los descriptores de archivo 0 y 1 se corresponden, respectivamente, con el teclado y la pantalla. Es decir, el archivo #0 es un archivo de solo lectura, y leyendo de él se obtiene la entrada del usuario. El archivo #1 es de solo escritura, y escribiendo en él se muestra texto en la pantalla del usuario.
Muchos dispositivos requieren sin embargo un manejo más detallado, para lo que existe la llamada al sistema operativo IOCTL, que es capaz de enviar códigos y datos de manera especial para acceder a características de bajo nivel.
En cualquier caso, en Windows y en MS-DOS se puede imprimir un archivo de texto archivo.txt en la impresora por defecto utilizando:
$ type archivo.txt > prn
Sí, PRN es un archivo especial que representa a la impresora, y es la razón por la que no se puede crear un archivo de nombre prn en MS-DOS o Windows.
En UNIX/LINUX, el comando es ligeramente distinto, pero el resultado es el mismo.
$ cat archivo.txt > /dev/lp0
El comando cat vuelva el archivo, igual que type en MS_DOS o Windows. El carácter > indica que la salida del anterior comando se va a redirigir, de la pantalla a otro dispositivo. PRN es el dispositivo de la impresora en MS_DOS/Windows, mientras que en UNIX solía ser /dev/lp0, pero en tiempos modernos es /dev/usb/lp0.
He escrito print.py, una pequeña utilidad que permite volcar datos en texto plano directamente a la impresora por defecto. Admite algunos códigos, como los siguientes:
^[ : chr(s) 27
^v : chr(s) 12
^l : chr(s) 10
^r : chr(s) 13
Si quieremos enviar un texto a la impresora, podemos hacer lo siguiente:
$ python print.py --send "hola!^v"
El comando anterior vuelca el mensaje hola en la impresora, que lo imprime, y a continuación manda el código ^v, que se corresponde con el carácter #12, y realiza un FF (form feed), o alimentación de página, lo que hará que salga el papel.
Si preferimos imprimir un archivo de texto, podemos hacerlo con:
$ python print.py --file datos.txt
¿Cuál es el corazón del programa? Pues nada, abrir un archivo en modo texto y escribir en él.
class Printer:
# más cosas...
@property
def device(self):
return self.__dev
...
def send(self, data: str):
"""Sends new data to the printer.
:param data: the data to send to the printer, as str.
"""
with open(self.device, "wt") as dev:
dev.write(data)
...
...
def sendfile(self, filename: str):
"""Sends a whole file to the printer.
:param filename: the path of the file to print.
"""
with open(filename, "rt") as f:
for l in f:
self.send(l.rstrip())
self.send(interpret_data("^r^l"))
...
...
self.send(interpret_data("^v"))
...
Para terminar... ¿se puede comprar hoy en día una impresora matricial? La respuesta es sí: desde impresoras "normales" hasta impresoras de tickets para comercios.
Continuará.
Top comments (0)