码禾数字
← กลับไป MhQuickPrint

/quickprint · คู่มือ

MhQuickPrint · คู่มือ

คู่มือนี้สำหรับนักพัฒนาที่เชื่อมต่อ MhQuickPrint เข้ากับระบบธุรกิจ: เชื่อมอย่างไร ส่งอะไร เรนเดอร์หน้าอย่างไร จัดการงานและข้อผิดพลาดอย่างไร ครบในฉบับเดียว ผู้ใช้ทั่วไปอ่านหัวข้อ 1 ก็พอ

01เริ่มใช้เร็ว (ผู้ใช้ทั่วไป)

  1. 01

    ดาวน์โหลดแล้วรัน

    รับไฟล์ exe เดียว (ราว 1.3 MB) จากหน้าดาวน์โหลด วางในโฟลเดอร์ถาวรแล้วดับเบิลคลิก ไอคอนในถาดระบบแปลว่าบริการพร้อมแล้ว โปรแกรมไม่มีหน้าต่างหลัก คลิกซ้ายดูสถานะ คลิกขวาเปิดเมนูเต็ม

  2. 02

    พรีวิวและพิมพ์

    เมื่อระบบสั่งพิมพ์ หน้าต่างพรีวิวเนทีฟจะเปิดขึ้น: เปลี่ยนหน้า ซูม เปลี่ยนเครื่องพิมพ์ เลือกกระดาษ ตั้งจำนวนสำเนาและขอบ กดพิมพ์แล้วออกทันที

  3. 03

    ออก

    คลิกขวาไอคอนถาดระบบ → "หยุดบริการและออก" (มีหน้าต่างยืนยัน) รันพร้อมกันได้ครั้งละหนึ่งอินสแตนซ์

หน้าต่างสถานะบริการ
หน้าต่างสถานะบริการ
ยืนยันการออกจากถาดระบบ
ยืนยันการออกจากถาดระบบ

02สถาปัตยกรรมและหลักการ

โปรแกรมโฮสต์เป็นผู้ตัดสินว่า "จะพิมพ์อะไร": จัดหน้าเอกสาร ป้าย หรือสลิปเป็นภาพหน้าเต็ม (PNG) ขนาดคำนวณตาม DPI ของเครื่องพิมพ์เป้าหมาย

MhQuickPrint ทำหน้าที่ "ส่งถึงเครื่องพิมพ์อย่างแม่นยำ": ภาพเดียวกันใช้ทั้งแสดงพรีวิวและสั่งพิมพ์แบบ 1:1 ไม่มีการย่อขยายซ้ำ พรีวิวเป็นอย่างไร ผลลัพธ์เป็นอย่างนั้น

ทั้งสองฝั่งคุยกันผ่าน named pipe ของ Windows ซึ่งเป็นช่องทางภายในเครื่อง ไม่ผ่านเครือข่าย ไม่เปิดพอร์ต มีเฉพาะโปรแกรมของผู้ใช้ปัจจุบันที่เชื่อมได้ หน้าเว็บเชื่อมตรงไม่ได้และไม่จำเป็น — ระบบเว็บให้แบ็กเอนด์หรือไคลเอนต์ท้องถิ่น (เช่น main process ของ Electron) เป็นไคลเอนต์ของ pipe แทน

03ภาพรวมการเชื่อมต่อ: 5 ขั้น

  1. 01

    เช็กบริการ

    เชื่อม named pipe แล้วส่ง system.hello ถ้าได้ serverVersion ตอบมาแปลว่าบริการออนไลน์

  2. 02

    รวบรวมเครื่องพิมพ์

    printers.list คืนรายการเครื่องพิมพ์และสถานะคิว เลือก printerId เป้าหมาย

  3. 03

    สอบถามความสามารถ

    printers.capabilities คืน dpiX / dpiY ขนาดกระดาษ (ไมโครเมตร) พื้นที่พิมพ์ได้ และ capabilityHash

  4. 04

    เรนเดอร์หน้า

    เรนเดอร์หน้าเป็น PNG เต็มหน้าที่ DPI เป้าหมาย: จุด = มม. × DPI ÷ 25.4 (ปัดเศษ)

  5. 05

    พรีวิวหรือพิมพ์

    jobs.preview เปิดหน้าต่างพรีวิวเนทีฟ jobs.print ปรินต์เงียบ ติดตามด้วย jobs.status และยกเลิกด้วย jobs.cancel

