From 7583d820f4d32fbc2e247f3e7fc510265c111d3b Mon Sep 17 00:00:00 2001 From: Roman Priesol Date: Sat, 12 Sep 2026 16:55:38 +0200 Subject: [PATCH] Address Datamax via USB VID:PID/Serial through libusb, like brother_ql_web does --- Gemfile | 1 + app.rb | 32 ++++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 1108559..1d32f49 100644 --- a/Gemfile +++ b/Gemfile @@ -6,5 +6,6 @@ gem 'puma' gem 'rqrcode' gem 'logger' gem 'ostruct' +gem 'libusb' gem 'rackup', "~> 2.1" diff --git a/app.rb b/app.rb index f172452..eda09e7 100644 --- a/app.rb +++ b/app.rb @@ -3,6 +3,7 @@ require 'sinatra' require "sinatra/reloader" require 'tmpdir' require 'net/http' +require 'libusb' set :protection, :except => :frame_options set :environment, :production @@ -13,7 +14,14 @@ if !File.exist?('config.json') || !File.readable?('config.json') raise RuntimeException, 'Could not read config.json' end -PRINTER_DEVICE = '/dev/usb/lp_datamax' # stable udev symlink, see /etc/udev/rules.d/99-datamax.rules +# Addressed the same way as the Brother QL printers (usb://VID:PID/Serial via +# brother_ql_web) instead of a udev symlink -- self-contained, no reliance on +# OS device naming/ordering. +DATAMAX_VENDOR_ID = 0x0b0b +DATAMAX_PRODUCT_ID = 0x106e +DATAMAX_SERIAL = '24624' +DATAMAX_ENDPOINT = 0x02 # bulk OUT, interface 0 + PRINTER_DPI = 203 # DatamaxONeil E4204B Mark III native head resolution PRINTER_DARKNESS = 8 # lower than factory default to stop thermal dot-gain from fusing barcode bars TEST_LABEL_PDF = File.join(File.dirname(__FILE__), 'test-label.pdf') @@ -81,6 +89,22 @@ helpers do "^XA\n^MD#{PRINTER_DARKNESS}\n^PW#{width}\n^LL#{height}\n^FO0,0^GFA,#{total_bytes},#{total_bytes},#{bytes_per_row},#{hexdata}\n^XZ\n" end + def print_to_datamax(data) + usb = LIBUSB::Context.new + device = usb.devices(idVendor: DATAMAX_VENDOR_ID, idProduct: DATAMAX_PRODUCT_ID) + .find { |d| d.serial_number == DATAMAX_SERIAL } + unless device + raise format('Datamax printer not found (USB %04x:%04x/%s)', + DATAMAX_VENDOR_ID, DATAMAX_PRODUCT_ID, DATAMAX_SERIAL) + end + + device.open do |handle| + handle.claim_interface(0) do + handle.bulk_transfer(endpoint: DATAMAX_ENDPOINT, dataOut: data) + end + end + end + # Transparently reverse-proxies the current request to one of the # brother_ql_web instances, so all three printers are reachable through # this single sinatra port instead of their own dedicated ports. @@ -148,13 +172,13 @@ post '/upload' do when ".pdf" begin zpl = pdf_to_zpl(filepath) - File.open(PRINTER_DEVICE, 'wb') { |f| f.write(zpl) } + print_to_datamax(zpl) flash "PDF: Upload successful (#{filename})", "success" rescue => e flash "PDF: Failed to print (#{e.message})", "danger" end when ".zpl" - File.open(PRINTER_DEVICE, 'wb') { |f| f.write(File.binread(filepath)) } + print_to_datamax(File.binread(filepath)) flash "ZPL: Upload successful (#{filename})", "success" else flash "#{filetype} is not supported. Only .pdf and .zpl!", "false" @@ -171,7 +195,7 @@ end post '/test-print' do begin zpl = pdf_to_zpl(TEST_LABEL_PDF) - File.open(PRINTER_DEVICE, 'wb') { |f| f.write(zpl) } + print_to_datamax(zpl) flash 'Test label sent to printer', 'success' rescue => e flash "Test print failed (#{e.message})", 'danger'