Printers

Printer status

Any printer asked how it is in its own language, Zebra, PJL, TSPL, ESC/POS or Brother, or by SNMP for toner levels, with one clear answer: ready, paper out, ribbon out, head open, paused or jammed.

Version 1.0.0 · 4 functions · 1 crate

#printing#status#monitoring#devices

Functions

What use printer_status.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use printer_status_read, printer_status from printer_status (Quillo 0.6).

printer_status_read(kind: str, reply_base64: str) → anyor an error

What a printer's status answer means, without asking: kind is "zebra", "pjl", "tspl", "escpos" or "brother", reply the bytes it sent, in base64. Gives { kind, ready, state: "ready" | "paper out" | "ribbon out" | "head open" | "paused" | "printing" | ..., problems: [...], details }.

printer_status(kind: str, host: str, timeout_ms: int) → anyor an error

Asks a printer on its raw port (host or host:port, 9100) how it is, in the language kind names: "zebra" (~HS), "pjl" (@PJL INFO STATUS, laser and office printers), "tspl" (ESC !?), "escpos" (DLE EOT 1 to 4) or "brother" (ESC i S). Gives one answer: { kind, ready, state, problems, details }. Ask in the printer's own language: another may print the question as text.

printer_snmp(host: str, community: str, timeout_ms: int) → anyor an error

A networked printer asked by SNMP v2c (UDP 161, community usually "public"), the Printer and Host Resources MIBs: { kind: "snmp", ready, state, problems (low paper, no paper, low toner, no toner, door open, jammed, offline, service requested...), details: { model, status, supplies: [{ name, level, max, percent }], pages } }.

printer_snmp_request(community: str, oid: str, request_id: int) → stror an error

The bytes of an SNMP v2c GETNEXT request for oid (dotted, like 1.3.6.1.2.1.43.11.1.1.9), in hex: to check the encoding against a capture.

Get it

$ quillo install printer_status               # printer_status.qwer into this folder$ quillo install printer_status --example     # and its example, printer_status.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/printer_status.qwer$ curl -O https://quillolang.org/plugins/files/printer_status.qwe

Then, in your program, all of it or only what you call:

main.qwe
use printer_status.qweruse printer_status_read, printer_status from printer_status      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
base64 = "0.22"

Example

printer_status.qwe
// A dashboard of a warehouse's printers: each asked how it is in its own language (a Zebra// label printer, an HP laser printer, a TSC printer), with one answer each; replies from a// receipt printer and a Brother QL read as they come; and the SNMP question for toner levels.////   PRINTER_HOST=192.168.1.60 quillo run printer_status.qwe      then open http://localhost:8080/status//   quillo run printer_status.qwe      and http://localhost:8080/repliesuse "printer_status.qwer"route GET /status {    let host = env("PRINTER_HOST", null) ?? reply 503 "set PRINTER_HOST to the printer's address (host or host:port)"    {        zebra: printer_status("zebra", host, 2000)?,        laser: printer_status("pjl", host, 2000)?.state,        tsc: printer_status("tspl", host, 2000)?.ready,    }}// Answers as printers send them (in base64): a Zebra out of paper with its head open, a receipt// printer at the end of its roll, a Brother QL with its cover open.route GET /replies = {    zebra: printer_status_read("zebra", "AjAzMCwxLDAsMTI0NSwwMDAsMCwwLDAsMDAwLDAsMCwwAw0KAjAwMCwwLDEsMSwwLDIsNCwwLDAwMDAwMDAwLDEsMDAwAw0KAjEyMzQsMAMNCg==")?,    receipt: printer_status_read("escpos", "FhISfg==")?.state,    brother: printer_status_read("brother", "gCBCNEEAAAAAED5KAAAAAAAAAAAAAAAAAAAAAAAAAAA=")?.problems,    snmp_request: printer_snmp_request("public", "1.3.6.1.2.1.43.11.1.1.9", 1001)?,}// A real printer's toner: PRINTER_SNMP=192.168.1.70 quillo run printer_status.qweroute GET /toner {    let host = env("PRINTER_SNMP", null) ?? reply 503 "set PRINTER_SNMP to a printer's address"    printer_snmp(host, "public", 2000)?}
$ quillo run printer_status.qwe$ curl 'localhost:8080/status'$ curl 'localhost:8080/replies'