04ช่องทางสื่อสารและขีดจำกัด

ชื่อ pipe ตายตัว: \\.\pipe\LOCAL\MhQuickPrint.v1 จำนวนเต็มเป็น unsigned little-endian ข้อความเป็น UTF-8 JSON สิทธิ์ pipe (DACL) อนุญาตเฉพาะผู้ใช้อินเทอร์แอกทีฟปัจจุบัน กลุ่มผู้ดูแล และ SYSTEM ปฏิเสธ network logon ทุกกรณี ทุกคำขอมี callerId และ timestamp กำกับ

ข้อจำกัดค่า
เอกสารควบคุม (JSON)≤ 1 MiB
blob ต่อหน้า≤ 32 MiB
หน้าต่องาน≤ 200
blob รวมต่องาน≤ 256 MiB
คำขอพร้อมกัน≤ 4
ความคลาดเคลื่อนเวลา≤ 5 นาที (UTC ท้องถิ่น)

05รูปแบบเฟรม

แต่ละคำขอ/คำตอบเป็นหนึ่งเฟรม: เอกสารควบคุม JSON อยู่หน้า ตามด้วยข้อมูลหน้า (PNG) เป็น blob ความยาวประกาศในเฮดเดอร์ทั้งหมด ไม่มี Base64

ผังเฟรม (little-endian)
uint32  jsonLength        // 控制文档长度
byte[]  json              // UTF-8 JSON
uint32  blobCount         // 附带二进制页数量
repeat blobCount 次:
  uint64  blobLength      // 该页 PNG 字节数
  byte[]  blob            // 整页 PNG 点阵
ฟิลด์ร่วมของคำขอ
{
  "protocol": 1,
  "requestId": "123e4567-e89b-12d3-a456-426614174000",
  "callerId": "LabelTrunk",
  "timestampUtcMs": 1700000000000,
  "command": "system.health"
}
ฟิลด์ชนิดความหมาย
protocolintเวอร์ชันโปรโตคอล ปัจจุบันคือ 1
requestIduuidเชื่อมโยงคำขอ–คำตอบ ส่งกลับเหมือนเดิม
callerIdstringชื่อระบบผู้เรียก
timestampUtcMslongเวลา UTC ท้องถิ่นเป็นมิลลิวินาที (±5 นาที)
commandstringชื่อคำสั่ง ดูหัวข้อถัดไป

06คำสั่งทั้งหมด

ภาพรวมคำสั่ง (blob ที่ต้องส่งและหน้าที่):

