Browse Source

Cleanup, improve communication while printing

master
Přemysl Janouch 1 month ago
parent
commit
89fc9d86c7
Signed by: Přemysl Janouch <p@janouch.name> GPG Key ID: A0420B94F92B9493
6 changed files with 555 additions and 404 deletions
  1. 24
    1
      go.mod
  2. 68
    0
      go.sum
  3. 14
    9
      label-exp/main.go
  4. 31
    394
      ql/ql.go
  5. 212
    0
      ql/ql_linux.go
  6. 206
    0
      ql/status.go

+ 24
- 1
go.mod View File

@@ -2,4 +2,27 @@ module janouch.name/sklad
2 2
 
3 3
 go 1.12
4 4
 
5
-require github.com/boombuler/barcode v1.0.0
5
+require (
6
+	github.com/boombuler/barcode v1.0.0
7
+	github.com/cosiner/argv v0.0.1 // indirect
8
+	github.com/cpuguy83/go-md2man v1.0.10 // indirect
9
+	github.com/go-delve/delve v1.2.0 // indirect
10
+	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
11
+	github.com/mattn/go-colorable v0.1.1 // indirect
12
+	github.com/mattn/go-isatty v0.0.7 // indirect
13
+	github.com/mattn/go-runewidth v0.0.4 // indirect
14
+	github.com/peterh/liner v1.1.0 // indirect
15
+	github.com/pkg/profile v1.3.0 // indirect
16
+	github.com/russross/blackfriday v2.0.0+incompatible // indirect
17
+	github.com/sirupsen/logrus v1.4.1 // indirect
18
+	github.com/spf13/cobra v0.0.3 // indirect
19
+	github.com/spf13/pflag v1.0.3 // indirect
20
+	github.com/stretchr/objx v0.2.0 // indirect
21
+	golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c // indirect
22
+	golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a // indirect
23
+	golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 // indirect
24
+	golang.org/x/sys v0.0.0-20190411185658-b44545bcd369 // indirect
25
+	golang.org/x/tools v0.0.0-20190411180116-681f9ce8ac52 // indirect
26
+	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
27
+	gopkg.in/yaml.v2 v2.2.2 // indirect
28
+)

+ 68
- 0
go.sum View File

@@ -1,2 +1,70 @@
1 1
 github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc=
2 2
 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
3
+github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5/go.mod h1:p/NrK5tF6ICIly4qwEDsf6VDirFiWWz0FenfYBwJaKQ=
4
+github.com/cosiner/argv v0.0.1 h1:2iAFN+sWPktbZ4tvxm33Ei8VY66FPCxdOxpncUGpAXE=
5
+github.com/cosiner/argv v0.0.1/go.mod h1:p/NrK5tF6ICIly4qwEDsf6VDirFiWWz0FenfYBwJaKQ=
6
+github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
7
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
8
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10
+github.com/go-delve/delve v1.2.0 h1:uwGyfYO0WsWqbnDWvxCBKOr2qFLpii3tLxwM+fTJs70=
11
+github.com/go-delve/delve v1.2.0/go.mod h1:yP+LD36s/ud5nm4lsQY0TwNhYu2PAwk6xItz+442j74=
12
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
13
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
14
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
15
+github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
16
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
17
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
18
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
19
+github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
20
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
21
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
22
+github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
23
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
24
+github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
25
+github.com/peterh/liner v1.1.0 h1:f+aAedNJA6uk7+6rXsYBnhdo4Xux7ESLe+kcuVUF5os=
26
+github.com/peterh/liner v1.1.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
27
+github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
28
+github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
29
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
30
+github.com/russross/blackfriday v0.0.0-20180428102519-11635eb403ff/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
31
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
32
+github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
33
+github.com/sirupsen/logrus v0.0.0-20180523074243-ea8897e79973/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
34
+github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
35
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
36
+github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
37
+github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
38
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
39
+github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
40
+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
41
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
42
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
43
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
44
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
45
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
46
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
47
+golang.org/x/arch v0.0.0-20171004143515-077ac972c2e4/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
48
+golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c h1:Rx/HTKi09myZ25t1SOlDHmHOy/mKxNAcu0hP1oPX9qM=
49
+golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
50
+golang.org/x/crypto v0.0.0-20180614174826-fd5f17ee7299/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
51
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
52
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
53
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
54
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
55
+golang.org/x/sys v0.0.0-20180614134839-8883426083c0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
56
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
57
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
58
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
59
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
60
+golang.org/x/sys v0.0.0-20190411185658-b44545bcd369 h1:aBlRBZoCuZNRDClvfkDoklQqdLzBaA3uViASg2z2p24=
61
+golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
62
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
63
+golang.org/x/tools v0.0.0-20181120060634-fc4f04983f62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
64
+golang.org/x/tools v0.0.0-20190411180116-681f9ce8ac52/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
65
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
66
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
67
+gopkg.in/yaml.v2 v2.0.0-20170407172122-cd8b52f8269e/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
68
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
69
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
70
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 14
- 9
label-exp/main.go View File

@@ -182,7 +182,6 @@ func getStatus(printer *ql.Printer) error {
182 182
 	if err := printer.UpdateStatus(); err != nil {
183 183
 		return err
184 184
 	}
185
-	log.Printf("status\n%s", printer.LastStatus)
186 185
 	return nil
187 186
 }
188 187
 
@@ -192,17 +191,23 @@ func handle(w http.ResponseWriter, r *http.Request) {
192 191
 		return
193 192
 	}
