diff options
Diffstat (limited to 'ql')
| -rw-r--r-- | ql/ql.go | 425 | ||||
| -rw-r--r-- | ql/ql_linux.go | 212 | ||||
| -rw-r--r-- | ql/status.go | 206 | 
3 files changed, 449 insertions, 394 deletions
| @@ -8,54 +8,11 @@ package ql  //  http://www.undocprint.org/formats/communication_protocols/ieee_1284  import ( -	"errors" -	"fmt"  	"image" -	"io" -	"log" -	"os" -	"path/filepath"  	"regexp"  	"strings" -	"syscall" -	"time" -	"unsafe"  ) -// #include <linux/ioctl.h> -import "C" - -// ----------------------------------------------------------------------------- - -func _IOC(dir, typ, nr, size int) uintptr { -	return (uintptr(dir) << C._IOC_DIRSHIFT) | -		(uintptr(typ) << C._IOC_TYPESHIFT) | -		(uintptr(nr) << C._IOC_NRSHIFT) | -		(uintptr(size) << C._IOC_SIZESHIFT) -} - -const ( -	iocnrGetDeviceID = 1 -) - -// lpiocGetDeviceID reads the IEEE-1284 Device ID string of a printer. -func lpiocGetDeviceID(fd uintptr) ([]byte, error) { -	var buf [1024]byte -	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, -		_IOC(C._IOC_READ, 'P', iocnrGetDeviceID, len(buf)), -		uintptr(unsafe.Pointer(&buf))); err != 0 { -		return nil, err -	} - -	// In theory it might get trimmed along the way. -	length := int(buf[0])<<8 | int(buf[1]) -	if 2+length > len(buf) { -		return buf[2:], errors.New("the device ID string got trimmed") -	} - -	return buf[2 : 2+length], nil -} -  // -----------------------------------------------------------------------------  var deviceIDRegexp = regexp.MustCompile( @@ -96,14 +53,6 @@ func (id deviceID) FindFirst(key, abbreviation string) string {  // ----------------------------------------------------------------------------- -type Printer struct { -	File         *os.File -	Manufacturer string -	Model        string -	LastStatus   *Status -	MediaInfo    *MediaInfo -} -  func compatible(id deviceID) bool {  	for _, commandSet := range id.Find("COMMAND SET", "CMD") {  		if commandSet == "PT-CBP" { @@ -113,118 +62,6 @@ func compatible(id deviceID) bool {  	return false  } -// Open finds and initializes the first USB printer found supporting -// the appropriate protocol. Returns nil if no printer could be found. -func Open() (*Printer, error) { -	// Linux usblp module, located in /drivers/usb/class/usblp.c -	paths, err := filepath.Glob("/dev/usb/lp[0-9]*") -	if err != nil { -		return nil, err -	} -	for _, candidate := range paths { -		f, err := os.OpenFile(candidate, os.O_RDWR, 0) -		if err != nil { -			continue -		} -		// Filter out obvious non-printers. -		deviceID, err := lpiocGetDeviceID(f.Fd()) -		if err != nil { -			f.Close() -			continue -		} -		parsedID := parseIEEE1284DeviceID(deviceID) -		// Filter out printers that wouldn't understand the protocol. -		if !compatible(parsedID) { -			f.Close() -			continue -		} -		return &Printer{ -			File:         f, -			Manufacturer: parsedID.FindFirst("MANUFACTURER", "MFG"), -			Model:        parsedID.FindFirst("MODEL", "MDL"), -		}, nil -	} -	return nil, nil -} - -// Initialize initializes the printer for further operations. -func (p *Printer) Initialize() error { -	// Clear the print buffer. -	invalidate := make([]byte, 400) -	if _, err := p.File.Write(invalidate); err != nil { -		return err -	} - -	// Initialize. -	if _, err := p.File.WriteString("\x1b\x40"); err != nil { -		return err -	} - -	// Flush any former responses in the printer's queue. -	// -	// I'm not sure if this is necessary, or rather whether the kernel driver -	// does any buffering that could cause data to be returned at this point. -	/* -		var dummy [32]byte -		for { -			if _, err := f.Read(dummy[:]); err == io.EOF { -				break -			} -		} -	*/ - -	return nil -} - -var errTimeout = errors.New("timeout") -var errInvalidRead = errors.New("invalid read") - -// pollStatusBytes waits for the printer to send a status packet and returns -// it as raw data. -func (p *Printer) pollStatusBytes( -	timeout time.Duration) (status [32]byte, err error) { -	start, n := time.Now(), 0 -	for { -		if n, err = p.File.Read(status[:]); err == io.EOF { -			time.Sleep(10 * time.Millisecond) -		} else if err != nil { -			return status, err -		} else if n < 32 { -			return status, errInvalidRead -		} else { -			return status, nil -		} -		if time.Now().Sub(start) > timeout { -			return status, errTimeout -		} -	} -} - -// Request new status information from the printer. The printer -// must be in an appropriate mode, i.e. on-line and not currently printing. -func (p *Printer) UpdateStatus() error { -	// Request status information. -	if _, err := p.File.WriteString("\x1b\x69\x53"); err != nil { -		return err -	} - -	// Retrieve status information. -	status, err := p.pollStatusBytes(time.Second) -	if err != nil { -		p.LastStatus = nil -		return err -	} - -	s := Status(status) -	p.LastStatus = &s -	return nil -} - -// Close closes the underlying file. -func (p *Printer) Close() error { -	return p.File.Close() -} -  // -----------------------------------------------------------------------------  type mediaSize struct { @@ -278,30 +115,35 @@ func GetMediaInfo(widthMM, lengthMM int) *MediaInfo {  // ----------------------------------------------------------------------------- +const ( +	printBytes = 90 +	printPins  = printBytes * 8 +) +  // makeBitmapData converts an image to the printer's raster format. -func makeBitmapData(src image.Image, offset, length int) (data []byte) { +func makeBitmapData(src image.Image, margin, length int) (data []byte) {  	bounds := src.Bounds() -	pixels := [720]bool{} +	if bounds.Dy() > length { +		bounds.Max.Y = bounds.Min.Y + length +	} +	if bounds.Dx() > printPins-margin { +		bounds.Max.X = bounds.Min.X + printPins +	} + +	pixels := [printPins]bool{}  	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {  		length-- -		if length <= 0 { -			break -		} -		off := offset +		// The graphics needs to be inverted horizontally, iterating backwards. +		offset := margin  		for x := bounds.Max.X - 1; x >= bounds.Min.X; x-- { -			if off >= len(pixels) { -				break -			} - -			// TODO: Anything to do with the ColorModel?  			r, g, b, a := src.At(x, y).RGBA() -			pixels[off] = r == 0 && g == 0 && b == 0 && a != 0 -			off++ +			pixels[offset] = r == 0 && g == 0 && b == 0 && a >= 0x8000 +			offset++  		} -		data = append(data, 'g', 0x00, 90) -		for i := 0; i < 90; i++ { +		data = append(data, 'g', 0x00, printBytes) +		for i := 0; i < printBytes; i++ {  			var b byte  			for j := 0; j < 8; j++ {  				b <<= 1 @@ -313,17 +155,20 @@ func makeBitmapData(src image.Image, offset, length int) (data []byte) {  		}  	}  	for ; length > 0; length-- { -		data = append(data, 'g', 0x00, 90) -		data = append(data, make([]byte, 90)...) +		data = append(data, 'g', 0x00, printBytes) +		data = append(data, make([]byte, printBytes)...)  	}  	return  } -func (p *Printer) makePrintData(image image.Image) (data []byte) { +func makePrintData(status *Status, image image.Image) (data []byte) {  	mediaInfo := GetMediaInfo( -		p.LastStatus.MediaWidthMM(), -		p.LastStatus.MediaLengthMM(), +		status.MediaWidthMM(), +		status.MediaLengthMM(),  	) +	if mediaInfo == nil { +		return nil +	}  	// Raster mode.  	// Should be the only supported mode for QL-800. @@ -339,12 +184,12 @@ func (p *Printer) makePrintData(image image.Image) (data []byte) {  	}  	mediaType := byte(0x0a) -	if p.LastStatus.MediaLengthMM() != 0 { +	if status.MediaLengthMM() != 0 {  		mediaType = byte(0x0b)  	}  	data = append(data, 0x1b, 0x69, 0x7a, 0x02|0x04|0x40|0x80, mediaType, -		byte(p.LastStatus.MediaWidthMM()), byte(p.LastStatus.MediaLengthMM()), +		byte(status.MediaWidthMM()), byte(status.MediaLengthMM()),  		byte(dy), byte(dy>>8), byte(dy>>16), byte(dy>>24), 0, 0x00)  	// Auto cut, each 1 label. @@ -355,7 +200,7 @@ func (p *Printer) makePrintData(image image.Image) (data []byte) {  	// Not sure what it means, doesn't seem to have any effect to turn it off.  	data = append(data, 0x1b, 0x69, 0x4b, 0x08) -	if p.LastStatus.MediaLengthMM() != 0 { +	if status.MediaLengthMM() != 0 {  		// 3mm margins along the direction of feed. 0x23 = 35 dots, the minimum.  		data = append(data, 0x1b, 0x69, 0x64, 0x23, 0x00)  	} else { @@ -373,211 +218,3 @@ func (p *Printer) makePrintData(image image.Image) (data []byte) {  	// Print command with feeding.  	return append(data, 0x1a)  } - -func (p *Printer) Print(image image.Image) error { -	data := p.makePrintData(image) - -	// Print the prepared data. -	if _, err := p.File.Write(data); err != nil { -		return err -	} - -	// TODO: We specifically need to wait for a transition to the receiving -	// state, and try to figure out something from the statuses. -	// We may also receive an error status instead of the transition to -	// the printing state. Or even after it. -	start, b := time.Now(), [32]byte{} -	for { -		if n, err := p.File.Read(b[:]); err == io.EOF { -			time.Sleep(100 * time.Millisecond) -		} else if err != nil { -			return err -		} else if n < 32 { -			return errors.New("invalid read") -		} else { -			status := Status(b) -			log.Printf("status\n%s", &status) -		} -		if time.Now().Sub(start) > 3*time.Second { -			break -		} -	} -	return nil -} - -// ----------------------------------------------------------------------------- - -// Status is a decoder for the status packed returned by the printer. -type Status [32]byte - -func (s *Status) MediaWidthMM() int  { return int(s[10]) } -func (s *Status) MediaLengthMM() int { return int(s[17]) } - -func decodeBitfieldErrors(b byte, errors [8]string) []string { -	var result []string -	for i := uint(0); i < 8; i++ { -		if b&(1<<i) != 0 { -			result = append(result, errors[i]) -		} -	} -	return result -} - -func (s *Status) Errors() (errors []string) { -	errors = append(errors, decodeBitfieldErrors(s[8], [8]string{ -		"no media", "end of media", "cutter jam", "?", "printer in use", -		"printer turned off", "high-voltage adapter", "fan motor error"})...) -	errors = append(errors, decodeBitfieldErrors(s[9], [8]string{ -		"replace media", "expansion buffer full", "communication error", -		"communication buffer full", "cover open", "cancel key", -		"media cannot be fed", "system error"})...) -	return -} - -// String implements the Stringer interface. -func (s *Status) String() string { -	var b strings.Builder -	s.Dump(&b) -	return b.String() -} - -// Dump writes the status data to an io.Writer in a human-readable format. -func (s *Status) Dump(f io.Writer) { -	/* -		if s[0] != 0x80 || s[1] != 0x20 || s[2] != 0x42 || s[3] != 0x34 { -			fmt.Fprintln(f, "unexpected status fixed bytes") -		} -	*/ - -	// Model code. -	switch m := s[4]; m { -	case 0x38: -		fmt.Fprintln(f, "model: QL-800") -	case 0x39: -		fmt.Fprintln(f, "model: QL-810W") -	case 0x41: -		fmt.Fprintln(f, "model: QL-820NWB") -	case 0x43: -		fmt.Fprintln(f, "model: QL-1100") -	case 0x44: -		fmt.Fprintln(f, "model: QL-1110NWB") -	case 0x45: -		fmt.Fprintln(f, "model: QL-1115NWB") -	default: -		fmt.Fprintln(f, "model:", m) -	} - -	/* -		// s[6] seems to be 0x00 in a real-world QL-800, as in QL-1100 docs. -		if s[5] != 0x30 || s[6] != 0x30 || s[7] != 0x00 { -			fmt.Fprintln(f, "unexpected status fixed bytes") -		} -	*/ - -	// Error information 1. -	for _, e := range decodeBitfieldErrors(s[8], [8]string{ -		"no media", "end of media", "cutter jam", "?", "printer in use", -		"printer turned off", "high-voltage adapter", "fan motor error"}) { -		fmt.Fprintln(f, "error 1:", e) -	} - -	// Error information 2. -	for _, e := range decodeBitfieldErrors(s[9], [8]string{ -		"replace media", "expansion buffer full", "communication error", -		"communication buffer full", "cover open", "cancel key", -		"media cannot be fed", "system error"}) { -		fmt.Fprintln(f, "error 2:", e) -	} - -	// Media width. -	fmt.Fprintln(f, "media width:", s[10], "mm") - -	// Media type. -	switch t := s[11]; t { -	case 0x00: -		fmt.Fprintln(f, "media: no media") -	case 0x4a, 0x0a: // 0x4a = J, in reality we get 0x0a, as in QL-1100 docs. -		fmt.Fprintln(f, "media: continuous length tape") -	case 0x4b, 0x0b: // 0x4b = K, in reality we get 0x0b, as in QL-1100 docs. -		fmt.Fprintln(f, "media: die-cut labels") -	default: -		fmt.Fprintln(f, "media:", t) -	} - -	/* -		// In a real-world QL-800, s[14] seems to be: -		//  0x01 with die-cut 29mm long labels, -		//  0x14 with 29mm tape, -		//  0x23 with red-black 62mm tape, -		// and directly corresponds to physical pins on the tape. -		if s[12] != 0x00 || s[13] != 0x00 || s[14] != 0x3f { -			fmt.Fprintln(f, "unexpected status fixed bytes") -		} -	*/ - -	// Mode. -	fmt.Fprintln(f, "mode:", s[15]) - -	/* -		if s[16] != 0x00 { -			fmt.Fprintln(f, "unexpected status fixed bytes") -		} -	*/ - -	// Media length. -	fmt.Fprintln(f, "media length:", s[17], "mm") - -	// Status type. -	switch t := s[18]; t { -	case 0x00: -		fmt.Fprintln(f, "status type: reply to status request") -	case 0x01: -		fmt.Fprintln(f, "status type: printing completed") -	case 0x02: -		fmt.Fprintln(f, "status type: error occurred") -	case 0x04: -		fmt.Fprintln(f, "status type: turned off") -	case 0x05: -		fmt.Fprintln(f, "status type: notification") -	case 0x06: -		fmt.Fprintln(f, "status type: phase change") -	default: -		fmt.Fprintln(f, "status type:", t) -	} - -	// Phase type. -	switch t := s[19]; t { -	case 0x00: -		fmt.Fprintln(f, "phase state: receiving state") -	case 0x01: -		fmt.Fprintln(f, "phase state: printing state") -	default: -		fmt.Fprintln(f, "phase state:", t) -	} - -	// Phase number. -	fmt.Fprintln(f, "phase number:", int(s[20])*256+int(s[21])) - -	// Notification number. -	switch n := s[22]; n { -	case 0x00: -		fmt.Fprintln(f, "notification number: not available") -	case 0x03: -		fmt.Fprintln(f, "notification number: cooling (started)") -	case 0x04: -		fmt.Fprintln(f, "notification number: cooling (finished)") -	default: -		fmt.Fprintln(f, "notification number:", n) -	} - -	/* -		// In a real-world QL-800, s[25] seems to be: -		//  0x01 with 29mm tape or die-cut 29mm long labels, -		//  0x81 with red-black 62mm tape. -		if s[23] != 0x00 || s[24] != 0x00 || s[25] != 0x00 || s[26] != 0x00 || -			s[27] != 0x00 || s[28] != 0x00 || s[29] != 0x00 || s[30] != 0x00 || -			s[31] != 0x00 { -			fmt.Fprintln(f, "unexpected status fixed bytes") -		} -	*/ -} diff --git a/ql/ql_linux.go b/ql/ql_linux.go new file mode 100644 index 0000000..1b1f22c --- /dev/null +++ b/ql/ql_linux.go @@ -0,0 +1,212 @@ +package ql + +import ( +	"errors" +	"image" +	"io" +	"os" +	"path/filepath" +	"syscall" +	"time" +	"unsafe" +) + +// #include <linux/ioctl.h> +import "C" + +// ----------------------------------------------------------------------------- + +func _IOC(dir, typ, nr, size int) uintptr { +	return (uintptr(dir) << C._IOC_DIRSHIFT) | +		(uintptr(typ) << C._IOC_TYPESHIFT) | +		(uintptr(nr) << C._IOC_NRSHIFT) | +		(uintptr(size) << C._IOC_SIZESHIFT) +} + +const ( +	iocnrGetDeviceID = 1 +) + +// lpiocGetDeviceID reads the IEEE-1284 Device ID string of a printer. +func lpiocGetDeviceID(fd uintptr) ([]byte, error) { +	var buf [1024]byte +	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, +		_IOC(C._IOC_READ, 'P', iocnrGetDeviceID, len(buf)), +		uintptr(unsafe.Pointer(&buf))); err != 0 { +		return nil, err +	} + +	// In theory it might get trimmed along the way. +	length := int(buf[0])<<8 | int(buf[1]) +	if 2+length > len(buf) { +		return buf[2:], errors.New("the device ID string got trimmed") +	} + +	return buf[2 : 2+length], nil +} + +// ----------------------------------------------------------------------------- + +type Printer struct { +	File         *os.File +	Manufacturer string +	Model        string + +	LastStatus *Status +	MediaInfo  *MediaInfo + +	// StatusNotify is called whenever we receive a status packet. +	StatusNotify func(*Status) +} + +// Open finds and initializes the first USB printer found supporting +// the appropriate protocol. Returns nil if no printer could be found. +func Open() (*Printer, error) { +	// Linux usblp module, located in /drivers/usb/class/usblp.c +	paths, err := filepath.Glob("/dev/usb/lp[0-9]*") +	if err != nil { +		return nil, err +	} +	for _, candidate := range paths { +		f, err := os.OpenFile(candidate, os.O_RDWR, 0) +		if err != nil { +			continue +		} +		// Filter out obvious non-printers. +		deviceID, err := lpiocGetDeviceID(f.Fd()) +		if err != nil { +			f.Close() +			continue +		} +		parsedID := parseIEEE1284DeviceID(deviceID) +		// Filter out printers that wouldn't understand the protocol. +		if !compatible(parsedID) { +			f.Close() +			continue +		} +		return &Printer{ +			File:         f, +			Manufacturer: parsedID.FindFirst("MANUFACTURER", "MFG"), +			Model:        parsedID.FindFirst("MODEL", "MDL"), +		}, nil +	} +	return nil, nil +} + +// Initialize initializes the printer for further operations. +func (p *Printer) Initialize() error { +	// Clear the print buffer. +	invalidate := make([]byte, 400) +	if _, err := p.File.Write(invalidate); err != nil { +		return err +	} + +	// Initialize. +	if _, err := p.File.WriteString("\x1b\x40"); err != nil { +		return err +	} + +	// Flush any former responses in the printer's queue. +	// +	// I'm not sure if this is necessary, or rather whether the kernel driver +	// does any buffering that could cause data to be returned at this point. +	/* +		var dummy [32]byte +		for { +			if _, err := f.Read(dummy[:]); err == io.EOF { +				break +			} +		} +	*/ + +	return nil +} + +var errTimeout = errors.New("timeout") +var errInvalidRead = errors.New("invalid read") + +func (p *Printer) updateStatus(status Status) { +	p.LastStatus = &status +	if p.StatusNotify != nil { +		p.StatusNotify(p.LastStatus) +	} +} + +// pollStatusBytes waits for the printer to send a status packet and returns +// it as raw data. +func (p *Printer) pollStatusBytes( +	timeout time.Duration) (*Status, error) { +	start, buf := time.Now(), [32]byte{} +	for { +		if n, err := p.File.Read(buf[:]); err == io.EOF { +			time.Sleep(10 * time.Millisecond) +		} else if err != nil { +			return nil, err +		} else if n < 32 { +			return nil, errInvalidRead +		} else { +			p.updateStatus(Status(buf)) +			return p.LastStatus, nil +		} +		if time.Now().Sub(start) > timeout { +			return nil, errTimeout +		} +	} +} + +// Request new status information from the printer. The printer +// must be in an appropriate mode, i.e. on-line and not currently printing. +func (p *Printer) UpdateStatus() error { +	// Request status information. +	if _, err := p.File.WriteString("\x1b\x69\x53"); err != nil { +		return err +	} + +	// Retrieve status information. +	if _, err := p.pollStatusBytes(time.Second); err != nil { +		p.LastStatus = nil +		return err +	} +	return nil +} + +var errErrorOccurred = errors.New("error occurred") +var errUnexpectedStatus = errors.New("unexpected status") +var errUnknownMedia = errors.New("unknown media") + +func (p *Printer) Print(image image.Image) error { +	data := makePrintData(p.LastStatus, image) +	if data == nil { +		return errUnknownMedia +	} +	if _, err := p.File.Write(data); err != nil { +		return err +	} + +	// See diagrams: we may receive an error status instead of the transition +	// to the printing state. Or even after it. +	// +	// Not sure how exactly cooling behaves and I don't want to test it. +	for { +		status, err := p.pollStatusBytes(10 * time.Second) +		if err != nil { +			return err +		} + +		switch status.Type() { +		case StatusTypePhaseChange: +			// Nothing to do. +		case StatusTypePrintingCompleted: +			return nil +		case StatusTypeErrorOccurred: +			return errErrorOccurred +		default: +			return errUnexpectedStatus +		} +	} +} + +// Close closes the underlying file. +func (p *Printer) Close() error { +	return p.File.Close() +} diff --git a/ql/status.go b/ql/status.go new file mode 100644 index 0000000..ab3e675 --- /dev/null +++ b/ql/status.go @@ -0,0 +1,206 @@ +package ql + +import ( +	"fmt" +	"io" +	"strings" +) + +// Status is a decoder for the status packed returned by the printer. +type Status [32]byte + +func (s *Status) MediaWidthMM() int  { return int(s[10]) } +func (s *Status) MediaLengthMM() int { return int(s[17]) } + +type StatusType byte + +const ( +	StatusTypeReplyToRequest    StatusType = 0x00 +	StatusTypePrintingCompleted            = 0x01 +	StatusTypeErrorOccurred                = 0x02 +	StatusTypeTurnedOff                    = 0x04 +	StatusTypeNotification                 = 0x05 +	StatusTypePhaseChange                  = 0x06 +) + +func (s *Status) Type() StatusType { return StatusType(s[18]) } + +type StatusPhase byte + +const ( +	StatusPhaseReceiving StatusPhase = 0x00 +	StatusPhasePrinting              = 0x01 +) + +func (s *Status) Phase() StatusPhase { return StatusPhase(s[19]) } + +func decodeBitfieldErrors(b byte, errors [8]string) []string { +	var result []string +	for i := uint(0); i < 8; i++ { +		if b&(1<<i) != 0 { +			result = append(result, errors[i]) +		} +	} +	return result +} + +func (s *Status) Errors() (errors []string) { +	errors = append(errors, decodeBitfieldErrors(s[8], [8]string{ +		"no media", "end of media", "cutter jam", "?", "printer in use", +		"printer turned off", "high-voltage adapter", "fan motor error"})...) +	errors = append(errors, decodeBitfieldErrors(s[9], [8]string{ +		"replace media", "expansion buffer full", "communication error", +		"communication buffer full", "cover open", "cancel key", +		"media cannot be fed", "system error"})...) +	return +} + +// ----------------------------------------------------------------------------- + +// String implements the Stringer interface. +func (s *Status) String() string { +	var b strings.Builder +	s.Dump(&b) +	return b.String() +} + +// Dump writes the status data to an io.Writer in a human-readable format. +func (s *Status) Dump(f io.Writer) { +	/* +		if s[0] != 0x80 || s[1] != 0x20 || s[2] != 0x42 || s[3] != 0x34 { +			fmt.Fprintln(f, "unexpected status fixed bytes") +		} +	*/ + +	// Model code. +	switch m := s[4]; m { +	case 0x38: +		fmt.Fprintln(f, "model: QL-800") +	case 0x39: +		fmt.Fprintln(f, "model: QL-810W") +	case 0x41: +		fmt.Fprintln(f, "model: QL-820NWB") +	case 0x43: +		fmt.Fprintln(f, "model: QL-1100") +	case 0x44: +		fmt.Fprintln(f, "model: QL-1110NWB") +	case 0x45: +		fmt.Fprintln(f, "model: QL-1115NWB") +	default: +		fmt.Fprintln(f, "model:", m) +	} + +	/* +		// s[6] seems to be 0x00 in a real-world QL-800, as in QL-1100 docs. +		if s[5] != 0x30 || s[6] != 0x30 || s[7] != 0x00 { +			fmt.Fprintln(f, "unexpected status fixed bytes") +		} +	*/ + +	// Error information 1. +	for _, e := range decodeBitfieldErrors(s[8], [8]string{ +		"no media", "end of media", "cutter jam", "?", "printer in use", +		"printer turned off", "high-voltage adapter", "fan motor error"}) { +		fmt.Fprintln(f, "error 1:", e) +	} + +	// Error information 2. +	for _, e := range decodeBitfieldErrors(s[9], [8]string{ +		"replace media", "expansion buffer full", "communication error", +		"communication buffer full", "cover open", "cancel key", +		"media cannot be fed", "system error"}) { +		fmt.Fprintln(f, "error 2:", e) +	} + +	// Media width. +	fmt.Fprintln(f, "media width:", s[10], "mm") + +	// Media type. +	switch t := s[11]; t { +	case 0x00: +		fmt.Fprintln(f, "media: no media") +	case 0x4a, 0x0a: // 0x4a = J, in reality we get 0x0a, as in QL-1100 docs. +		fmt.Fprintln(f, "media: continuous length tape") +	case 0x4b, 0x0b: // 0x4b = K, in reality we get 0x0b, as in QL-1100 docs. +		fmt.Fprintln(f, "media: die-cut labels") +	default: +		fmt.Fprintln(f, "media:", t) +	} + +	/* +		// In a real-world QL-800, s[14] seems to be: +		//  0x01 with die-cut 29mm long labels, +		//  0x14 with 29mm tape, +		//  0x23 with red-black 62mm tape, +		// and directly corresponds to physical pins on the tape. +		if s[12] != 0x00 || s[13] != 0x00 || s[14] != 0x3f { +			fmt.Fprintln(f, "unexpected status fixed bytes") +		} +	*/ + +	// Mode. +	fmt.Fprintln(f, "mode:", s[15]) + +	/* +		if s[16] != 0x00 { +			fmt.Fprintln(f, "unexpected status fixed bytes") +		} +	*/ + +	// Media length. +	fmt.Fprintln(f, "media length:", s[17], "mm") + +	// Status type. +	switch t := s[18]; t { +	case 0x00: +		fmt.Fprintln(f, "status type: reply to status request") +	case 0x01: +		fmt.Fprintln(f, "status type: printing completed") +	case 0x02: +		fmt.Fprintln(f, "status type: error occurred") +	case 0x04: +		fmt.Fprintln(f, "status type: turned off") +	case 0x05: +		fmt.Fprintln(f, "status type: notification") +	case 0x06: +		fmt.Fprintln(f, "status type: phase change") +	default: +		fmt.Fprintln(f, "status type:", t) +	} + +	// Phase type. +	switch t := s[19]; t { +	case 0x00: +		fmt.Fprintln(f, "phase state: receiving state") +	case 0x01: +		fmt.Fprintln(f, "phase state: printing state") +	default: +		fmt.Fprintln(f, "phase state:", t) +	} + +	// Phase number. +	fmt.Fprintln(f, "phase number:", int(s[20])*256+int(s[21])) + +	// Notification number. +	switch n := s[22]; n { +	case 0x00: +		fmt.Fprintln(f, "notification number: not available") +	case 0x03: +		fmt.Fprintln(f, "notification number: cooling (started)") +	case 0x04: +		fmt.Fprintln(f, "notification number: cooling (finished)") +	default: +		fmt.Fprintln(f, "notification number:", n) +	} + +	/* +		// In a real-world QL-800, s[25] seems to be: +		//  0x01 with 29mm tape or die-cut 29mm long labels, +		//  0x81 with red-black 62mm tape. +		if s[23] != 0x00 || s[24] != 0x00 || s[25] != 0x00 || s[26] != 0x00 || +			s[27] != 0x00 || s[28] != 0x00 || s[29] != 0x00 || s[30] != 0x00 || +			s[31] != 0x00 { +			fmt.Fprintln(f, "unexpected status fixed bytes") +		} +	*/ +} | 