ตัวอย่างคำขอ jobs.print ฉบับเต็ม
{
  "protocol": 1,
  "requestId": "123e4567-e89b-12d3-a456-426614174000",
  "callerId": "LabelTrunk",
  "timestampUtcMs": 1700000000000,
  "command": "jobs.print",
  "job": {
    "clientJobId": "123e4567-e89b-12d3-a456-426614174001",
    "name": "ENTRY-TEMPLATE",
    "printerId": "stable-printer-id",
    "copies": 1,
    "paper": { "widthMm": 100.0, "heightMm": 60.0 },
    "device": { "dpiX": 203, "dpiY": 203, "capabilityHash": "sha256" },
    "pages": [
      { "widthDots": 799, "heightDots": 480, "format": "png", "blobIndex": 0 }
    ]
  }
}
คำตอบ (ตัวอย่างข้อผิดพลาด)
{
  "protocol": 1,
  "requestId": "123e4567-e89b-12d3-a456-426614174000",
  "ok": false,
  "error": {
    "code": "MQP-3002",
    "message": "页面与二进制数据不匹配"
  }
}
คำสั่งblobหน้าที่
system.hello0จับมือ: ตกลงโปรโตคอลและเวอร์ชัน
system.health0สถานะการทำงาน สิทธิ์ และการอัปเดต
printers.list0รายการเครื่องพิมพ์และสถานะคิว
printers.capabilities0DPI กระดาษ พื้นที่พิมพ์ และแฮชความสามารถ
jobs.previewหน้าละหนึ่งเปิดพรีวิวเนทีฟด้วยข้อมูลหน้า
jobs.printหน้าละหนึ่งตรวจแล้วส่งงานพิมพ์เงียบ
jobs.status0อ่านสถานะงานปัจจุบัน
jobs.cancel0ขอยกเลิกและคืนสถานะที่แท้จริง
settings.getCalibration0อ่านค่าสอบเทียบ (ยังไม่เปิดใน 0.1.0 คืน MQP-1005)
settings.openCalibration0เปิดหน้าจอสอบเทียบ (ยังไม่เปิดใน 0.1.0)
ฟิลด์ของ jobชนิดความหมาย
clientJobIdstringรหัสงานเฉพาะของธุรกิจ: เหมือนกันทั้งหมดคืนงานเดิม เหมือน id ต่างเนื้อหาคืน MQP-4002 ไม่พิมพ์ซ้ำ
namestringชื่องานเพื่อสะดวกจำ
printerIdstringรหัสเครื่องพิมพ์เป้าหมาย (จาก printers.list)
copiesintจำนวนสำเนา 1–999
paper.widthMm / heightMmnumberกว้าง/สูงของกระดาษเป็นมิลลิเมตร (หน่วยหลัก)
device.dpiX / dpiYintDPI ที่ใช้เรนเดอร์ ต้องตรงกับความสามารถเครื่องพิมพ์
device.capabilityHashstringแฮชความสามารถ ถ้าเปลี่ยนงานจะถูกปฏิเสธเพื่อเรนเดอร์ใหม่
pages[].widthDots / heightDotsintขนาดหน้าเป็นจุด = มม. × DPI ÷ 25.4 ปัดเศษ
pages[].formatstringรูปแบบหน้า ปัจจุบันรับเฉพาะ "png"
pages[].blobIndexintดัชนี blob ในเฟรม ต้องไม่ซ้ำและครบทุก blob

07กฎการเรนเดอร์หน้า

การจัดหน้าเป็นของผู้เรียก: บาร์โค้ด QR ตาราง ข้อความ วาดลง PNG เต็มหน้าโดยเอนจินของคุณ MhQuickPrint ทำสองอย่าง — แสดงพรีวิวและพิมพ์ 1:1 ไม่ย่อขยายซ้ำ

สูตรขนาด (ปัดเป็นจำนวนเต็ม):

widthDots  = Math.round(widthMm  * dpiX / 25.4)
heightDots = Math.round(heightMm * dpiY / 25.4)

// 例:100 × 60 mm 热敏纸 @203 DPI
// widthDots  = Math.round(100 * 203 / 25.4) = 799
// heightDots = Math.round(60  * 203 / 25.4) = 480

กฎการตรวจ: ขนาดจุดต้องตรงกับ มม. × DPI เป๊ะ ไม่งั้นงานถูกปฏิเสธ (กันพิมพ์เพี้ยน) เปลี่ยนเครื่องที่ DPI ต่างกันต้องเรนเดอร์ใหม่ตามค่าใหม่ก่อนส่ง

08สถานะงานและการกันซ้ำ

