Move printer stuff into the ql package

This commit is contained in:
Přemysl Eric Janouch 2019-04-12 21:50:36 +02:00
parent b763c2d362
commit 4c2f29384d
Signed by: p
GPG Key ID: A0420B94F92B9493
3 changed files with 344 additions and 408 deletions

View File

@ -6,142 +6,6 @@ import (
"janouch.name/sklad/ql" "janouch.name/sklad/ql"
) )
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 printStatusInformation(d []byte) {
if d[0] != 0x80 || d[1] != 0x20 || d[2] != 0x42 || d[3] != 0x34 {
log.Println("unexpected status fixed bytes")
}
// Model code.
switch b := d[4]; b {
case 0x38:
log.Println("model: QL-800")
case 0x39:
log.Println("model: QL-810W")
case 0x41:
log.Println("model: QL-820NWB")
default:
log.Println("model:", b)
}
// d[6] seems to be 0x00 in a real-world QL-800.
if d[5] != 0x30 || d[6] != 0x30 || d[7] != 0x00 {
log.Println("unexpected status fixed bytes")
}
// Error information 1.
for _, e := range decodeBitfieldErrors(d[8], [8]string{
"no media", "end of media", "cutter jam", "?", "printer in use",
"printer turned off", "high-voltage adapter", "fan motor error"}) {
log.Println("error:", e)
}
// Error information 2.
for _, e := range decodeBitfieldErrors(d[9], [8]string{
"replace media", "expansion buffer full", "communication error",
"communication buffer full", "cover open", "cancel key",
"media cannot be fed", "system error"}) {
log.Println("error:", e)
}
// Media width.
log.Println("media width:", d[10], "mm")
// Media type.
switch b := d[11]; b {
case 0x00:
log.Println("media: no media")
case 0x4a, 0x0a: // 0x4a = J, in reality we get 0x0a as in QL-1100 docs
log.Println("media: continuous length tape")
case 0x4b, 0x0b: // 0x4b = K, in reality we get 0x0b as in QL-1100 docs
log.Println("media: die-cut labels")
default:
log.Println("media:", b)
}
// In a real-world QL-800, d[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 d[12] != 0x00 || d[13] != 0x00 || d[14] != 0x3f {
log.Println("unexpected status fixed bytes")
}
// Mode.
log.Println("mode:", d[15])
if d[16] != 0x00 {
log.Println("unexpected status fixed bytes")
}
// Media length.
log.Println("media length:", d[17], "mm")
// Status type.
switch b := d[18]; b {
case 0x00:
log.Println("status type: reply to status request")
case 0x01:
log.Println("status type: printing completed")
case 0x02:
log.Println("status type: error occurred")
case 0x04:
log.Println("status type: turned off")
case 0x05:
log.Println("status type: notification")
case 0x06:
log.Println("status type: phase change")
default:
log.Println("status type:", b)
}
// Phase type.
switch b := d[19]; b {
case 0x00:
log.Println("phase state: receiving state")
case 0x01:
log.Println("phase state: printing state")
default:
log.Println("phase state:", b)
}
// Phase number.
log.Println("phase number:", int(d[20])*256+int(d[21]))
// Notification number.
switch b := d[22]; b {
case 0x00:
log.Println("notification number: not available")
case 0x03:
log.Println("notification number: cooling (started)")
case 0x04:
log.Println("notification number: cooling (finished)")
default:
log.Println("notification number:", b)
}
// In a real-world QL-800, d[25] seems to be:
// 0x01 with 29mm tape or die-cut 29mm long labels,
// 0x81 with red-black 62mm tape.
if d[23] != 0x00 || d[24] != 0x00 || d[25] != 0x00 || d[26] != 0x00 ||
d[27] != 0x00 || d[28] != 0x00 || d[29] != 0x00 || d[30] != 0x00 ||
d[31] != 0x00 {
log.Println("unexpected status fixed bytes")
}
}
func main() { func main() {
printer, err := ql.Open() printer, err := ql.Open()
if err != nil { if err != nil {
@ -150,16 +14,14 @@ func main() {
if printer == nil { if printer == nil {
log.Fatalln("no suitable printer found") log.Fatalln("no suitable printer found")
} }
defer printer.Close() defer printer.Close()
if err := printer.Initialize(); err != nil { if err := printer.Initialize(); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
if err := printer.UpdateStatus(); err != nil {
status, err := printer.GetStatus()
if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
log.Printf("status\n%s", printer.LastStatus)
printStatusInformation(status)
} }

View File

@ -7,12 +7,10 @@ import (
"image/color" "image/color"
"image/draw" "image/draw"
"image/png" "image/png"
"io"
"log" "log"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"time"
"github.com/boombuler/barcode" "github.com/boombuler/barcode"
"github.com/boombuler/barcode/qr" "github.com/boombuler/barcode/qr"
@ -74,218 +72,6 @@ func (lr *leftRotate) At(x, y int) color.Color {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
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 printStatusInformation(d []byte) {
log.Println("-- status")
// Error information 1.
for _, e := range decodeBitfieldErrors(d[8], [8]string{
"no media", "end of media", "cutter jam", "?", "printer in use",
"printer turned off", "high-voltage adapter", "fan motor error"}) {
log.Println("error:", e)
}
// Error information 2.
for _, e := range decodeBitfieldErrors(d[9], [8]string{
"replace media", "expansion buffer full", "communication error",
"communication buffer full", "cover open", "cancel key",
"media cannot be fed", "system error"}) {
log.Println("error:", e)
}
// Media width.
log.Println("media width:", d[10], "mm")
// Media type.
switch b := d[11]; b {
case 0x00:
log.Println("media: no media")
case 0x4a, 0x0a: // 0x4a = J, in reality we get 0x0a as in QL-1100 docs
log.Println("media: continuous length tape")
case 0x4b, 0x0b: // 0x4b = K, in reality we get 0x0b as in QL-1100 docs
log.Println("media: die-cut labels")
default:
log.Println("media:", b)
}
// Mode.
log.Println("mode:", d[15])
// Media length.
log.Println("media length:", d[17], "mm")
// Status type.
switch b := d[18]; b {
case 0x00:
log.Println("status type: reply to status request")
case 0x01:
log.Println("status type: printing completed")
case 0x02:
log.Println("status type: error occurred")
case 0x04:
log.Println("status type: turned off")
case 0x05:
log.Println("status type: notification")
case 0x06:
log.Println("status type: phase change")
default:
log.Println("status type:", b)
}
// Phase type.
switch b := d[19]; b {
case 0x00:
log.Println("phase state: receiving state")
case 0x01:
log.Println("phase state: printing state")
default:
log.Println("phase state:", b)
}
// Phase number.
log.Println("phase number:", int(d[20])*256+int(d[21]))
// Notification number.
switch b := d[22]; b {
case 0x00:
log.Println("notification number: not available")
case 0x03:
log.Println("notification number: cooling (started)")
case 0x04:
log.Println("notification number: cooling (finished)")
default:
log.Println("notification number:", b)
}
}
// genLabelData converts an image to the printer's raster format.
func genLabelData(src image.Image, offset, length int) (data []byte) {
bounds := src.Bounds()
pixels := make([]bool, 720)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
length--
if length <= 0 {
break
}
off := offset
for x := bounds.Max.X - 1; x >= bounds.Min.X; x-- {
// 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++
}
data = append(data, 'g', 0x00, 90)
for i := 0; i < 90; i++ {
var b byte
for j := 0; j < 8; j++ {
b <<= 1
if pixels[i*8+j] {
b |= 1
}
}
data = append(data, b)
}
}
for ; length > 0; length-- {
data = append(data, 'g', 0x00, 90)
data = append(data, make([]byte, 90)...)
}
return
}
func printLabel(printer *ql.Printer, src image.Image,
status *ql.Status, mediaInfo *ql.MediaInfo) error {
data := []byte(nil)
// Raster mode.
// Should be the only supported mode for QL-800.
data = append(data, 0x1b, 0x69, 0x61, 0x01)
// Automatic status mode (though it's the default).
data = append(data, 0x1b, 0x69, 0x21, 0x00)
// Print information command.
dy := src.Bounds().Dy()
if mediaInfo.PrintAreaLength != 0 {
dy = mediaInfo.PrintAreaLength
}
mediaType := byte(0x0a)
if status.MediaLengthMM != 0 {
mediaType = byte(0x0b)
}
data = append(data, 0x1b, 0x69, 0x7a, 0x02|0x04|0x40|0x80, mediaType,
byte(status.MediaWidthMM), byte(status.MediaLengthMM),
byte(dy), byte(dy>>8), byte(dy>>16), byte(dy>>24), 0, 0x00)
// Auto cut, each 1 label.
data = append(data, 0x1b, 0x69, 0x4d, 0x40)
data = append(data, 0x1b, 0x69, 0x41, 0x01)
// Cut at end (though it's the default).
// Not sure what it means, doesn't seem to have any effect to turn it off.
data = append(data, 0x1b, 0x69, 0x4b, 0x08)
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 {
// May not set anything other than zero.
data = append(data, 0x1b, 0x69, 0x64, 0x00, 0x00)
}
// Compression mode: no compression.
// Should be the only supported mode for QL-800.
data = append(data, 0x4d, 0x00)
// The graphics data itself.
data = append(data, genLabelData(src, mediaInfo.SideMarginPins, dy)...)
// Print command with feeding.
data = append(data, 0x1a)
// ---
// Print the prepared data.
if _, err := printer.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(), make([]byte, 32)
for {
if time.Now().Sub(start) > 3*time.Second {
break
}
if n, err := printer.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 {
printStatusInformation(b)
}
}
return nil
}
// -----------------------------------------------------------------------------
var font *bdf.Font var font *bdf.Font
func genLabelForHeight(text string, height, scale int) image.Image { func genLabelForHeight(text string, height, scale int) image.Image {
@ -339,9 +125,9 @@ var tmpl = template.Must(template.New("form").Parse(`
<p>Printer: {{ .Printer.Manufacturer }} {{ .Printer.Model }} <p>Printer: {{ .Printer.Manufacturer }} {{ .Printer.Model }}
<p>Tape: <p>Tape:
{{ if .Status }} {{ if .Printer.LastStatus }}
{{ .Status.MediaWidthMM }} mm &times; {{ .Printer.LastStatus.MediaWidthMM }} mm &times;
{{ .Status.MediaLengthMM }} mm {{ .Printer.LastStatus.MediaLengthMM }} mm
{{ if .MediaInfo }} {{ if .MediaInfo }}
(offset: {{ .MediaInfo.SideMarginPins }} pt, (offset: {{ .MediaInfo.SideMarginPins }} pt,
@ -350,8 +136,8 @@ var tmpl = template.Must(template.New("form").Parse(`
(unknown media) (unknown media)
{{ end }} {{ end }}
{{ if .Status.Errors }} {{ if .Printer.LastStatus.Errors }}
{{ range .Status.Errors }} {{ range .Printer.LastStatus.Errors }}
<p>Error: {{ . }} <p>Error: {{ . }}
{{ end }} {{ end }}
{{ end }} {{ end }}
@ -389,16 +175,15 @@ func getPrinter() (*ql.Printer, error) {
return printer, nil return printer, nil
} }
func getStatus(printer *ql.Printer) (*ql.Status, error) { func getStatus(printer *ql.Printer) error {
if err := printer.Initialize(); err != nil { if err := printer.Initialize(); err != nil {
return nil, err return err
} }
if data, err := printer.GetStatus(); err != nil { if err := printer.UpdateStatus(); err != nil {
return nil, err return err
} else {
printStatusInformation(data)
return ql.DecodeStatus(data), nil
} }
log.Printf("status\n%s", printer.LastStatus)
return nil
} }
func handle(w http.ResponseWriter, r *http.Request) { func handle(w http.ResponseWriter, r *http.Request) {
@ -407,25 +192,22 @@ func handle(w http.ResponseWriter, r *http.Request) {
return return
} }
var ( var initErr error
status *ql.Status
initErr error
)
printer, printerErr := getPrinter() printer, printerErr := getPrinter()
if printerErr == nil { if printerErr == nil {
defer printer.Close() defer printer.Close()
status, initErr = getStatus(printer) initErr = getStatus(printer)
} }
var mediaInfo *ql.MediaInfo var mediaInfo *ql.MediaInfo
if status != nil { if printer.LastStatus != nil {
mediaInfo = ql.GetMediaInfo(status.MediaWidthMM, status.MediaLengthMM) mediaInfo = ql.GetMediaInfo(printer.LastStatus.MediaWidthMM(),
printer.LastStatus.MediaLengthMM())
} }
var params = struct { var params = struct {
Printer *ql.Printer Printer *ql.Printer
PrinterErr error PrinterErr error
Status *ql.Status
InitErr error InitErr error
MediaInfo *ql.MediaInfo MediaInfo *ql.MediaInfo
Text string Text string
@ -433,7 +215,6 @@ func handle(w http.ResponseWriter, r *http.Request) {
}{ }{
Printer: printer, Printer: printer,
PrinterErr: printerErr, PrinterErr: printerErr,
Status: status,
InitErr: initErr, InitErr: initErr,
MediaInfo: mediaInfo, MediaInfo: mediaInfo,
Text: r.FormValue("text"), Text: r.FormValue("text"),
@ -450,8 +231,7 @@ func handle(w http.ResponseWriter, r *http.Request) {
label = &leftRotate{image: genLabelForHeight( label = &leftRotate{image: genLabelForHeight(
params.Text, mediaInfo.PrintAreaPins, params.Scale)} params.Text, mediaInfo.PrintAreaPins, params.Scale)}
if r.FormValue("print") != "" { if r.FormValue("print") != "" {
if err := printLabel( if err := printer.Print(label); err != nil {
printer, label, status, mediaInfo); err != nil {
log.Println("print error:", err) log.Println("print error:", err)
} }
} }

354
ql/ql.go
View File

@ -9,7 +9,10 @@ package ql
import ( import (
"errors" "errors"
"fmt"
"image"
"io" "io"
"log"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -97,6 +100,8 @@ type Printer struct {
File *os.File File *os.File
Manufacturer string Manufacturer string
Model string Model string
LastStatus *Status
MediaInfo *MediaInfo
} }
func compatible(id deviceID) bool { func compatible(id deviceID) bool {
@ -160,9 +165,9 @@ func (p *Printer) Initialize() error {
// I'm not sure if this is necessary, or rather whether the kernel driver // 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. // does any buffering that could cause data to be returned at this point.
/* /*
var dummy [32]byte
for { for {
dummy := make([]byte, 32) if _, err := f.Read(dummy[:]); err == io.EOF {
if _, err := f.Read(dummy); err == io.EOF {
break break
} }
} }
@ -171,33 +176,50 @@ func (p *Printer) Initialize() error {
return nil return nil
} }
// GetStatus retrieves the printer's status as raw data. The printer must be var errTimeout = errors.New("timeout")
// in an appropriate mode, i.e. on-line and not currently printing. var errInvalidRead = errors.New("invalid read")
func (p *Printer) GetStatus() ([]byte, error) {
// Request status information.
if _, err := p.File.WriteString("\x1b\x69\x53"); err != nil {
return nil, err
}
// We need to poll the device a bit. // pollStatusBytes waits for the printer to send a status packet and returns
status := make([]byte, 32) // it as raw data.
start := time.Now() func (p *Printer) pollStatusBytes(
timeout time.Duration) (status [32]byte, err error) {
start, n := time.Now(), 0
for { for {
if n, err := p.File.Read(status); err == io.EOF { if n, err = p.File.Read(status[:]); err == io.EOF {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
} else if err != nil { } else if err != nil {
return nil, err return status, err
} else if n < 32 { } else if n < 32 {
return nil, errors.New("invalid read") return status, errInvalidRead
} else { } else {
return status, nil return status, nil
} }
if time.Now().Sub(start) > time.Second { if time.Now().Sub(start) > timeout {
return nil, errors.New("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. // Close closes the underlying file.
func (p *Printer) Close() error { func (p *Printer) Close() error {
return p.File.Close() return p.File.Close()
@ -256,12 +278,141 @@ func GetMediaInfo(widthMM, lengthMM int) *MediaInfo {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
type Status struct { // makeBitmapData converts an image to the printer's raster format.
MediaWidthMM int func makeBitmapData(src image.Image, offset, length int) (data []byte) {
MediaLengthMM int bounds := src.Bounds()
Errors []string pixels := [720]bool{}
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
length--
if length <= 0 {
break
}
off := offset
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++
}
data = append(data, 'g', 0x00, 90)
for i := 0; i < 90; i++ {
var b byte
for j := 0; j < 8; j++ {
b <<= 1
if pixels[i*8+j] {
b |= 1
}
}
data = append(data, b)
}
}
for ; length > 0; length-- {
data = append(data, 'g', 0x00, 90)
data = append(data, make([]byte, 90)...)
}
return
} }
func (p *Printer) makePrintData(image image.Image) (data []byte) {
mediaInfo := GetMediaInfo(
p.LastStatus.MediaWidthMM(),
p.LastStatus.MediaLengthMM(),
)
// Raster mode.
// Should be the only supported mode for QL-800.
data = append(data, 0x1b, 0x69, 0x61, 0x01)
// Automatic status mode (though it's the default).
data = append(data, 0x1b, 0x69, 0x21, 0x00)
// Print information command.
dy := image.Bounds().Dy()
if mediaInfo.PrintAreaLength != 0 {
dy = mediaInfo.PrintAreaLength
}
mediaType := byte(0x0a)
if p.LastStatus.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(dy), byte(dy>>8), byte(dy>>16), byte(dy>>24), 0, 0x00)
// Auto cut, each 1 label.
data = append(data, 0x1b, 0x69, 0x4d, 0x40)
data = append(data, 0x1b, 0x69, 0x41, 0x01)
// Cut at end (though it's the default).
// 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 {
// 3mm margins along the direction of feed. 0x23 = 35 dots, the minimum.
data = append(data, 0x1b, 0x69, 0x64, 0x23, 0x00)
} else {
// May not set anything other than zero.
data = append(data, 0x1b, 0x69, 0x64, 0x00, 0x00)
}
// Compression mode: no compression.
// Should be the only supported mode for QL-800.
data = append(data, 0x4d, 0x00)
// The graphics data itself.
data = append(data, makeBitmapData(image, mediaInfo.SideMarginPins, dy)...)
// 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 { func decodeBitfieldErrors(b byte, errors [8]string) []string {
var result []string var result []string
for i := uint(0); i < 8; i++ { for i := uint(0); i < 8; i++ {
@ -272,18 +423,161 @@ func decodeBitfieldErrors(b byte, errors [8]string) []string {
return result return result
} }
// TODO: What exactly do we need? Probably extend as needed. func (s *Status) Errors() (errors []string) {
func DecodeStatus(d []byte) *Status { errors = append(errors, decodeBitfieldErrors(s[8], [8]string{
var status Status
status.MediaWidthMM = int(d[10])
status.MediaLengthMM = int(d[17])
status.Errors = append(status.Errors, decodeBitfieldErrors(d[8], [8]string{
"no media", "end of media", "cutter jam", "?", "printer in use", "no media", "end of media", "cutter jam", "?", "printer in use",
"printer turned off", "high-voltage adapter", "fan motor error"})...) "printer turned off", "high-voltage adapter", "fan motor error"})...)
status.Errors = append(status.Errors, decodeBitfieldErrors(d[9], [8]string{ errors = append(errors, decodeBitfieldErrors(s[9], [8]string{
"replace media", "expansion buffer full", "communication error", "replace media", "expansion buffer full", "communication error",
"communication buffer full", "cover open", "cancel key", "communication buffer full", "cover open", "cancel key",
"media cannot be fed", "system error"})...) "media cannot be fed", "system error"})...)
return &status 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")
}
*/
} }