194 193
 
195
-	var initErr error
194
+	var (
195
+		initErr   error
196
+		mediaInfo *ql.MediaInfo
197
+	)
196 198
 	printer, printerErr := getPrinter()
197 199
 	if printerErr == nil {
198 200
 		defer printer.Close()
199
-		initErr = getStatus(printer)
200
-	}
201
+		printer.StatusNotify = func(status *ql.Status) {
202
+			log.Printf("\x1b[1mreceived status\x1b[m\n%s", status)
203
+		}
201 204
 
202
-	var mediaInfo *ql.MediaInfo
203
-	if printer.LastStatus != nil {
204
-		mediaInfo = ql.GetMediaInfo(printer.LastStatus.MediaWidthMM(),
205
-			printer.LastStatus.MediaLengthMM())
205
+		if initErr = getStatus(printer); initErr == nil {
206
+			mediaInfo = ql.GetMediaInfo(
207
+				printer.LastStatus.MediaWidthMM(),
208
+				printer.LastStatus.MediaLengthMM(),
209
+			)
210
+		}
206 211
 	}
207 212
 
208 213
 	var params = struct {
@@ -269,7 +274,7 @@ func main() {
269 274
 		log.Fatalln(err)
270 275
 	}
271 276
 
272
-	log.Println("Starting server")
277
+	log.Println("starting server")
273 278
 	http.HandleFunc("/", handle)
274 279
 	log.Fatal(http.ListenAndServe(":8080", nil))
275 280
 }

+ 31
- 394
ql/ql.go View File

@@ -8,54 +8,11 @@ package ql
8 8
 //  http://www.undocprint.org/formats/communication_protocols/ieee_1284
9 9
 
10 10
 import (
11
-	"errors"
12
-	"fmt"
13 11
 	"image"
14
-	"io"
15
-	"log"
16
-	"os"
17
-	"path/filepath"
18 12
 	"regexp"
19 13
 	"strings"
20
-	"syscall"
21
-	"time"
22
-	"unsafe"
23 14
 )
24 15
 
25
-// #include <linux/ioctl.h>
26
-import "C"
27
-
28
-// -----------------------------------------------------------------------------
29
-
30
-func _IOC(dir, typ, nr, size int) uintptr {
31
-	return (uintptr(dir) << C._IOC_DIRSHIFT) |
32
-		(uintptr(typ) << C._IOC_TYPESHIFT) |
33
-		(uintptr(nr) << C._IOC_NRSHIFT) |
34
-		(uintptr(size) << C._IOC_SIZESHIFT)
35
-}
36
-
37
-const (
38
-	iocnrGetDeviceID = 1
39
-)
40
-
41
-// lpiocGetDeviceID reads the IEEE-1284 Device ID string of a printer.
42
-func lpiocGetDeviceID(fd uintptr) ([]byte, error) {
43
-	var buf [1024]byte
44
-	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd,
45
-		_IOC(C._IOC_READ, 'P', iocnrGetDeviceID, len(buf)),
46
-		uintptr(unsafe.Pointer(&buf))); err != 0 {
47
-		return nil, err
48
-	}
49
-
50
-	// In theory it might get trimmed along the way.
51
-	length := int(buf[0])<<8 | int(buf[1])
52
-	if 2+length > len(buf) {
53
-		return buf[2:], errors.New("the device ID string got trimmed")
54
-	}
55
-
56
-	return buf[2 : 2+length], nil
57
-}
58
-
59 16
 // -----------------------------------------------------------------------------
60 17
 
