diff --git a/go.mod b/go.mod index a130cf5..68cebd4 100644 --- a/go.mod +++ b/go.mod @@ -6,14 +6,24 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/go-ble/ble v0.0.0-20240122180141-8c5522f54333 github.com/makeworld-the-better-one/dither v1.0.0 + tinygo.org/x/bluetooth v0.12.0 ) require ( + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/mattn/go-colorable v0.1.6 // indirect github.com/mattn/go-isatty v0.0.12 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab // indirect github.com/pkg/errors v0.8.1 // indirect + github.com/saltosystems/winrt-go v0.0.0-20240509164145-4f7860a3bd2b // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/soypat/cyw43439 v0.0.0-20250505012923-830110c8f4af // indirect + github.com/soypat/seqs v0.0.0-20250124201400-0d65bc7c1710 // indirect + github.com/tinygo-org/cbgo v0.0.4 // indirect + github.com/tinygo-org/pio v0.2.0 // indirect + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect - golang.org/x/sys v0.0.0-20211204120058-94396e421777 // indirect + golang.org/x/sys v0.11.0 // indirect ) diff --git a/main.go b/main.go index 87ba5a2..db683eb 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ You should have received a copy of the GNU General Public License along with Foo package main import ( - "context" "flag" "fmt" "image" @@ -22,22 +21,29 @@ import ( "io" "log" "os" - "os/signal" "time" "github.com/disintegration/imaging" - ble "github.com/go-ble/ble" - "github.com/go-ble/ble/linux" + "github.com/go-ble/ble" dither "github.com/makeworld-the-better-one/dither" + "tinygo.org/x/bluetooth" ) const minLines = 86 // firmware refuses to print anything shorter +func MustParseUUID(s string) bluetooth.UUID { + uuid, err := bluetooth.ParseUUID(s) + if err != nil { + panic("invalid UUID: " + err.Error()) + } + return uuid +} + var ( - mainServiceUUID = ble.MustParse("ae30") - printCharacteristic = ble.MustParse("ae01") - notifyCharacteristic = ble.MustParse("ae02") - dataCharacteristic = ble.MustParse("ae03") + mainServiceUUID = MustParseUUID("ae30") + printCharacteristic = MustParseUUID("ae01") + notifyCharacteristic = MustParseUUID("ae02") + dataCharacteristic = MustParseUUID("ae03") targetPrinterName = "MXW01" scanTimeout = 10 * time.Second printCommandHeader = []byte{0x22, 0x21} @@ -53,7 +59,7 @@ var ( ejectPaper uint retractPaper uint outputPath string - version = "dev" + version = "dev" ) func init() { @@ -91,7 +97,7 @@ func init() { flag.StringVar(&outputPath, "output", "", "Output PNG preview instead of printing (specify output path)") flag.Usage = func() { - fmt.Fprintf(os.Stderr, "Bleh! Cat Printer Utility for MXW01, version %s\n",version) + fmt.Fprintf(os.Stderr, "Bleh! Cat Printer Utility for MXW01, version %s\n", version) fmt.Fprintf(os.Stderr, "Usage: %s [options] \n", os.Args[0]) fmt.Fprintln(os.Stderr, ` Options: @@ -440,72 +446,75 @@ func renderPreviewFrom4bpp(pixels []byte, width, height int) image.Image { return img } +var adapter = bluetooth.DefaultAdapter + +func initBLE() { + err := adapter.Enable() + if err != nil { + log.Fatalf("failed to enable BLE adapter: %v", err) + } +} + func main() { flag.Parse() needNotifications := getStatus || getBattery || getVersion || getPrintType || getQueryCount || ejectPaper > 0 || retractPaper > 0 if needNotifications { - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) - defer stop() + //ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + //defer stop() - d, err := linux.NewDevice() - if err != nil { - log.Fatalf("Failed to open BLE device: %v", err) - } - ble.SetDefaultDevice(d) + initBLE() log.Println("Scanning for printer...") - log.Println("Connecting...") - var adv ble.Advertisement - ctxScan, cancel := context.WithTimeout(ctx, scanTimeout) - err = ble.Scan(ctxScan, false, func(a ble.Advertisement) { - if a.LocalName() == targetPrinterName { - adv = a - cancel() + var printerAddr bluetooth.Address + + err := adapter.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) { + fmt.Printf("Found device: %s, Name: %s\n", result.Address.String(), result.LocalName()) + if result.LocalName() == targetPrinterName { + printerAddr = result.Address + log.Printf("Found target printer: %s", targetPrinterName) + adapter.StopScan() } - }, nil) - if err != nil && err != context.Canceled { + }) + + if err != nil { log.Fatalf("Scan error: %v", err) } - if adv == nil { - log.Println("Printer not found.") - return + + if printerAddr == (bluetooth.Address{}) { + log.Fatal("Printer not found.") } - client, err := ble.Dial(ctx, adv.Addr()) + log.Println("Connecting...") + + device, err := adapter.Connect(printerAddr, bluetooth.ConnectionParams{}) if err != nil { log.Fatalf("Connect failed: %v", err) } - defer client.CancelConnection() - mtu, err := client.ExchangeMTU(100) - if err != nil { - log.Printf("MTU negotiation failed: %v", err) - } else { - log.Printf("Negotiated ATT MTU: %d", mtu) - } - var printChr, notifyChr *ble.Characteristic - services, err := client.DiscoverServices([]ble.UUID{mainServiceUUID}) + log.Println("Connected to printer:", device.Address) + + var printChr, notifyChr *bluetooth.DeviceCharacteristic + services, err := device.DiscoverServices([]bluetooth.UUID{mainServiceUUID}) if err != nil || len(services) == 0 { log.Fatalf("Service discovery failed: %v", err) } svc := services[0] - chars, err := client.DiscoverCharacteristics(nil, svc) + chars, err := svc.DiscoverCharacteristics(nil) if err != nil { log.Fatalf("Characteristic discovery failed: %v", err) } - for _, c := range chars { - switch c.UUID.String() { + for i, c := range chars { + switch c.UUID().String() { case notifyCharacteristic.String(): - notifyChr = c + notifyChr = &chars[i] case printCharacteristic.String(): - printChr = c + printChr = &chars[i] } } if notifyChr != nil { - _, _ = client.DiscoverDescriptors(nil, notifyChr) - err = client.Subscribe(notifyChr, false, func(b []byte) { + err = notifyChr.EnableNotifications(func(b []byte) { parseNotification(b) }) if err != nil { @@ -514,30 +523,33 @@ func main() { log.Println("Subscribed to printer notifications.") } } + if printChr == nil { + log.Fatal("Print/command characteristic not found!") + } if getStatus { - sendSimpleCommand(client, printChr, 0xA1) + printChr.WriteWithoutResponse([]byte{0xA1}) } if getBattery { - sendSimpleCommand(client, printChr, 0xAB) + printChr.WriteWithoutResponse([]byte{0xAB}) } if getVersion { - sendSimpleCommand(client, printChr, 0xB1) + printChr.WriteWithoutResponse([]byte{0xB1}) } if getPrintType { - sendSimpleCommand(client, printChr, 0xB0) + printChr.WriteWithoutResponse([]byte{0xB0}) } if getQueryCount { - sendSimpleCommand(client, printChr, 0xA7) + printChr.WriteWithoutResponse([]byte{0xA7}) } if ejectPaper > 0 { - sendLineCommand(client, printChr, 0xA3, ejectPaper) + printChr.WriteWithoutResponse([]byte{0xA3, byte(ejectPaper)}) } if retractPaper > 0 { - sendLineCommand(client, printChr, 0xA4, retractPaper) + printChr.WriteWithoutResponse([]byte{0xA4, byte(retractPaper)}) } if getStatus || getBattery || getVersion || getPrintType || getQueryCount || ejectPaper > 0 || retractPaper > 0 { log.Println("Waiting for notifications...") - time.Sleep(2 * time.Second) + time.Sleep(5 * time.Second) } if flag.NArg() < 1 { return // no image to print, other commands may have run @@ -546,146 +558,6 @@ func main() { } } - var printMode PrintMode - switch mode { - case "1bpp": - printMode = Mode1bpp - case "4bpp": - printMode = Mode4bpp - default: - fmt.Println("Invalid mode. Use '1bpp' or '4bpp'.") - return - } - imagePath := flag.Arg(0) - - var pixels []byte - var height int - img, err := decodeImage(imagePath) - - if err != nil { - log.Fatalf("Image load error: %v", err) - } - img = padImageToMinLines(img, minLines) - - switch printMode { - case Mode1bpp: - pixels, height, err = loadImageMonoFromImage(img, ditherType) - case Mode4bpp: - pixels, height, err = loadImage4BitFromImage(img, ditherType) - } - if err != nil { - log.Fatalf("Image conversion error: %v", err) - } - - if outputPath != "" { - var previewImg image.Image - switch printMode { - case Mode1bpp: - previewImg = renderPreviewFrom1bpp(pixels, linePixels, height) - case Mode4bpp: - previewImg = renderPreviewFrom4bpp(pixels, linePixels, height) - } - var out io.Writer - if outputPath == "-" { - out = os.Stdout - } else { - f, err := os.Create(outputPath) - if err != nil { - log.Fatalf("Failed to create output file: %v", err) - } - defer f.Close() - out = f - } - err = imaging.Encode(out, previewImg, imaging.PNG) - if err != nil { - log.Fatalf("Failed to write PNG preview: %v", err) - } - if outputPath != "-" { - fmt.Printf("Preview PNG written to %s\n", outputPath) - } - return - } - - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) - defer stop() - - d, err := linux.NewDevice() - if err != nil { - log.Fatalf("Failed to open BLE device: %v", err) - } - ble.SetDefaultDevice(d) - - log.Println("Scanning for printer...") - log.Println("Connecting...") - var adv ble.Advertisement - ctxScan, cancel := context.WithTimeout(ctx, scanTimeout) - err = ble.Scan(ctxScan, false, func(a ble.Advertisement) { - if a.LocalName() == targetPrinterName { - adv = a - cancel() - } - }, nil) - if err != nil && err != context.Canceled { - log.Fatalf("Scan error: %v", err) - } - if adv == nil { - log.Println("Printer not found.") - return - } - - client, err := ble.Dial(ctx, adv.Addr()) - if err != nil { - log.Fatalf("Connect failed: %v", err) - } - defer client.CancelConnection() - - mtu, err := client.ExchangeMTU(100) - if err != nil { - log.Printf("MTU negotiation failed: %v", err) - } else { - log.Printf("Negotiated ATT MTU: %d", mtu) - } - var printChr, dataChr *ble.Characteristic - services, err := client.DiscoverServices([]ble.UUID{mainServiceUUID}) - if err != nil || len(services) == 0 { - log.Fatalf("Service discovery failed: %v", err) - } - svc := services[0] - chars, err := client.DiscoverCharacteristics(nil, svc) - if err != nil { - log.Fatalf("Characteristic discovery failed: %v", err) - } - for _, c := range chars { - switch c.UUID.String() { - case dataCharacteristic.String(): - dataChr = c - case printCharacteristic.String(): - printChr = c - } - } - - if printChr == nil { - log.Fatalf("Missing required print characteristic") - } - - i := intensity - if i < 0 { - i = 0 - } - if i > 100 { - i = 100 - } - intensityByte := byte(i) - - if dataChr == nil { - log.Fatalf("Missing required data characteristic") - } - - err = sendImageBufferToPrinter(client, dataChr, printChr, pixels, height, printMode, intensityByte) - if err != nil { - log.Fatalf("Failed to print image: %v", err) - } - log.Println("Done!") }