├── go.mod ├── .gitignore ├── Makefile ├── README.md ├── go.sum ├── LICENSE ├── scanner └── scanner.go ├── main.go └── code ├── code_test.go └── code.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pteich/usbsymbolreader 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/pteich/hid v1.0.1-0.20190728155042-d965915879b9 7 | github.com/stretchr/testify v1.3.0 8 | ) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: build 2 | 3 | clean: 4 | rm -f ./codescanner 5 | 6 | build: 7 | CGO_ENABLED=1 \ 8 | go build -a -o codescanner *.go 9 | 10 | build-windows: 11 | CGO_ENABLED=1 \ 12 | GOARCH=amd64 \ 13 | GOOS=windows \ 14 | CC=x86_64-w64-mingw32-gcc \ 15 | go build -a -o codescanner.exe *.go 16 | 17 | build-linux: 18 | CGO_ENABLED=1 \ 19 | GOARCH=amd64 \ 20 | GOOS=linux \ 21 | CC=gcc \ 22 | go build -a -o codescanner.linux *.go 23 | 24 | test: 25 | go test ./... 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USB Symbol Scanner Reader 2 | 3 | This Go program connects to any barcode scanner (generic symbol scanner) that runs in 4 | IBM Hand Held mode, IBM Table Top mode or OPOS Hand Held mode. It uses an forked version of the Go USB HID library https://github.com/karalabe/hid. 5 | 6 | It runs on OS X (tested with Mojave), Windows (tested with Windows 10 64Bit) and should run on Linux (untested). 7 | 8 | No drivers are needed for symbol scanners. It's tested with Zebra DS3678 scanner. 9 | 10 | While running it detects new devices or detached devices. It connects to the first symbol scanner found. 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/pteich/hid v1.0.1-0.20190728155042-d965915879b9 h1:SxlR4hLWQOhVFsNLzCXFR01AlNSkF4NzFQiUFzAuqy4= 6 | github.com/pteich/hid v1.0.1-0.20190728155042-d965915879b9/go.mod h1:232wN5fzL16zquJN7VCIF7RXDsZDidGV6rSULyjmmfY= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 9 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Peter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scanner/scanner.go: -------------------------------------------------------------------------------- 1 | package scanner 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/pteich/hid" 7 | "github.com/pteich/usbsymbolreader/code" 8 | ) 9 | 10 | // Scanner describes the symbol scanner device 11 | type Scanner struct { 12 | device *hid.Device 13 | } 14 | 15 | // New takes a HID device info and returns a Scanner with the opened device 16 | func New(deviceInfo hid.DeviceInfo) (*Scanner, error) { 17 | 18 | device, err := deviceInfo.Open() 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return &Scanner{ 24 | device: device, 25 | }, nil 26 | } 27 | 28 | // ReadCodes starts a code read loop and returns a channel that recieves new codes 29 | func (s *Scanner) ReadCodes(ctx context.Context) <-chan *code.Code { 30 | 31 | scanCtx, cancel := context.WithCancel(ctx) 32 | 33 | codeChan := make(chan *code.Code) 34 | go func() { 35 | defer close(codeChan) 36 | for { 37 | buf := make([]byte, 255) 38 | n, err := s.device.ReadTimeout(buf, 500) 39 | if err != nil { 40 | cancel() 41 | return 42 | } 43 | 44 | if n > 0 { 45 | fmt.Println(buf) 46 | scannedCode, err := code.New(buf) 47 | if err != nil { 48 | continue 49 | } 50 | 51 | codeChan <- scannedCode 52 | } 53 | 54 | select { 55 | case <-scanCtx.Done(): 56 | s.device.Close() 57 | return 58 | default: 59 | } 60 | } 61 | }() 62 | 63 | return codeChan 64 | } 65 | 66 | // Device returns the real device 67 | func (s *Scanner) Device() *hid.Device { 68 | return s.device 69 | } 70 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/pteich/hid" 6 | "github.com/pteich/usbsymbolreader/scanner" 7 | "log" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | 16 | // create main context 17 | ctx, done := context.WithCancel(context.Background()) 18 | 19 | // listen for system signals 20 | signalChannel := make(chan os.Signal, 1) 21 | signal.Notify(signalChannel, os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) 22 | go func() { 23 | select { 24 | case <-signalChannel: 25 | log.Print("shutdown signal received") 26 | done() 27 | } 28 | }() 29 | 30 | // main loop to enumerate USB devices and start reading from it 31 | for { 32 | 33 | log.Print("searching for USB devices...") 34 | devices := hid.Enumerate(0, 0) 35 | log.Printf("found %d devices", len(devices)) 36 | 37 | for _, deviceInfo := range devices { 38 | log.Printf("found %s by %s . VendorID %d - ProductId %d", deviceInfo.Product, deviceInfo.Manufacturer, deviceInfo.VendorID, deviceInfo.ProductID) 39 | 40 | if deviceInfo.VendorID == 1504 || deviceInfo.Product == "Symbol Bar Code Scanner" { 41 | 42 | symbolScanner, err := scanner.New(deviceInfo) 43 | if err != nil { 44 | log.Print(err) 45 | break 46 | } 47 | 48 | log.Printf("connected to %s (Serial No. %s)", deviceInfo.Product, deviceInfo.Serial) 49 | 50 | codes := symbolScanner.ReadCodes(ctx) 51 | for code := range codes { 52 | // TODO safe to file 53 | log.Printf("scanned code: %s", code.String()) 54 | } 55 | 56 | break 57 | } 58 | } 59 | 60 | select { 61 | case <-ctx.Done(): 62 | log.Print("shutting down") 63 | return 64 | case <-time.After(1 * time.Second): 65 | } 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /code/code_test.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestNew(t *testing.T) { 9 | 10 | testData := []struct { 11 | name string 12 | input []byte 13 | expected string 14 | codeType SymbolType 15 | }{ 16 | {name: "testcase 1 code39", input: []byte{9, 16, 3, 0, 81, 49, 0, 10, 11}, expected: "Q1", codeType: Code39}, 17 | {name: "testcase 2 code39", input: []byte{9, 16, 3, 0, 67, 78, 0, 10, 11}, expected: "CN", codeType: Code39}, 18 | {name: "testcase 3 code39", input: []byte{15, 16, 3, 0, 68, 49, 50, 77, 65, 82, 49, 57, 0, 10, 11}, expected: "D12MAR19", codeType: Code39}, 19 | {name: "testcase 4 ean13 ascii", input: []byte{18, 16, 3, 0, 51, 53, 55, 52, 54, 54, 49, 52, 48, 52, 50, 54, 52, 22}, expected: "3574661404264", codeType: EAN13}, 20 | {name: "testcase 5 ean13 ascii", input: []byte{18, 16, 3, 0, 52, 48, 50, 54, 54, 48, 48, 56, 57, 49, 54, 48, 56, 22}, expected: "4026600891608", codeType: EAN13}, 21 | {name: "testcase 6 ean13 numbers", input: []byte{18, 16, 3, 0, 3, 5, 7, 4, 6, 6, 1, 4, 0, 4, 2, 6, 4, 22}, expected: "3574661404264", codeType: EAN13}, 22 | {name: "testcase 7 datamatrix", input: []byte{44, 16, 3, 0, 66, 67, 83, 84, 85, 81, 66, 77, 84, 90, 70, 70, 66, 66, 71, 73, 68, 83, 52, 73, 75, 77, 82, 70, 90, 77, 124, 65, 49, 56, 48, 55, 49, 56, 48, 49, 124, 0, 50, 11}, expected: "BCSTUQBMTZFFBBGIDS4IKMRFZM|A18071801|", codeType: DataMatrix}, 23 | {name: "testcase 8 datamatrix", input: []byte{20, 16, 3, 0, 65, 66, 67, 32, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 50, 11}, expected: "ABC 123456789", codeType: DataMatrix}, 24 | } 25 | 26 | for _, test := range testData { 27 | 28 | t.Run(test.name, func(t *testing.T) { 29 | scannedCode, err := New(test.input) 30 | assert.NoError(t, err) 31 | assert.Equal(t, test.expected, scannedCode.String()) 32 | assert.Equal(t, test.codeType, scannedCode.Type) 33 | t.Log(scannedCode.IsNumeric) 34 | }) 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /code/code.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "strconv" 7 | ) 8 | 9 | // SymbolType describes the type of the scanned symbol 10 | type SymbolType int 11 | 12 | const ( 13 | Code128 SymbolType = iota 14 | Code39 15 | Code2of5 16 | Codabar 17 | DataMatrix 18 | EAN8 19 | EAN13 20 | UPCA 21 | UPCE 22 | PDF417 23 | PostNet37 24 | Undefined 25 | ) 26 | 27 | // Code defines a scanned code 28 | type Code struct { 29 | input []byte 30 | data bytes.Buffer 31 | bufferLength byte 32 | IsNumeric bool 33 | Type SymbolType 34 | } 35 | 36 | // New returns a new Code struct from a scanned byte array 37 | func New(input []byte) (*Code, error) { 38 | 39 | bufferLength := input[0] 40 | if bufferLength == 0 { 41 | return nil, errors.New("zero length code") 42 | } 43 | 44 | if int(bufferLength) > len(input) { 45 | return nil, errors.New("input buffer length exceeded") 46 | } 47 | 48 | code := &Code{ 49 | input: input[1:bufferLength], 50 | } 51 | 52 | code.IsNumeric = input[bufferLength-1] != 11 53 | var offset byte = 2 54 | if code.IsNumeric { 55 | offset = 1 56 | } 57 | 58 | symbolType := input[bufferLength-offset] 59 | switch symbolType { 60 | case 24: 61 | if code.IsNumeric { 62 | code.Type = Undefined 63 | } else { 64 | code.Type = Code128 65 | } 66 | break 67 | case 10: 68 | if code.IsNumeric { 69 | code.Type = UPCE 70 | } else { 71 | code.Type = Code39 72 | } 73 | break 74 | case 13: 75 | if code.IsNumeric { 76 | code.Type = UPCA 77 | } else { 78 | code.Type = Code2of5 79 | } 80 | break 81 | case 14: 82 | if code.IsNumeric { 83 | code.Type = Undefined 84 | } else { 85 | code.Type = Codabar 86 | } 87 | break 88 | case 22: 89 | if code.IsNumeric { 90 | code.Type = EAN13 91 | } else { 92 | code.Type = Undefined 93 | } 94 | break 95 | case 37: 96 | if code.IsNumeric { 97 | code.Type = Undefined 98 | } else { 99 | code.Type = PostNet37 100 | } 101 | break 102 | case 46: 103 | if code.IsNumeric { 104 | code.Type = Undefined 105 | } else { 106 | code.Type = PDF417 107 | } 108 | break 109 | case 50: 110 | if code.IsNumeric { 111 | code.Type = Undefined 112 | } else { 113 | code.Type = DataMatrix 114 | } 115 | break 116 | case 12: 117 | if code.IsNumeric { 118 | code.Type = Undefined 119 | } else { 120 | code.Type = EAN8 121 | } 122 | break 123 | default: 124 | code.Type = Undefined 125 | break 126 | } 127 | 128 | codeBufferEnd := bufferLength - 4 129 | if code.IsNumeric { 130 | codeBufferEnd = bufferLength - 2 131 | } 132 | 133 | for i := 3; i < int(codeBufferEnd); i++ { 134 | if code.IsNumeric && code.Type != UPCE { 135 | if code.input[i] < 32 { 136 | code.data.WriteString(strconv.Itoa(int(code.input[i]))) 137 | } else { 138 | code.data.WriteString(string(code.input[i])) 139 | } 140 | } else { 141 | code.data.WriteByte(code.input[i]) 142 | } 143 | } 144 | 145 | return code, nil 146 | } 147 | 148 | // String is the string representation of the code 149 | func (code *Code) String() string { 150 | return code.data.String() 151 | } 152 | 153 | // Bytes is the byte array representation of the code 154 | func (code *Code) Bytes() []byte { 155 | return code.data.Bytes() 156 | } 157 | --------------------------------------------------------------------------------