🖨️ esc/pos implementation in gleam
at main 122 lines 3.4 kB view raw
1//// Functions for connecting to and communicating with ESC/POS printers. 2//// 3//// Supports both USB (device file) and network (TCP socket) connections. 4//// 5//// ## Example 6//// 7//// ```gleam 8//// // USB printer 9//// let assert Ok(printer) = printer.device("/dev/usb/lp0") 10//// 11//// // Network printer 12//// let assert Ok(printer) = printer.connect("192.168.1.100", 9100) 13//// 14//// escpos.new() 15//// |> escpos.writeln("Hello!") 16//// |> escpos.cut() 17//// |> printer.print(printer) 18//// 19//// // Close network printer socket 20//// printer.disconnect(printer) 21//// ``` 22 23import escpos/protocol 24import gleam/bit_array 25import gleam/result 26import mug 27import simplifile 28 29// Holds the constructed BitArray to be sent to the printer 30pub opaque type CommandBuffer { 31 CommandBuffer(data: BitArray) 32} 33 34/// A handle to a connected printer, either over USB or TCP. 35pub opaque type Printer { 36 NetworkPrinter(socket: mug.Socket) 37 UsbPrinter(device: String) 38} 39 40/// Errors that can occur when connecting to or printing with a printer. 41pub type PrinterError { 42 ConnectionFailed(mug.ConnectError) 43 DisconnectionFailed(mug.Error) 44 TransmissionError(mug.Error) 45 NetworkPrintError(mug.Error) 46 UsbPrintError(simplifile.FileError) 47 UsbDeviceError(simplifile.FileError) 48} 49 50/// Opens a USB printer by its device file path (e.g. `/dev/usb/lp0`) and writes 51/// the initialization command. 52pub fn device(path: String) -> Result(Printer, PrinterError) { 53 case simplifile.file_info(path) { 54 Ok(_) -> { 55 use _ <- result.try( 56 simplifile.write_bits(path, protocol.init) 57 |> result.map_error(UsbDeviceError), 58 ) 59 60 Ok(UsbPrinter(device: path)) 61 } 62 Error(err) -> Error(UsbDeviceError(err)) 63 } 64} 65 66/// Connects to a network printer over TCP and sends the initialization command. 67pub fn connect(ip: String, port: Int) -> Result(Printer, PrinterError) { 68 use socket <- result.try( 69 mug.new(ip, port) 70 |> mug.timeout(milliseconds: 1000) 71 |> mug.connect() 72 |> result.map_error(ConnectionFailed), 73 ) 74 75 use _ <- result.try( 76 mug.send(socket, protocol.init) 77 |> result.map_error(TransmissionError), 78 ) 79 80 Ok(NetworkPrinter(socket)) 81} 82 83/// Sends a command buffer to the printer. 84/// 85/// For network printers this writes to the TCP socket. For USB printers 86/// this writes directly to the device file. 87pub fn print(cb: CommandBuffer, printer: Printer) -> Result(Nil, PrinterError) { 88 case printer { 89 NetworkPrinter(socket:) -> 90 mug.send(socket, cb.data) 91 |> result.map_error(NetworkPrintError) 92 UsbPrinter(device:) -> 93 simplifile.write_bits(device, cb.data) 94 |> result.map_error(UsbPrintError) 95 } 96} 97 98/// Closes the connection to a network printer. For USB printers this is a no-op. 99pub fn disconnect(printer: Printer) -> Result(Nil, PrinterError) { 100 case printer { 101 NetworkPrinter(socket:) -> 102 mug.shutdown(socket) 103 |> result.map_error(DisconnectionFailed) 104 UsbPrinter(_) -> Ok(Nil) 105 } 106} 107 108// Creates an empty CommandBuffer 109pub fn new_buffer() -> CommandBuffer { 110 CommandBuffer(<<>>) 111} 112 113// Appends BitArray to CommandBuffer 114pub fn append_to_buffer(buffer: CommandBuffer, data: BitArray) -> CommandBuffer { 115 bit_array.append(buffer.data, data) 116 |> CommandBuffer 117} 118 119/// Returns the raw BitArray from CommandBuffer 120pub fn buffer_to_bits(buffer: CommandBuffer) -> BitArray { 121 buffer.data 122}