สถานะงาน: Created → Previewing / Validating → Spooling → Queued → Printing → Completed / Failed / Cancelled ความหมายตรงไปตรงมา: Queued คือ Windows รับแล้ว Completed คือออกจากคิวโดยไม่มีข้อผิดพลาดที่ทราบ ไม่อ้างว่า "กระดาษออกแล้ว"

กันพิมพ์ซ้ำ: ส่งพร้อม clientJobId ที่เป็นเอกลักษณ์ของธุรกิจ ลองใหม่หรือกดรัวก็ได้งานเดิมเดียวกัน ถ้าใช้ id เดิมแต่เนื้อหาต่างจะได้ MQP-4002 ไม่พิมพ์สองรอบ

09รหัสข้อผิดพลาด

ทุกข้อผิดพลาดมีรหัส MQP ที่เสถียรและข้อความอ่านรู้เรื่อง จัดกลุ่มดังนี้:

กลุ่มความหมายและกรณีทั่วไป
MQP-1xxxโปรโตคอล / IPC: เฟรมเสีย เกินขีด เวลาคลาด คำสั่งยังไม่ implement (MQP-1005)
MQP-2xxxเครื่องพิมพ์ / ไดรเวอร์: ไม่มีเครื่อง ออฟไลน์ แฮชความสามารถเปลี่ยน (MQP-2007)
MQP-3xxxหน้า / ภาพ: ไม่ใช่ PNG ขนาดไม่ตรง (MQP-3002)
MQP-4xxxงาน / คิว: clientJobId ขัดกัน (MQP-4002) ฯลฯ
MQP-5xxxสิทธิ์: ยังไม่ได้รับสิทธิ์ (MQP-5001)
MQP-6xxxการติดตั้ง / อัปเดต

10ไคลเอนต์ตัวอย่าง Node.js

ไคลเอนต์ pipe ตัวอย่างที่รันได้ (ชุดเดียวกับเครื่องมือ debug ทางการ) เอาไปใช้ใน main process ของ Electron หรือบริการท้องถิ่นได้เลย:

qp-client.mjs
import net from 'node:net';
import { randomUUID } from 'node:crypto';

const PIPE = '\\\\.\\pipe\\LOCAL\\MhQuickPrint.v1';

function frame(json, blobs = []) {
  const control = Buffer.from(JSON.stringify(json));
  const parts = [Buffer.alloc(4), control, Buffer.alloc(4)];
  parts[0].writeUInt32LE(control.length);
  parts[2].writeUInt32LE(blobs.length);
  for (const blob of blobs) {
    const len = Buffer.alloc(8);
    len.writeBigUInt64LE(BigInt(blob.length));
    parts.push(len, blob);
  }
  return Buffer.concat(parts);
}

export function send(command, fields = {}, blobs = []) {
  const request = {
    protocol: 1,
    requestId: randomUUID(),
    callerId: 'my-app',          // 你的系统标识
    timestampUtcMs: Date.now(),
    command,
    ...fields,
  };
  return new Promise((resolve, reject) => {
    const socket = net.createConnection(PIPE);
    const chunks = [];
    let expected = null;
    socket.setTimeout(10000, () => socket.destroy(new Error('响应超时')));
    socket.once('connect', () => socket.write(frame(request, blobs)));
    socket.on('data', chunk => {
      chunks.push(chunk);
      const data = Buffer.concat(chunks);
      if (expected === null && data.length >= 4) expected = data.readUInt32LE(0);
      if (expected !== null && data.length >= expected + 8) {
        socket.end();
        resolve(JSON.parse(data.subarray(4, 4 + expected).toString('utf8')));
      }
    });
    socket.once('error', reject);
  });
}
ตัวอย่างใช้งาน: อ่านความสามารถ → เรนเดอร์ → พิมพ์เงียบ
import { send } from './qp-client.mjs';

// 1) 确认服务在线
await send('system.hello');

