package main import ( phomemofilter "brainbaking.com/phomemoprinter/filter" "flag" "log" "os" "os/exec" "path/filepath" "strconv" "strings" "time" "tinygo.org/x/bluetooth" ) var adapter = bluetooth.DefaultAdapter func main() { var home, _ = os.UserHomeDir() var defaultDir = filepath.Join(home, "Downloads", "phomemo") dir := flag.String("dir", defaultDir, "Directory to pick up any images from") file := flag.String("file", "", "Print only a specific image. If provided, -dir will be ignored.") flag.Parse() if *file != "" { log.Printf("Attempting to print file %s...\n", *file) if !isPossibleToPrint(*file) { log.Fatalln("Cannot print, this is not a photo (.jpg/.png only!)") os.Exit(1) } *dir = filepath.Join(os.TempDir(), "phomemo") os.Mkdir(*dir, os.ModePerm) // ignore error in case still existing err := copyFileToFolder(*file, *dir) if err != nil { log.Fatalf("Cannot copy %s to %s: %w. Quitting. \n", *file, *dir, err) os.Exit(1) } } else { log.Printf("Attempting to scan dir %s for files to print...\n", *dir) } files, err := os.ReadDir(*dir) must("Open directory target", err) if len(files) == 0 { log.Fatalf("No files to print found in %s, nothing to do. Quitting.\n", *dir) os.Exit(0) } must("Enable Bluetooth stack", adapter.Enable()) pyfilterloc := dumpFilter(*dir) defer func() { os.RemoveAll(pyfilterloc) }() log.Println("Scanning for Bluetooth devices...") ch := make(chan bluetooth.ScanResult, 1) go func() { err = adapter.Scan(func(adapter *bluetooth.Adapter, device bluetooth.ScanResult) { if device.LocalName() == "Mr.in_M02" { ch <- device adapter.StopScan() } }) must("start scan", err) }() select { case device := <-ch: log.Println("Found device: ", device.Address.String(), device.RSSI, device.LocalName()) for _, file := range files { filePath := filepath.Join(*dir, file.Name()) if isPossibleToPrint(filePath) { log.Printf("Trying to print %s\n", filePath) tryToPrint(device, pyfilterloc, filePath) } } case <-time.After(10 * time.Second): log.Fatal("Timeout trying to locate Phomemo M02, is it on? Quitting.") os.Exit(1) } } func copyFileToFolder(file string, folder string) error { contents, err := os.ReadFile(file) if err != nil { return err } err = os.WriteFile(filepath.Join(folder, "toprint.jpg"), contents, os.ModePerm) if err != nil { return err } return nil } func tryToPrint(phomemoAddress bluetooth.ScanResult, pyfilterloc string, filePath string) { var phomemo *bluetooth.Device phomemo, err := adapter.Connect(phomemoAddress.Address, bluetooth.ConnectionParams{}) must("Failed to connect to adapter", err) log.Println("Connected to ", phomemoAddress.Address.String()) defer func() { if err := phomemo.Disconnect(); err != nil { log.Fatalf("Cannot disconnect: %w\n", err) } else { log.Println("disconnected") } }() srvcs, err := phomemo.DiscoverServices(nil) must("failed to discover service", err) log.Println("Discovering all services on device...") srvc := srvcs[0] // there's only one (0000ff00-...) anyway log.Println("- service", srvc.UUID().String()) chars, err := srvc.DiscoverCharacteristics(nil) must("Failed to discover characteristics of service", err) // 3 characteristics to discover 0000ff01, 0000ff02, and 0000ff03 writeData(pyfilterloc, filePath, chars[1]) // 0000ff02-... readResult(chars[0]) // 0000ff01-... } func isPossibleToPrint(filename string) bool { return strings.HasSuffix(filename, ".jpg") || strings.HasSuffix(filename, ".JPG") || strings.HasSuffix(filename, ".png") || strings.HasSuffix(filename, ".PNG") } func dumpFilter(dir string) string { filename := filepath.Join(dir, "phomemo-filter.py") err := os.WriteFile(filename, phomemofilter.Pyfilter, 0666) must("unable to dump python filter for use", err) return filename } func writeData(pyfilterloc string, path string, char bluetooth.DeviceCharacteristic) { // first, it needs to be prepared for the printer pathpho := path + ".pho" argstr := []string{"-c", "/usr/local/bin/python " + pyfilterloc + " " + path + " > " + pathpho} log.Println("args", argstr) _, err := exec.Command("/bin/zsh", argstr...).Output() must("something went wrong while filtering phomemo data py", err) data, _ := os.ReadFile(pathpho) log.Printf("-- writing file %s to characteristic %s\n", path, char.UUID().String()) _, err = char.WriteWithoutResponse(data) if err != nil { log.Println(" ", err.Error()) } time.Sleep(1 * time.Second) os.RemoveAll(pathpho) } func readResult(char bluetooth.DeviceCharacteristic) { log.Println("-- reading from to characteristic", char.UUID().String()) buf := make([]byte, 28) n, err := char.Read(buf) if err != nil { log.Println(" ", err.Error()) } else { log.Println(" data bytes", strconv.Itoa(n)) log.Println(" value =", buf[:n]) } time.Sleep(1 * time.Second) } func must(action string, err error) { if err != nil { log.Fatalf("Fatal error: Failed to "+action+": %w \n", err) os.Exit(1) } }