It talks to the Redis that REDIS_URL names, else Redis on this machine. The site's test runs it against a stand-in.

Source

printer_status.qwer
//! A printer asked how it is, in its own language, and one clear answer back: Zebra ZPL (~HS: paper out, ribbon out, head open, paused), HP and office printers (PJL INFO STATUS codes), TSC TSPL (ESC !?), ESC/POS receipt printers (DLE EOT), Brother QL and P-touch (ESC i S), and any networked printer by SNMP (the Printer MIB: its state, the errors it detects and toner or ink levels).//! version: 1.0.0//!//! [dependencies]//! base64 = "0.22"use serde_json::{json, Value};use std::io::{Read, Write};use std::net::{SocketAddr, TcpStream, ToSocketAddrs, UdpSocket};use std::time::{Duration, Instant};fn addr(host: &str, port: u16) -> Result<SocketAddr, String> {    let h = host.trim();    let with_port = if h.starts_with('[') {        if h.contains("]:") { h.to_string() } else { format!("{}:{}", h, port) }    } else if h.matches(':').count() == 1 {        h.to_string()    } else if h.contains(':') {        format!("[{}]:{}", h, port)    } else {        format!("{}:{}", h, port)    };    with_port.to_socket_addrs().map_err(|e| format!("{}: {}", host, e))?.next().ok_or(format!("{} has no address", host))}/// Writes `ask` and reads until `done` says the answer is whole, or the time is up.fn ask(host: &str, ask: &[u8], timeout_ms: i64, done: &dyn Fn(&[u8]) -> bool) -> Result<Vec<u8>, String> {    let t = Duration::from_millis(timeout_ms.clamp(100, 60_000) as u64);    let a = addr(host, 9100)?;    let mut s = TcpStream::connect_timeout(&a, t).map_err(|e| format!("cannot reach the printer at {}: {}", a, e))?;    s.set_write_timeout(Some(t)).map_err(|e| e.to_string())?;    s.write_all(ask).map_err(|e| e.to_string())?;    let until = Instant::now() + t;    let _ = s.set_read_timeout(Some(Duration::from_millis(100)));    let mut out = vec![];    let mut buf = [0u8; 1024];    while Instant::now() < until && !done(&out) {        match s.read(&mut buf) {            Ok(0) => break,            Ok(n) => out.extend_from_slice(&buf[..n]),            Err(_) => {}        }    }    if out.is_empty() {        return Err(format!("the printer at {} did not answer", a));    }    Ok(out)}fn answer(kind: &str, problems: Vec<String>, busy: Option<&str>, details: Value) -> Value {    let state = if let Some(p) = problems.first() { p.clone() } else if let Some(b) = busy { b.to_string() } else { "ready".to_string() };    json!({ "kind": kind, "ready": problems.is_empty(), "state": state, "problems": problems, "details": details })}/// Zebra's three host status lines (~HS) read.fn zebra(raw: &[u8]) -> Result<Value, String> {    let text = String::from_utf8_lossy(raw);    let lines: Vec<Vec<String>> = text.split('\x02').skip(1).map(|l| l.split('\x03').next().unwrap_or("").split(',').map(|s| s.trim().to_string()).collect()).collect();    if lines.len() < 2 || lines[0].len() < 12 || lines[1].len() < 9 {        return Err(format!("not a Zebra ~HS answer: {:?}", text));    }    let f = |l: usize, i: usize| lines[l][i] == "1";    let mut p = vec![];    for (on, what) in [(f(0, 1), "paper out"), (f(1, 3), "ribbon out"), (f(1, 2), "head open"), (f(0, 2), "paused"), (f(0, 5), "buffer full"), (f(0, 11), "head too hot"), (f(0, 10), "head too cold"), (f(0, 9), "memory corrupt")] {        if on {            p.push(what.to_string());        }    }    let waiting: i64 = lines[0][4].parse().unwrap_or(0);    let remaining: i64 = lines[1][8].parse().unwrap_or(0);    let busy = if remaining > 0 || waiting > 0 { Some("printing") } else { None };    Ok(answer("zebra", p, busy, json!({ "formats_waiting": waiting, "labels_remaining": remaining, "label_length_dots": lines[0][3].parse::<i64>().unwrap_or(0), "thermal_transfer": f(1, 4) })))}fn pjl(raw: &[u8]) -> Result<Value, String> {    let text = String::from_utf8_lossy(raw);    let mut code = None;    let mut display = Value::Null;    let mut online = None;    for l in text.split(|c| c == '\r' || c == '\n' || c == '\x0c') {        if let Some((k, v)) = l.split_once('=') {            let v = v.trim().trim_matches('"');            match k.trim().to_uppercase().as_str() {                "CODE" => code = v.parse::<i64>().ok(),                "DISPLAY" => display = json!(v),                "ONLINE" => online = Some(v.eq_ignore_ascii_case("TRUE")),                _ => {}            }        }    }    let code = code.ok_or_else(|| format!("not a PJL status answer: {:?}", text))?;    let (problem, busy): (Option<String>, Option<&str>) = match code {        10001 | 35078 => (None, None),        10003 | 10024 => (None, Some("warming up")),        10023 | 10026 | 10007 | 10005 | 10004 | 10013..=10022 | 11000..=11999 => (None, Some("printing")),        10006 => (None, Some("toner low")),        10002 | 40079 => (Some("offline".into()), None),        40021 => (Some("door open".into()), None),        40022 | 42000..=42999 | 44000..=44999 => (Some("paper jam".into()), None),        40038 => (Some("toner low, replace soon".into()), None),        41000..=41999 => (Some("paper out".into()), None),        40000..=40999 | 43000..=43999 => (Some(format!("needs attention (code {})", code)), None),        50000..=59999 => (Some(format!("hardware error (code {})", code)), None),        12000..=35999 => (None, Some("warning")),        _ => (None, Some("unknown state")),    };    let mut p: Vec<String> = problem.into_iter().collect();    if online == Some(false) && p.is_empty() {        p.push("offline".into());    }    Ok(answer("pjl", p, busy, json!({ "code": code, "display": display, "online": online })))}fn tspl(raw: &[u8]) -> Result<Value, String> {    let c = *raw.first().ok_or("no TSPL status byte")?;    let mut p = vec![];    for (bit, what) in [(0x01, "head open"), (0x02, "paper jam"), (0x04, "paper out"), (0x08, "ribbon out"), (0x10, "paused"), (0x80, "other error")] {        if c & bit != 0 {            p.push(what.to_string());        }    }    Ok(answer("tspl", p, if c & 0x20 != 0 { Some("printing") } else { None }, json!({ "code": c })))}fn escpos(raw: &[u8]) -> Result<Value, String> {    if raw.len() < 4 || raw.iter().take(4).any(|b| b & 0x93 != 0x12) {        return Err(format!("not four ESC/POS status bytes: {:?}", raw));    }    let mut p = vec![];    for (on, what) in [        (raw[1] & 0x20 != 0 || raw[3] & 0x60 == 0x60, "paper out"),        (raw[1] & 0x04 != 0, "cover open"),        (raw[2] & 0x08 != 0, "cutter error"),        (raw[2] & 0x20 != 0, "unrecoverable error"),        (raw[2] & 0x40 != 0, "head too hot"),        (raw[0] & 0x08 != 0 && raw[1] & 0x24 == 0, "offline"),    ] {        if on {            p.push(what.to_string());        }    }    let near_end = raw[3] & 0x0c == 0x0c;    Ok(answer("escpos", p, if near_end { Some("paper near its end") } else { None }, json!({ "paper_near_end": near_end, "drawer_open": raw[0] & 0x04 == 0 })))}fn brother(raw: &[u8]) -> Result<Value, String> {    let s = raw;    if s.len() < 32 || s[0] != 0x80 || s[1] != 0x20 {        return Err("not a 32-byte Brother status".into());    }    let mut p = vec![];    for (byte, bit, what) in [(8, 0x01, "no media"), (8, 0x02, "end of media"), (8, 0x04, "cutter jam"), (8, 0x80, "fan error"), (9, 0x01, "wrong media"), (9, 0x10, "cover open"), (9, 0x40, "media cannot be fed"), (9, 0x80, "system error"), (9, 0x04, "communication error")] {        if s[byte] & bit != 0 {            p.push(what.to_string());        }    }    let busy = if s[8] & 0x10 != 0 { Some("printing") } else { None };    Ok(answer("brother", p, busy, json!({ "media_width_mm": s[10], "media_length_mm": s[17] })))}/// What a printer's status answer means, without asking: `kind` is "zebra", "pjl", "tspl", "escpos" or "brother", `reply` the bytes it sent, in base64./// Gives `{ kind, ready, state: "ready" | "paper out" | "ribbon out" | "head open" | "paused" | "printing" | ..., problems: [...], details }`.pub fn printer_status_read(kind: &str, reply_base64: &str) -> Result<serde_json::Value, String> {    use base64::Engine;    let raw = base64::engine::general_purpose::STANDARD.decode(reply_base64.trim()).map_err(|e| format!("not base64: {}", e))?;    read(kind, &raw)}fn read(kind: &str, raw: &[u8]) -> Result<Value, String> {    match kind {        "zebra" => zebra(raw),        "pjl" => pjl(raw),        "tspl" => tspl(raw),        "escpos" => escpos(raw),        "brother" => brother(raw),        other => Err(format!("kind {:?}: zebra, pjl, tspl, escpos or brother", other)),    }}/// Asks a printer on its raw port (`host` or `host:port`, 9100) how it is, in the language `kind` names: "zebra" (~HS), "pjl" (@PJL INFO STATUS, laser and office printers), "tspl" (ESC !?), "escpos" (DLE EOT 1 to 4) or "brother" (ESC i S)./// Gives one answer: `{ kind, ready, state, problems, details }`. Ask in the printer's own language: another may print the question as text.pub fn printer_status(kind: &str, host: &str, timeout_ms: i64) -> Result<serde_json::Value, String> {    let raw = match kind {        "zebra" => ask(host, b"~HS\r\n", timeout_ms, &|b| b.iter().filter(|c| **c == 0x03).count() >= 3)?,        "pjl" => ask(host, b"\x1b%-12345X@PJL\r\n@PJL INFO STATUS\r\n\x1b%-12345X", timeout_ms, &|b| b.contains(&0x0c))?,        "tspl" => ask(host, b"\x1b!?", timeout_ms, &|b| !b.is_empty())?,        "escpos" => ask(host, &[0x10, 0x04, 1, 0x10, 0x04, 2, 0x10, 0x04, 3, 0x10, 0x04, 4], timeout_ms, &|b| b.len() >= 4)?,        "brother" => {            let mut q = vec![0u8; 100];            q.extend_from_slice(&[0x1b, b'@', 0x1b, b'i', b'S']);            ask(host, &q, timeout_ms, &|b| b.len() >= 32)?        }        other => return Err(format!("kind {:?}: zebra, pjl, tspl, escpos or brother", other)),    };    read(kind, &raw)}// ---- SNMP v2c, enough for the Printer MIB: GETNEXT walks of a few columns.fn ber_len(n: usize, out: &mut Vec<u8>) {    if n < 128 {        out.push(n as u8);    } else if n < 256 {        out.extend_from_slice(&[0x81, n as u8]);    } else {        out.extend_from_slice(&[0x82, (n >> 8) as u8, n as u8]);    }}fn tlv(tag: u8, body: &[u8]) -> Vec<u8> {    let mut v = vec![tag];    ber_len(body.len(), &mut v);    v.extend_from_slice(body);    v}fn ber_int(x: i64) -> Vec<u8> {    let mut b = x.to_be_bytes().to_vec();    while b.len() > 1 && ((b[0] == 0 && b[1] & 0x80 == 0) || (b[0] == 0xff && b[1] & 0x80 != 0)) {        b.remove(0);    }    tlv(0x02, &b)}fn ber_oid(oid: &[u32]) -> Vec<u8> {    let mut b = vec![(oid[0] * 40 + oid[1]) as u8];    for &n in &oid[2..] {        let mut stack = vec![(n & 0x7f) as u8];        let mut v = n >> 7;        while v > 0 {            stack.push((v & 0x7f) as u8 | 0x80);            v >>= 7;        }        stack.reverse();        b.extend_from_slice(&stack);    }    tlv(0x06, &b)}fn get_next(community: &str, id: i64, oid: &[u32]) -> Vec<u8> {    let vb = tlv(0x30, &[ber_oid(oid), vec![0x05, 0x00]].concat());    let pdu = tlv(0xa1, &[ber_int(id), ber_int(0), ber_int(0), tlv(0x30, &vb)].concat());    tlv(0x30, &[ber_int(1), tlv(0x04, community.as_bytes()), pdu].concat())}/// Reads one TLV at `i`: (tag, body start, body end).fn read_tlv(b: &[u8], i: usize) -> Option<(u8, usize, usize)> {    let tag = *b.get(i)?;    let l0 = *b.get(i + 1)? as usize;    let (len, start) = if l0 < 0x80 {        (l0, i + 2)    } else {        let n = l0 & 0x7f;        let mut len = 0usize;        for k in 0..n {            len = (len << 8) | *b.get(i + 2 + k)? as usize;        }        (len, i + 2 + n)    };    if start + len > b.len() {        return None;    }    Some((tag, start, start + len))}/// The OID and value of a GetResponse's first variable binding.fn parse_response(b: &[u8]) -> Option<(Vec<u32>, Value)> {    let (_, s, _) = read_tlv(b, 0)?; // message    let (_, _, e1) = read_tlv(b, s)?; // version    let (_, _, e2) = read_tlv(b, e1)?; // community    let (tag, ps, _) = read_tlv(b, e2)?; // PDU    if tag != 0xa2 {        return None;    }    let (_, _, a) = read_tlv(b, ps)?; // request id    let (_, es, ee) = read_tlv(b, a)?; // error status    if b[es..ee].iter().any(|x| *x != 0) {        return None;    }    let (_, _, c) = read_tlv(b, ee)?; // error index    let (_, ls, _) = read_tlv(b, c)?; // binding list    let (_, vs, _) = read_tlv(b, ls)?; // binding    let (_, os, oe) = read_tlv(b, vs)?;    let ob = &b[os..oe];    let mut oid = vec![(ob[0] / 40) as u32, (ob[0] % 40) as u32];    let mut n = 0u32;    for &x in &ob[1..] {        n = (n << 7) | (x & 0x7f) as u32;        if x & 0x80 == 0 {            oid.push(n);            n = 0;        }    }    let (vt, vs2, ve) = read_tlv(b, oe)?;    let raw = &b[vs2..ve];    let v = match vt {        0x02 | 0x41 | 0x42 | 0x43 => {            let mut x: i64 = if vt == 0x02 && raw.first().map_or(false, |f| f & 0x80 != 0) { -1 } else { 0 };            for &y in raw {                x = (x << 8) | y as i64;            }            json!(x)        }        0x04 => {            if raw.iter().all(|c| *c >= 0x20 && *c < 0x7f || *c == b'\n' || *c == b'\r') {                json!(String::from_utf8_lossy(raw).trim_end_matches('\0').to_string())            } else {                json!(raw.iter().map(|c| format!("{:02x}", c)).collect::<String>())            }        }        _ => Value::Null,    };    Some((oid, v))}/// Walks a column of the MIB: each row's last OID number and its value.fn walk(sock: &UdpSocket, community: &str, base: &[u32], id: &mut i64) -> Result<Vec<(u32, Value)>, String> {    let mut out = vec![];    let mut cur = base.to_vec();    for _ in 0..64 {        *id += 1;        sock.send(&get_next(community, *id, &cur)).map_err(|e| e.to_string())?;        let mut buf = [0u8; 2048];        let n = sock.recv(&mut buf).map_err(|e| format!("no SNMP answer: {}", e))?;        let Some((oid, v)) = parse_response(&buf[..n]) else { break };        if !oid.starts_with(base) || oid.len() <= base.len() {            break;        }        out.push((*oid.last().unwrap(), v));        cur = oid;    }    Ok(out)}/// A networked printer asked by SNMP v2c (UDP 161, `community` usually "public"), the Printer and Host Resources MIBs:/// `{ kind: "snmp", ready, state, problems (low paper, no paper, low toner, no toner, door open, jammed, offline, service requested...), details: { model, status, supplies: [{ name, level, max, percent }], pages } }`.pub fn printer_snmp(host: &str, community: &str, timeout_ms: i64) -> Result<serde_json::Value, String> {    let a = addr(host, 161)?;    let sock = UdpSocket::bind(if a.is_ipv4() { "0.0.0.0:0" } else { "[::]:0" }).map_err(|e| e.to_string())?;    sock.set_read_timeout(Some(Duration::from_millis(timeout_ms.clamp(100, 30_000) as u64))).map_err(|e| e.to_string())?;    sock.connect(a).map_err(|e| e.to_string())?;    let mut id = 1000;    let first = |v: Vec<(u32, Value)>| v.into_iter().next().map(|x| x.1).unwrap_or(Value::Null);    let model = first(walk(&sock, community, &[1, 3, 6, 1, 2, 1, 25, 3, 2, 1, 3], &mut id)?);    let status = first(walk(&sock, community, &[1, 3, 6, 1, 2, 1, 25, 3, 5, 1, 1], &mut id)?);    let errors = first(walk(&sock, community, &[1, 3, 6, 1, 2, 1, 25, 3, 5, 1, 2], &mut id)?);    let pages = first(walk(&sock, community, &[1, 3, 6, 1, 2, 1, 43, 10, 2, 1, 4], &mut id)?);    let names = walk(&sock, community, &[1, 3, 6, 1, 2, 1, 43, 11, 1, 1, 6], &mut id)?;    let maxes = walk(&sock, community, &[1, 3, 6, 1, 2, 1, 43, 11, 1, 1, 8], &mut id)?;    let levels = walk(&sock, community, &[1, 3, 6, 1, 2, 1, 43, 11, 1, 1, 9], &mut id)?;    let mut supplies = vec![];    for (k, name) in &names {        let max = maxes.iter().find(|m| m.0 == *k).and_then(|m| m.1.as_i64()).unwrap_or(-1);        let level = levels.iter().find(|m| m.0 == *k).and_then(|m| m.1.as_i64()).unwrap_or(-1);        // -2 is "unknown", -3 "some left"; a percent only when both are real.        let pct = if max > 0 && level >= 0 { json!((level * 100 / max).min(100)) } else { Value::Null };        supplies.push(json!({ "name": name, "level": level, "max": max, "percent": pct }));    }    // hrPrinterDetectedErrorState: a bit string, the first bit the high bit of the first byte.    let bits: Vec<u8> = errors.as_str().map(|h| (0..h.len() / 2).filter_map(|i| u8::from_str_radix(&h[i * 2..i * 2 + 2], 16).ok()).collect()).unwrap_or_else(|| errors.as_str().map(|s| s.bytes().collect()).unwrap_or_default());    let names_of_bits = ["low paper", "no paper", "low toner", "no toner", "door open", "jammed", "offline", "service requested", "input tray missing", "output tray missing", "supply missing", "output nearly full", "output full", "input tray empty", "maintenance overdue"];    let mut problems = vec![];    for (i, n) in names_of_bits.iter().enumerate() {        if bits.get(i / 8).map_or(false, |b| b & (0x80 >> (i % 8)) != 0) && !matches!(*n, "low paper" | "low toner" | "output nearly full" | "maintenance overdue") {            problems.push(n.to_string());        }    }    let st = match status.as_i64() {        Some(3) => None,        Some(4) => Some("printing"),        Some(5) => Some("warming up"),        Some(1) | Some(2) => Some("unknown state"),        _ => None,    };    for s in &supplies {        if s["percent"].as_i64().map_or(false, |p| p == 0) {            problems.push(format!("{} empty", s["name"].as_str().unwrap_or("a supply")));        }    }    let mut ans = answer("snmp", problems, st, json!({ "model": model, "status": status, "supplies": supplies, "pages": pages }));    let low: Vec<String> = names_of_bits.iter().enumerate().filter(|(i, n)| bits.get(i / 8).map_or(false, |b| b & (0x80 >> (i % 8)) != 0) && matches!(**n, "low paper" | "low toner" | "output nearly full" | "maintenance overdue")).map(|(_, n)| n.to_string()).collect();    ans["details"]["warnings"] = json!(low);    Ok(ans)}/// The bytes of an SNMP v2c GETNEXT request for `oid` (dotted, like `1.3.6.1.2.1.43.11.1.1.9`), in hex: to check the encoding against a capture.pub fn printer_snmp_request(community: &str, oid: &str, request_id: i64) -> Result<String, String> {    let o: Vec<u32> = oid.trim_start_matches('.').split('.').map(|p| p.parse::<u32>().map_err(|_| format!("{:?} is not an OID", oid))).collect::<Result<_, _>>()?;    if o.len() < 2 {        return Err("an OID has at least two numbers".into());    }    Ok(get_next(community, request_id, &o).iter().map(|b| format!("{:02x}", b)).collect())}
PJL print jobs and status pjl.qwer · 1.0.0PDF, PostScript and PCL files printed on office printers without a driver, wrapped in PJL with copies, duplex, tray, held jobs released by PIN; and the printer asked for its status, model and page count, its codes told in words.printingpdfstatusdevicesmonitoring6 views · 7 downloadsBrother QL and P-touch ptouch.qwer · 1.0.0Brother QL label printers and P-touch tape printers in raster mode: text made into a label picture, any picture made into the raster job for a DK roll or TZe tape, cut, compressed, sent over the network, and the printer's status read.printinglabelsimagesdevicesaddresses7 views · 7 downloadsDatamax DPL labels dpl.qwer · 1.0.0Honeywell and Datamax-O'Neil DPL labels from fields placed in millimetres from the top left: resident fonts, Code 128, EAN-13, QR and Data Matrix, lines and boxes, heat, speed and quantity, sent to port 9100.printinglabelsbarcodesdevices7 views · 9 downloadsEPL2 labels epl.qwer · 1.0.0Zebra and Eltron EPL2 labels from fields in millimetres: text in the resident fonts, Code 128, EAN-13, Code 39 and QR barcodes, lines, boxes and pictures, with label size, gap, speed and copies, sent to port 9100.printinglabelsbarcodesdevices6 views · 5 downloadsEpson ESC/P dot-matrix escp.qwer · 1.0.0Epson ESC/P and ESC/P2 for dot-matrix printers: pre-printed and multipart forms filled by line and column, reports with bold, double-width and condensed text, pitch, line spacing and page length, in code page 437 with box-drawing lines.printingformsinvoicesreportsdevices7 views · 11 downloadsGodex EZPL labels ezpl.qwer · 1.0.0Godex EZPL labels for G500, RT200 and other Godex printers: text in the resident fonts, Code 128, EAN-13, Code 39 and QR codes, lines and boxes, label size, darkness, speed and copies, sent to port 9100.printinglabelsbarcodesdevices6 views · 8 downloads
Thread
97Useful? Like it, or add to its thread below.

Thread 0 posts

On Printer status: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.