// 2) 选择打印机并读取能力(printerId 来自 printers.list)
const caps = await send('printers.capabilities', { printer: { printerId } });
const device = caps.result;   // dpiX / dpiY / capabilityHash / papers[]

// 3) 按目标 DPI 渲染整页 PNG(100 × 60 mm 热敏纸)
const widthDots  = Math.round(100 * device.dpiX / 25.4);
const heightDots = Math.round(60  * device.dpiY / 25.4);
const png = await renderLabelPng(widthDots, heightDots); // 你的排版引擎

// 4) 静默打印(blobIndex 对应 blobs 数组下标)
const result = await send('jobs.print', {
  job: {
    clientJobId: orderNo,       // 业务唯一号:防重复打印
    name: 'ENTRY-TEMPLATE',
    printerId,
    copies: 1,
    paper:  { widthMm: 100, heightMm: 60 },
    device: { dpiX: device.dpiX, dpiY: device.dpiY,
              capabilityHash: device.capabilityHash },
    pages:  [{ widthDots, heightDots, format: 'png', blobIndex: 0 }],
  },
}, [png]);

if (!result.ok) throw new Error(result.error.code + ' ' + result.error.message);

11คำถามที่พบบ่อยของการเชื่อมต่อ

เชื่อม pipe ไม่ได้ (ECONNREFUSED / ENOENT)?
เช็กตามลำดับ: 1) มีไอคอน MhQuickPrint ในถาดระบบหรือไม่ (บริการไม่ได้รันคือสาเหตุที่พบมากสุด ให้ดับเบิลคลิก exe) 2) ไคลเอนต์อยู่เซสชันผู้ใช้เดียวกับบริการหรือไม่ (pipe อนุญาตเฉพาะผู้ใช้อินเทอร์แอกทีฟ/ผู้ดูแล/SYSTEM) 3) พลาดไปใช้ไคลเอนต์ HTTP หรือไม่ — บริการนี้ไม่มีพอร์ต มีแต่ named pipe
ได้ MQP-2007 แฮชความสามารถไม่ตรง?
DPI / กระดาษ / ไดรเวอร์ของเครื่องพิมพ์เปลี่ยน เรียก printers.capabilities ใหม่ แล้วเรนเดอร์หน้าตาม dpiX / dpiY / capabilityHash และขนาดใหม่ก่อนส่งใหม่
ได้ MQP-3002 หน้ากับข้อมูลไม่ตรง?
เช็กสามอย่าง: blobIndex ไม่ซ้ำและครบทุก blob ไหม ไบต์ที่ส่งเป็น PNG จริงไหม (magic number) และ widthDots / heightDots เท่ากับ มม. × DPI ÷ 25.4 ปัดเศษ เป๊ะไหม
ได้ MQP-4002?
clientJobId เดิมส่งเนื้อหาต่างออกไป นั่นคือกลไกกันพิมพ์ซ้ำทำงาน: ใช้ผลของงานเดิมต่อ หรือถ้าเป็นงานพิมพ์ใหม่จริง ๆ ก็ใช้ clientJobId ใหม่
ได้ MQP-5001 ยังไม่มีสิทธิ์?
ช่วงทดลองใช้ฟรีหมดแล้ว คลิกขวาถาดระบบ → จัดการสิทธิ์ ส่งรหัสอุปกรณ์ 32 ตัวมาให้เราออกสิทธิ์ แล้ววางรหัสหรือนำเข้าไฟล์สิทธิ์
หน้าเว็บเชื่อมตรงได้ไหม?
ไม่ได้และไม่จำเป็น: named pipe ใช้ได้เฉพาะโปรเซสในเครื่อง ระบบเว็บที่ถูกต้องคือให้แบ็กเอนด์หรือไคลเอนต์ท้องถิ่น (main process ของ Electron หรือบริการประจำ) เป็นไคลเอนต์ของ pipe แล้วหน้าเว็บคุยผ่านตัวนี้
ดาวน์โหลดล่าสุด ภาพรวม