61 18
 var deviceIDRegexp = regexp.MustCompile(
@@ -96,14 +53,6 @@ func (id deviceID) FindFirst(key, abbreviation string) string {
96 53
 
97 54
 // -----------------------------------------------------------------------------
98 55
 
99
-type Printer struct {
100
-	File         *os.File
101
-	Manufacturer string
102
-	Model        string
103
-	LastStatus   *Status
104
-	MediaInfo    *MediaInfo
105
-}
106
-
107 56
 func compatible(id deviceID) bool {
108 57
 	for _, commandSet := range id.Find("COMMAND SET", "CMD") {
109 58
 		if commandSet == "PT-CBP" {
@@ -113,118 +62,6 @@ func compatible(id deviceID) bool {
113 62
 	return false
114 63
 }
115 64
 
116
-// Open finds and initializes the first USB printer found supporting
117
-// the appropriate protocol. Returns nil if no printer could be found.
118
-func Open() (*Printer, error) {
119
-	// Linux usblp module, located in /drivers/usb/class/usblp.c
120
-	paths, err := filepath.Glob("/dev/usb/lp[0-9]*")
121
-	if err != nil {
122
-		return nil, err
123
-	}
124
-	for _, candidate := range paths {
125
-		f, err := os.OpenFile(candidate, os.O_RDWR, 0)
126
-		if err != nil {
127
-			continue
128
-		}
129
-		// Filter out obvious non-printers.
130
-		deviceID, err := lpiocGetDeviceID(f.Fd())
131
-		if err != nil {
132
-			f.Close()
133
-			continue
134
-		}
135
-		parsedID := parseIEEE1284DeviceID(deviceID)
136
-		// Filter out printers that wouldn't understand the protocol.
137
-		if !compatible(parsedID) {
138
-			f.Close()
139
-			continue
140
-		}
141
-		return &Printer{
142
-			File:         f,
143
-			Manufacturer: parsedID.FindFirst("MANUFACTURER", "MFG"),
144
-			Model:        parsedID.FindFirst("MODEL", "MDL"),
145
-		}, nil
146
-	}
147
-	return nil, nil
148
-}
149
-
150
-// Initialize initializes the printer for further operations.
151
-func (p *Printer) Initialize() error {
152
-	// Clear the print buffer.
153
-	invalidate := make([]byte, 400)
154
-	if _, err := p.File.Write(invalidate); err != nil {
155
-		return err
156
-	}
157
-
158
-	// Initialize.
159
-	if _, err := p.File.WriteString("\x1b\x40"); err != nil {
160
-		return err
161
-	}
162
-
163
-	// Flush any former responses in the printer's queue.
164
-	//
165
-	// I'm not sure if this is necessary, or rather whether the kernel driver
166
-	// does any buffering that could cause data to be returned at this point.
167
-	/*
168
-		var dummy [32]byte
169
-		for {
170
-			if _, err := f.Read(dummy[:]); err == io.EOF {
171
-				break
172
-			}
173
-		}
174
-	*/
175
-
176
-	return nil
177
-}
178
-
179
-var errTimeout = errors.New("timeout")
180
-var errInvalidRead = errors.New("invalid read")
181
-
182
-// pollStatusBytes waits for the printer to send a status packet and returns
183
-// it as raw data.
184
-func (p *Printer) pollStatusBytes(
185
-	timeout time.Duration) (status [32]byte, err error) {
186
-	start, n := time.Now(), 0
187
-	for {
188
-		if n, err = p.File.Read(status[:]); err == io.EOF {
189
-			time.Sleep(10 * time.Millisecond)
190
-		} else if err != nil {
191
-			return status, err
192
-		} else if n < 32 {
193
-			return status, errInvalidRead
194
-		} else {
195
-			return status, nil
196
-		}
197
-		if time.Now().Sub(start) > timeout {
198
-			return status, errTimeout
199
-		}
200
-	}
201
-}
202
-
203
-// Request new status information from the printer. The printer
204
-// must be in an appropriate mode, i.e. on-line and not currently printing.
205
-func (p *Printer) UpdateStatus() error {
206
-	// Request status information.
207
-	if _, err := p.File.WriteString("\x1b\x69\x53"); err != nil {
208
-		return err
209
-	}
210
-
211
-	// Retrieve status information.
212
-	status, err := p.pollStatusBytes(time.Second)
213
-	if err != nil {
214
-		p.LastStatus = nil
215
-		return err
216
-	}
217
-
218
-	s := Status(status)
219
-	p.LastStatus = &s
220
-	return nil
221
-}
222
-
223
-// Close closes the underlying file.
224
-func (p *Printer) Close() error {
225
-	return p.File.Close()
226
-}
227
-
228 65
 // -----------------------------------------------------------------------------
229 66
 
230 67
 type mediaSize struct {
@@ -278,30 +115,35 @@ func GetMediaInfo(widthMM, lengthMM int) *MediaInfo {
278 115
 
279 116
 // -----------------------------------------------------------------------------
280 117
 
118
+const (
119
+	printBytes = 90
120
+	printPins  = printBytes * 8
121
+)
122
+
281 123
 // makeBitmapData converts an image to the printer's raster format.
282
-func makeBitmapData(src image.Image, offset, length int) (data []byte) {
124
+func makeBitmapData(src image.Image, margin, length int) (data []byte) {
283 125
 	bounds := src.Bounds()
284
-	pixels := [720]bool{}
126
+	if bounds.Dy() > length {
127
+		bounds.Max.Y = bounds.Min.Y + length
128
+	}
129
+	if bounds.Dx() > printPins-margin {
130
+		bounds.Max.X = bounds.Min.X + printPins
131
+	}
132
+
133
+	pixels := [printPins]bool{}
285 134
 	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
286 135
 		length--
287
-		if length <= 0 {
288
-			break
289
-		}
290 136
 
291
-		off := offset
137
+		// The graphics needs to be inverted horizontally, iterating backwards.
138
+		offset := margin
292 139
 		for x := bounds.Max.X - 1; x >= bounds.Min.X; x-- {
293
-			if off >= len(pixels) {
294
-				break
295
-			}
296
-
297
-			// TODO: Anything to do with the ColorModel?
298 140
 			r, g, b, a := src.At(x, y).RGBA()
299
-			pixels[off] = r == 0 && g == 0 && b == 0 && a != 0
300
-			off++
141
+			pixels[offset] = r == 0 && g == 0 && b == 0 && a >= 0x8000
142
+			offset++
301 143
 		}
302 144
 
303
-		data = append(data, 'g', 0x00, 90)
304
-		for i := 0; i < 90; i++ {
145
+		data = append(data, 'g', 0x00, printBytes)
146
+		for i := 0; i < printBytes; i++ {
305 147
 			var b byte
306 148
 			for j := 0; j < 8; j++ {
307 149
 				b <<= 1
@@ -313,17 +155,20 @@ func makeBitmapData(src image.Image, offset, length int) (data []byte) {
313 155
 		}
314 156
 	}
315 157
 	for ; length > 0; length-- {
316
-		data = append(data, 'g', 0x00, 90)
317
-		data = append(data, make([]byte, 90)...)
158
+		data = append(data, 'g', 0x00, printBytes)
159
+		data = append(data, make([]byte, printBytes)...)
318 160
 	}
319 161
 	return
320 162
 }
321 163
 
322
-func (p *Printer) makePrintData(image image.Image) (data []byte) {
164
+func makePrintData(status *Status, image image.Image) (data []byte) {
323 165
 	mediaInfo := GetMediaInfo(
324
-		p.LastStatus.MediaWidthMM(),
325
-		p.LastStatus.MediaLengthMM(),
166
+		status.MediaWidthMM(),
167
+		status.MediaLengthMM(),
326 168
 	)
169
+	if mediaInfo == nil {
170
+		return nil
171
+	}
327 172
 
328 173
 	// Raster mode.
329 174
 	// Should be the only supported mode for QL-800.
@@ -339,12 +184,12 @@ func (p *Printer) makePrintData(image image.Image) (data []byte) {
339 184
 	}
340 185
 
341 186
 	mediaType := byte(0x0a)
342
-	if p.LastStatus.MediaLengthMM() != 0 {
187
+	if status.MediaLengthMM() != 0 {
343 188
 		mediaType = byte(0x0b)
344 189
 	}
345 190
 
346 191
 	data = append(data, 0x1b, 0x69, 0x7a, 0x02|0x04|0x40|0x80, mediaType,
347
-		byte(p.LastStatus.MediaWidthMM()), byte(p.LastStatus.MediaLengthMM()),
192
+		byte(status.MediaWidthMM()), byte(status.MediaLengthMM()),
348 193
 		byte(dy), byte(dy>>8), byte(dy>>16), byte(dy>>24), 0, 0x00)
349 194
 
350 195
 	// Auto cut, each 1 label.
@@ -355,7 +200,7 @@ func (p *Printer) makePrintData(image image.Image) (data []byte) {
355 200
 	// Not sure what it means, doesn't seem to have any effect to turn it off.
356 201
 	data = append(data, 0x1b, 0x69, 0x4b, 0x08)
357 202
 
358
-	if p.LastStatus.MediaLengthMM() != 0 {
203
+	if status.MediaLengthMM() != 0 {
359 204
 		// 3mm margins along the direction of feed. 0x23 = 35 dots, the minimum.
360 205
 		data = append(data, 0x1b, 0x69, 0x64, 0x23, 0x00)
361 206
 	} else {
@@ -373,211 +218,3 @@ func (p *Printer) makePrintData(image image.Image) (data []byte) {
373 218
 	// Print command with feeding.
374 219
 	return append(data, 0x1a)
375 220
 }
376
-
377
-func (p *Printer) Print(image image.Image) error {
378
-	data := p.makePrintData(image)
379
-
380
-	// Print the prepared data.
381
-	if _, err := p.File.Write(data); err != nil {
382
-		return err
383
-	}
384
-
385
-	// TODO: We specifically need to wait for a transition to the receiving
386
-	// state, and try to figure out something from the statuses.
387
-	// We may also receive an error status instead of the transition to
388
-	// the printing state. Or even after it.
389
-	start, b := time.Now(), [32]byte{}
390
-	for {
391
-		if n, err := p.File.Read(b[:]); err == io.EOF {
392
-			time.Sleep(100 * time.Millisecond)
393
-		} else if err != nil {
394
-			return err
395
-		} else if n < 32 {
396
-			return errors.New("invalid read")
397
-		} else {
398
-			status := Status(b)
399
-			log.Printf("status\n%s", &status)
400
-		}
401
-		if time.Now().Sub(start) > 3*time.Second {
402
-			break
403
-		}
404
-	}
405
-	return nil
406
-}
407
-
408
-// -----------------------------------------------------------------------------
409
-
410
-// Status is a decoder for the status packed returned by the printer.
411
-type Status [32]byte
412
-
413
-func (s *Status) MediaWidthMM() int  { return int(s[10]) }
414
-func (s *Status) MediaLengthMM() int { return int(s[17]) }
415
-
416
-func decodeBitfieldErrors(b byte, errors [8]string) []string {
417
-	var result []string
418
-	for i := uint(0); i < 8; i++ {
419
-		if b&(1<<i) != 0 {
420
-			result = append(result, errors[i])
421
-		}
422
-	}
423
-	return result
424
-}
425
-
426
-func (s *Status) Errors() (errors []string) {
427
-	errors = append(errors, decodeBitfieldErrors(s[8], [8]string{
428
-		"no media", "end of media", "cutter jam", "?", "printer in use",
429
-		"printer turned off", "high-voltage adapter", "fan motor error"})...)
430
-	errors = append(errors, decodeBitfieldErrors(s[9], [8]string{
431
-		"replace media", "expansion buffer full", "communication error",
432
-		"communication buffer full", "cover open", "cancel key",
433
-		"media cannot be fed", "system error"})...)
434
-	return
435
-}
436
-
437
-// String implements the Stringer interface.
438
-func (s *Status) String() string {
439
-	var b strings.Builder
440
-	s.Dump(&b)
441
-	return b.String()
442
-}
443
-
444
-// Dump writes the status data to an io.Writer in a human-readable format.
445
-func (s *Status) Dump(f io.Writer) {
446
-	/*
447
-		if s[0] != 0x80 || s[1] != 0x20 || s[2] != 0x42 || s[3] != 0x34 {
448
-			fmt.Fprintln(f, "unexpected status fixed bytes")
449
-		}
450
-	*/
451
-
452
-	// Model code.
453
-	switch m := s[4]; m {
454
-	case 0x38:
455
-		fmt.Fprintln(f, "model: QL-800")
456
-	case 0x39:
457
-		fmt.Fprintln(f, "model: QL-810W")
458
-	case 0x41:
459
-		fmt.Fprintln(f, "model: QL-820NWB")
460
-	case 0x43:
461
-		fmt.Fprintln(f, "model: QL-1100")
462
-	case 0x44:
463
-		fmt.Fprintln(f, "model: QL-1110NWB")
464
-	case 0x45:
465
-		fmt.Fprintln(f, "model: QL-1115NWB")
466
-	default:
467
-		fmt.Fprintln(f, "model:", m)
468
-	}
469
-
470
-	/*
471
-		// s[6] seems to be 0x00 in a real-world QL-800, as in QL-1100 docs.
472
-		if s[5] != 0x30 || s[6] != 0x30 || s[7] != 0x00 {
473
-			fmt.Fprintln(f, "unexpected status fixed bytes")
474
-		}
475
-	*/
476
-
477
-	// Error information 1.
478
-	for _, e := range decodeBitfieldErrors(s[8], [8]string{
479
-		"no media", "end of media", "cutter jam", "?", "printer in use",
480
-		"printer turned off", "high-voltage adapter", "fan motor error"}) {
481
-		fmt.Fprintln(f, "error 1:", e)
482
-	}
483
-
484
-	// Error information 2.
485
-	for _, e := range decodeBitfieldErrors(s[9], [8]string{
486
-		"replace media", "expansion buffer full", "communication error",
487
-		"communication buffer full", "cover open", "cancel key",
488
-		"media cannot be fed", "system error"}) {
489
-		fmt.Fprintln(f, "error 2:", e)
490
-	}
491
-
492
-	// Media width.
493
-	fmt.Fprintln(f, "media width:", s[10], "mm")
494
-
495
-	// Media type.
496
-	switch t := s[11]; t {
497
-	case 0x00:
498
-		fmt.Fprintln(f, "media: no media")
499
-	case 0x4a, 0x0a: // 0x4a = J, in reality we get 0x0a, as in QL-1100 docs.
500
-		fmt.Fprintln(f, "media: continuous length tape")
501
-	case 0x4b, 0x0b: // 0x4b = K, in reality we get 0x0b, as in QL-1100 docs.
502
-		fmt.Fprintln(f, "media: die-cut labels")
503
-	default:
504
-		fmt.Fprintln(f, "media:", t)
505
-	}
506
-
507
-	/*
508
-		// In a real-world QL-800, s[14] seems to be:
509
-		//  0x01 with die-cut 29mm long labels,
510
-		//  0x14 with 29mm tape,
511
-		//  0x23 with red-black 62mm tape,
512
-		// and directly corresponds to physical pins on the tape.
513
-		if s[12] != 0x00 || s[13] != 0x00 || s[14] != 0x3f {
514
-			fmt.Fprintln(f, "unexpected status fixed bytes")
515
-		}
516
-	*/
517
-
518
-	// Mode.
519
-	fmt.Fprintln(f, "mode:", s[15])
520
-
521
-	/*
522
-		if s[16] != 0x00 {
523
-			fmt.Fprintln(f, "unexpected status fixed bytes")
524
-		}
525
-	*/
526
-
527
-	// Media length.
528
-	fmt.Fprintln(f, "media length:", s[17], "mm")
529
-
530
-	// Status type.
531
-	switch t := s[18]; t {
532
-	case 0x00:
533
-		fmt.Fprintln(f, "status type: reply to status request")
534
-	case 0x01:
535
-		fmt.Fprintln(f, "status type: printing completed")
536
-	case 0x02:
537
-		fmt.Fprintln(f, "status type: error occurred")
538
-	case 0x04:
539
-		fmt.Fprintln(f, "status type: turned off")
540
-	case 0x05:
541
-		fmt.Fprintln(f, "status type: notification")
542
-	case 0x06:
543
-		fmt.Fprintln(f, "status type: phase change")
544
-	default:
545
-		fmt.Fprintln(f, "status type:", t)
546
-	}
547
-
548
-	// Phase type.
549
-	switch t := s[19]; t {
550
-	case 0x00:
551
-		fmt.Fprintln(f, "phase state: receiving state")
552
-	case 0x01:
553
-		fmt.Fprintln(f, "phase state: printing state")
554
-	default:
555
-		fmt.Fprintln(f, "phase state:", t)
556
-	}
557
-
558
-	// Phase number.
559
-	fmt.Fprintln(f, "phase number:", int(s[20])*256+int(s[21]))
560
-
561
-	// Notification number.
562
-	switch n := s[22]; n {
563
-	case 0x00:
564
-		fmt.Fprintln(f, "notification number: not available")
565
-	case 0x03:
566
-		fmt.Fprintln(f, "notification number: cooling (started)")
567
-	case 0x04:
568
-		fmt.Fprintln(f, "notification number: cooling (finished)")
569
-	default:
570
-		fmt.Fprintln(f, "notification number:", n)
571
-	}
572
-
573
-	/*
574
-		// In a real-world QL-800, s[25] seems to be:
575
-		//  0x01 with 29mm tape or die-cut 29mm long labels,
576
-		//  0x81 with red-black 62mm tape.
577
-		if s[23] != 0x00 || s[24] != 0x00 || s[25] != 0x00 || s[26] != 0x00 ||
578
-			s[27] != 0x00 || s[28] != 0x00 || s[29] != 0x00 || s[30] != 0x00 ||
579
-			s[31] != 0x00 {
580
-			fmt.Fprintln(f, "unexpected status fixed bytes")
581
-		}
582
-	*/
583
-}

+ 212
- 0
ql/ql_linux.go View File

@@ -0,0 +1,212 @@
1
+package ql
2
+
3
+import (
4
+	"errors"
5
+	"image"
6
+	"io"
7
+	"os"
8
+	"path/filepath"
9
+	"syscall"
10
+	"time"
11
+	"unsafe"
12
+)
13
+
14
+// #include <linux/ioctl.h>
15
+import "C"
16
+
17
+// -----------------------------------------------------------------------------
18
+
19
+func _IOC(dir, typ, nr, size int) uintptr {
20
+	return (uintptr(dir) << C._IOC_DIRSHIFT) |
21
+		(uintptr(typ) << C._IOC_TYPESHIFT) |
22
+		(uintptr(nr) << C._IOC_NRSHIFT) |
23
+		(uintptr(size) << C._IOC_SIZESHIFT)
24
+}
25
+
26
+const (
27
+	iocnrGetDeviceID = 1
28
+)
29
+
30
+// lpiocGetDeviceID reads the IEEE-1284 Device ID string of a printer.
31
+func lpiocGetDeviceID(fd uintptr) ([]byte, error) {
32
+	var buf [1024]byte
33
+	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd,
34
+		_IOC(C._IOC_READ, 'P', iocnrGetDeviceID, len(buf)),
35
+		uintptr(unsafe.Pointer(&buf))); err != 0 {
36
+		return nil, err
37
+	}
38
+
39
+	// In theory it might get trimmed along the way.
40
+	length := int(buf[0])<<8 | int(buf[1])
41
+	if 2+length > len(buf) {
42
+		return buf[2:], errors.New("the device ID string got trimmed")
43
+	}
44
+
45
+	return buf[2 : 2+length], nil
46
+}
47
+
48
+// -----------------------------------------------------------------------------
49
+
50
+type Printer struct {
51
+	File         *os.File
52
+	Manufacturer string
53
+	Model        string
54
+
55
+	LastStatus *Status
56
+	MediaInfo  *MediaInfo
57
+
58
+	// StatusNotify is called whenever we receive a status packet.
59
+	StatusNotify func(*Status)
60
+}
61
+
62
+// Open finds and initializes the first USB printer found supporting
63
+// the appropriate protocol. Returns nil if no printer could be found.
64
+func Open() (*Printer, error) {
65
+	// Linux usblp module, located in /drivers/usb/class/usblp.c
66
+	paths, err := filepath.Glob("/dev/usb/lp[0-9]*")
67
+	if err != nil {
68
+		return nil, err
69
+	}
70
+	for _, candidate := range paths {
71
+		f, err := os.OpenFile(candidate, os.O_RDWR, 0)
72
+		if err != nil {
73
+			continue
74
+		}
75
+		// Filter out obvious non-printers.
76
+		deviceID, err := lpiocGetDeviceID(f.Fd())
77
+		if err != nil {
78
+			f.Close()
79
+			continue
80
+		}
81
+		parsedID := parseIEEE1284DeviceID(deviceID)
82
+		// Filter out printers that wouldn't understand the protocol.
83
+		if !compatible(parsedID) {
84
+			f.Close()
85
+			continue
86
+		}
87
+		return &Printer{
88
+			File:         f,
89
+			Manufacturer: parsedID.FindFirst("MANUFACTURER", "MFG"),
90
+			Model:        parsedID.FindFirst("MODEL", "MDL"),
91
+		}, nil
92
+	}
93
+	return nil, nil
94
+}
95
+
96
+// Initialize initializes the printer for further operations.
97
+func (p *Printer) Initialize() error {
98
+	// Clear the print buffer.
99
+	invalidate := make([]byte, 400)
100
+	if _, err := p.File.Write(invalidate); err != nil {
101
+		return err
102
+	}
103
+
104
+	// Initialize.
105
+	if _, err := p.File.WriteString("\x1b\x40"); err != nil {
106
+		return err
107
+	}
108
+
109
+	// Flush any former responses in the printer's queue.
110
+	//
111
+	// I'm not sure if this is necessary, or rather whether the kernel driver
112
+	// does any buffering that could cause data to be returned at this point.
113
+	/*
114
+		var dummy [32]byte
115
+		for {
116
+			if _, err := f.Read(dummy[:]); err == io.EOF {
117
+				break
118
+			}
119
+		}
120
+	*/
121
+
122
+	return nil
123
+}
124
+
125
+var errTimeout = errors.New("timeout")
126
+var errInvalidRead = errors.New("invalid read")
127
+
128
+func (p *Printer) updateStatus(status Status) {
129
+	p.LastStatus = &status
130
+	if p.StatusNotify != nil {
131
+		p.StatusNotify(p.LastStatus)
132
+	}
133
+}
134
+
135
+// pollStatusBytes waits for the printer to send a status packet and returns
136
+// it as raw data.
137
+func (p *Printer) pollStatusBytes(
138
+	timeout time.Duration) (*Status, error) {
139
+	start, buf := time.Now(), [32]byte{}
140
+	for {
141
+		if n, err := p.File.Read(buf[:]); err == io.EOF {
142
+			time.Sleep(10 * time.Millisecond)
143
+		} else if err != nil {
144
+			return nil, err
145
+		} else if n < 32 {
146
+			return nil, errInvalidRead
147
+		} else {
148
+			p.updateStatus(Status(buf))
149
+			return p.LastStatus, nil
150
+		}
151
+		if time.Now().Sub(start) > timeout {
152
+			return nil, errTimeout
153
+		}
154
+	}
155
+}
156
+
157
+// Request new status information from the printer. The printer
158
+// must be in an appropriate mode, i.e. on-line and not currently printing.
159
+func (p *Printer) UpdateStatus() error {
160
+	// Request status information.
161
+	if _, err := p.File.WriteString("\x1b\x69\x53"); err != nil {
162
+		return err
163
+	}
164
+
165
+	// Retrieve status information.
166
+	if _, err := p.pollStatusBytes(time.Second); err != nil {
167
+		p.LastStatus = nil
168
+		return err
169
+	}
170
+	return nil
171
+}
172
+
173
+var errErrorOccurred = errors.New("error occurred")
174
+var errUnexpectedStatus = errors.New("unexpected status")
175
+var errUnknownMedia = errors.New("unknown media")
176
+
177
+func (p *Printer) Print(image image.Image) error {
178
+	data := makePrintData(p.LastStatus, image)
179
+	if data == nil {
180
+		return errUnknownMedia
181
+	}
182
+	if _, err := p.File.Write(data); err != nil {
183
+		return err
184
+	}
185
+
186
+	// See diagrams: we may receive an error status instead of the transition
187
+	// to the printing state. Or even after it.
188
+	//
189
+	// Not sure how exactly cooling behaves and I don't want to test it.
190
+	for {
191
+		status, err := p.pollStatusBytes(10 * time.Second)
192
+		if err != nil {
193
+			return err
194
+		}
195
+
196
+		switch status.Type() {
197
+		case StatusTypePhaseChange:
198
+			// Nothing to do.
199
+		case StatusTypePrintingCompleted:
200
+			return nil
201
+		case StatusTypeErrorOccurred:
202
+			return errErrorOccurred
203
+		default:
204
+			return errUnexpectedStatus
205
+		}
206
+	}
207
+}
208
+
209
+// Close closes the underlying file.
210
+func (p *Printer) Close() error {
211
+	return p.File.Close()
212
+}

+ 206
- 0
ql/status.go View File

@@ -0,0 +1,206 @@
1
+package ql
2
+
3
+import (
4
+	"fmt"
5
+	"io"
6
+	"strings"
7
+)
8
+
9
+// Status is a decoder for the status packed returned by the printer.
10
+type Status [32]byte
11
+
12
+func (s *Status) MediaWidthMM() int  { return int(s[10]) }
13
+func (s *Status) MediaLengthMM() int { return int(s[17]) }
14
+
15
+type StatusType byte
16
+
17
+const (
18
+	StatusTypeReplyToRequest    StatusType = 0x00
19
+	StatusTypePrintingCompleted            = 0x01
20
+	StatusTypeErrorOccurred                = 0x02
21
+	StatusTypeTurnedOff                    = 0x04
22
+	StatusTypeNotification                 = 0x05
23
+	StatusTypePhaseChange                  = 0x06
24
+)
25
+
26
+func (s *Status) Type() StatusType { return StatusType(s[18]) }
27
+
28
+type StatusPhase byte
29
+
30
+const (
31
+	StatusPhaseReceiving StatusPhase = 0x00
32
+	StatusPhasePrinting              = 0x01
33
+)
34
+
35
+func (s *Status) Phase() StatusPhase { return StatusPhase(s[19]) }
36
+
37
+func decodeBitfieldErrors(b byte, errors [8]string) []string {
38
+	var result []string
39
+	for i := uint(0); i < 8; i++ {
40
+		if b&(1<<i) != 0 {
41
+			result = append(result, errors[i])
42
+		}
43
+	}
44
+	return result
45
+}
46
+
47
+func (s *Status) Errors() (errors []string) {
48
+	errors = append(errors, decodeBitfieldErrors(s[8], [8]string{
49
+		"no media", "end of media", "cutter jam", "?", "printer in use",
50
+		"printer turned off", "high-voltage adapter", "fan motor error"})...)
51
+	errors = append(errors, decodeBitfieldErrors(s[9], [8]string{
52
+		"replace media", "expansion buffer full", "communication error",
53
+		"communication buffer full", "cover open", "cancel key",
54
+		"media cannot be fed", "system error"})...)
55
+	return
56
+}
57
+
58
+// -----------------------------------------------------------------------------
59
+
60
+// String implements the Stringer interface.
61
+func (s *Status) String() string {
62
+	var b strings.Builder
63
+	s.Dump(&b)
64
+	return b.String()
65
+}
66
+
67
+// Dump writes the status data to an io.Writer in a human-readable format.
68
+func (s *Status) Dump(f io.Writer) {
69
+	/*
70
+		if s[0] != 0x80 || s[1] != 0x20 || s[2] != 0x42 || s[3] != 0x34 {
71
+			fmt.Fprintln(f, "unexpected status fixed bytes")
72
+		}
73
+	*/
74
+
75
+	// Model code.
76
+	switch m := s[4]; m {
77
+	case 0x38:
78
+		fmt.Fprintln(f, "model: QL-800")
79
+	case 0x39:
80
+		fmt.Fprintln(f, "model: QL-810W")
81
+	case 0x41:
82
+		fmt.Fprintln(f, "model: QL-820NWB")
83
+	case 0x43:
84
+		fmt.Fprintln(f, "model: QL-1100")
85
+	case 0x44:
86
+		fmt.Fprintln(f, "model: QL-1110NWB")
87
+	case 0x45:
88
+		fmt.Fprintln(f, "model: QL-1115NWB")
89
+	default:
90
+		fmt.Fprintln(f, "model:", m)
91
+	}
92
+
93
+	/*
94
+		// s[6] seems to be 0x00 in a real-world QL-800, as in QL-1100 docs.
95
+		if s[5] != 0x30 || s[6] != 0x30 || s[7] != 0x00 {
96
+			fmt.Fprintln(f, "unexpected status fixed bytes")
97
+		}
98
+	*/
99
+
100
+	// Error information 1.
101
+	for _, e := range decodeBitfieldErrors(s[8], [8]string{
102
+		"no media", "end of media", "cutter jam", "?", "printer in use",
103
+		"printer turned off", "high-voltage adapter", "fan motor error"}) {
104
+		fmt.Fprintln(f, "error 1:", e)
105
+	}
106
+
107
+	// Error information 2.
108
+	for _, e := range decodeBitfieldErrors(s[9], [8]string{
109
+		"replace media", "expansion buffer full", "communication error",
110
+		"communication buffer full", "cover open", "cancel key",
111
+		"media cannot be fed", "system error"}) {
112
+		fmt.Fprintln(f, "error 2:", e)
113
+	}
114
+
115
+	// Media width.
116
+	fmt.Fprintln(f, "media width:", s[10], "mm")
117
+
118
+	// Media type.
119
+	switch t := s[11]; t {
120
+	case 0x00:
121
+		fmt.Fprintln(f, "media: no media")
122
+	case 0x4a, 0x0a: // 0x4a = J, in reality we get 0x0a, as in QL-1100 docs.
123
+		fmt.Fprintln(f, "media: continuous length tape")
124
+	case 0x4b, 0x0b: // 0x4b = K, in reality we get 0x0b, as in QL-1100 docs.
125
+		fmt.Fprintln(f, "media: die-cut labels")
126
+	default:
127
+		fmt.Fprintln(f, "media:", t)
128
+	}
129
+
130
+	/*
131
+		// In a real-world QL-800, s[14] seems to be:
132
+		//  0x01 with die-cut 29mm long labels,
133
+		//  0x14 with 29mm tape,
134
+		//  0x23 with red-black 62mm tape,
135
+		// and directly corresponds to physical pins on the tape.
136
+		if s[12] != 0x00 || s[13] != 0x00 || s[14] != 0x3f {
137
+			fmt.Fprintln(f, "unexpected status fixed bytes")
138
+		}
139
+	*/
140
+
141
+	// Mode.
142
+	fmt.Fprintln(f, "mode:", s[15])
143
+
144
+	/*
145
+		if s[16] != 0x00 {
146
+			fmt.Fprintln(f, "unexpected status fixed bytes")
147
+		}
148
+	*/
149
+
150
+	// Media length.
151
+	fmt.Fprintln(f, "media length:", s[17], "mm")
152
+
153
+	// Status type.
154
+	switch t := s[18]; t {
155
+	case 0x00:
156
+		fmt.Fprintln(f, "status type: reply to status request")
157
+	case 0x01:
158
+		fmt.Fprintln(f, "status type: printing completed")
159
+	case 0x02:
160
+		fmt.Fprintln(f, "status type: error occurred")
161
+	case 0x04:
162
+		fmt.Fprintln(f, "status type: turned off")
163
+	case 0x05:
164
+		fmt.Fprintln(f, "status type: notification")
165
+	case 0x06:
166
+		fmt.Fprintln(f, "status type: phase change")
167
+	default:
168
+		fmt.Fprintln(f, "status type:", t)
169
+	}
170
+
171
+	// Phase type.
172
+	switch t := s[19]; t {
173
+	case 0x00:
174
+		fmt.Fprintln(f, "phase state: receiving state")
175
+	case 0x01:
176
+		fmt.Fprintln(f, "phase state: printing state")
177
+	default:
178
+		fmt.Fprintln(f, "phase state:", t)
179
+	}
180
+
181
+	// Phase number.
182
+	fmt.Fprintln(f, "phase number:", int(s[20])*256+int(s[21]))
183
+
184
+	// Notification number.
185
+	switch n := s[22]; n {
186
+	case 0x00:
187
+		fmt.Fprintln(f, "notification number: not available")
188
+	case 0x03:
189
+		fmt.Fprintln(f, "notification number: cooling (started)")
190
+	case 0x04:
191
+		fmt.Fprintln(f, "notification number: cooling (finished)")
192
+	default:
193
+		fmt.Fprintln(f, "notification number:", n)
194
+	}
195
+
196
+	/*
197
+		// In a real-world QL-800, s[25] seems to be:
198
+		//  0x01 with 29mm tape or die-cut 29mm long labels,
199
+		//  0x81 with red-black 62mm tape.
200
+		if s[23] != 0x00 || s[24] != 0x00 || s[25] != 0x00 || s[26] != 0x00 ||
201
+			s[27] != 0x00 || s[28] != 0x00 || s[29] != 0x00 || s[30] != 0x00 ||
202
+			s[31] != 0x00 {
203
+			fmt.Fprintln(f, "unexpected status fixed bytes")
204
+		}
205
+	*/
206
+}

Loading…
Cancel
Save