136 lines
3.4 KiB
Go
136 lines
3.4 KiB
Go
package catprinter
|
|
|
|
import (
|
|
"context"
|
|
"github.com/go-ble/ble"
|
|
"github.com/pkg/errors"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// For some reason, bleak reports the 0xaf30 service on my macOS, while it reports
|
|
// 0xae30 (which I believe is correct) on my Raspberry Pi. This hacky workaround
|
|
// should cover both cases.
|
|
|
|
var possibleServiceUuids = []string{
|
|
"ae30",
|
|
"af30",
|
|
}
|
|
|
|
const txCharacteristicUuid = "ae01"
|
|
|
|
const scanTimeout = 10 * time.Second
|
|
|
|
const waitAfterEachChunkS = 20 * time.Millisecond
|
|
|
|
const waitAfterDataSentS = 30 * time.Second
|
|
|
|
func chunkify(data []byte, chunkSize int) [][]byte {
|
|
var chunks [][]byte
|
|
for i := 0; i < len(data); i += chunkSize {
|
|
end := i + chunkSize
|
|
if end > len(data) {
|
|
end = len(data)
|
|
}
|
|
chunks = append(chunks, data[i:end])
|
|
}
|
|
return chunks
|
|
}
|
|
|
|
func (c *Client) writeData(data []byte) error {
|
|
|
|
chunks := chunkify(data, c.chunkSize)
|
|
c.log("Sending %d chunks of size %d...", len(chunks), c.chunkSize)
|
|
for i, chunk := range chunks {
|
|
err := c.printer.WriteCharacteristic(c.characteristic, chunk, true)
|
|
if err != nil {
|
|
return errors.Wrap(err, "writing to characteristic, chunk "+strconv.Itoa(i))
|
|
}
|
|
time.Sleep(waitAfterEachChunkS)
|
|
}
|
|
c.log("All sent.")
|
|
|
|
return nil
|
|
}
|
|
|
|
// ScanDevices scans for a device with the given name, or auto discovers it based on characteristics (not implemented yet).
|
|
// Returns a map with MACs as key, and device names as values.
|
|
func (c *Client) ScanDevices(name string) (map[string]string, error) {
|
|
|
|
var devices = make(map[string]string)
|
|
var found []string
|
|
|
|
c.log("Looking for a BLE device named %s", name)
|
|
|
|
ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), c.Timeout))
|
|
|
|
err := ble.Scan(ctx, true, func(a ble.Advertisement) {
|
|
if strings.Contains(strings.Join(found, " "), a.Addr().String()) {
|
|
return
|
|
}
|
|
found = append(found, a.Addr().String())
|
|
if strings.Contains(strings.ToLower(a.LocalName()), strings.ToLower(name)) {
|
|
devices[a.Addr().String()] = a.LocalName()
|
|
c.log("Matches %s %s", a.Addr().String(), a.LocalName())
|
|
return
|
|
}
|
|
c.log("No match %s %s", a.Addr().String(), a.LocalName())
|
|
}, nil)
|
|
|
|
switch errors.Cause(err) {
|
|
case nil:
|
|
case context.DeadlineExceeded:
|
|
c.log("Timeout: scan completed")
|
|
case context.Canceled:
|
|
c.log("Scan was canceled")
|
|
default:
|
|
return nil, errors.Wrap(err, "failed scan")
|
|
}
|
|
|
|
if len(devices) < 0 {
|
|
return nil, ErrPrinterNotFound
|
|
}
|
|
return devices, nil
|
|
|
|
}
|
|
|
|
// Connect establishes a BLE connection to a printer by MAC address.
|
|
func (c *Client) Connect(mac string) error {
|
|
|
|
ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), c.Timeout))
|
|
connect, err := ble.Dial(ctx, ble.NewAddr(mac))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
profile, err := connect.DiscoverProfile(true)
|
|
if err != nil {
|
|
return errors.Wrap(err, "discovering profile")
|
|
}
|
|
|
|
var char *ble.Characteristic
|
|
for _, service := range profile.Services {
|
|
c.log("service %s", service.UUID.String())
|
|
for _, characteristic := range service.Characteristics {
|
|
c.log(" %s", characteristic.UUID.String())
|
|
if characteristic.UUID.Equal(ble.MustParse(txCharacteristicUuid)) &&
|
|
strings.Contains(strings.Join(possibleServiceUuids, " "), service.UUID.String()) {
|
|
c.log(" found characteristic!")
|
|
char = characteristic
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if char == nil {
|
|
return ErrMissingCharacteristic
|
|
}
|
|
|
|
c.characteristic = char
|
|
c.printer = connect
|
|
c.chunkSize = c.printer.Conn().RxMTU() - 3
|
|
return nil
|
|
|
|
}
|