0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

StarXpand SDK for iOS 入門:検出 → 印刷 → 状態取得までを最短で

0
Posted at

お店の機器とシステムをつなぐ仕事をしています。プリンターまわりの開発をしていると、お店の方から「突然動かなくなった」「故障なのか分からない」という相談をよく受けます。

アプリから印刷するとき、どこまで届いていて、どこで止まっているのかが分かるように作っておくと、こうしたトラブルにかなり強くなります。

この記事では、スター精密が公開している StarXpand SDK for iOS を使って、iOS アプリから

  1. プリンターを探す
  2. 印刷データを作る
  3. 送って印刷する
  4. 状態(紙切れ・カバー開き)を取る
  5. SwiftUI の画面から使う

までを最短でつなぐ流れをまとめます。

この記事の内容は、公開されている README・サンプルアプリ・オンラインマニュアルの範囲です。対応機種や細かい仕様は、必ず公式のマニュアルで確認してください。

StarXpand SDK とは

スター精密のプリンターなどを iOS アプリから扱うための SDK で、中身は StarIO10 というフレームワークです。

  • 接続方式:LAN/Bluetooth/Bluetooth Low Energy/USB
  • 動作環境:iOS 15.0 以降・Swift 5
  • 導入:Swift Package Manager

LAN でも Bluetooth でも、同じ書き方で扱えるのがいちばんの利点だと感じています。

導入

Xcode の File > Add Packages に、次の URL を入れます。

https://github.com/star-micronics/StarXpand-SDK-iOS

Info.plist に足すもの

接続方式によって、必要な設定が変わります。ここを忘れると「見つからない」「つながらない」の原因になります。

接続方式 必要な設定
LAN Privacy - Local Network Usage Description
Bluetooth / BLE Privacy - Bluetooth Always Usage Description
Bluetooth / USB Supported external accessory protocols に jp.star-m.starpro

⚠️ Bluetooth と USB は MFi の扱いになるので、App Store に出すときは申請の手続きも必要です(詳しくは公式の README を参照してください)。

1. プリンターを探す

StarDeviceDiscoveryManagerFactory で探索用のマネージャーを作り、見つかったプリンターをデリゲートで受け取ります。

import StarIO10

final class PrinterFinder: StarDeviceDiscoveryManagerDelegate {
    private var manager: (any StarDeviceDiscoveryManager)?

    func start() throws {
        manager = try StarDeviceDiscoveryManagerFactory.create(interfaceTypes: [.lan, .bluetooth])
        manager?.discoveryTime = 5_000   // ミリ秒
        manager?.delegate = self
        try manager?.startDiscovery()
    }

    func manager(_ manager: any StarDeviceDiscoveryManager, didFind printer: StarPrinter) {
        let settings = printer.connectionSettings
        print("見つかった:", settings.interfaceType, settings.identifier, printer.information?.model as Any)
    }

    func managerDidFinishDiscovery(_ manager: any StarDeviceDiscoveryManager) {
        print("探索おわり")
    }
}

見つかったプリンターの connectionSettings(接続方式と識別子)を保存しておけば、次回からは探さずにつなげます。

2. 印刷データを作る

印刷データは、ビルダーを入れ子にして組み立てる形です。

func makeReceipt() -> String {
    let builder = StarXpandCommand.StarXpandCommandBuilder()
    _ = builder.addDocument(
        StarXpandCommand.DocumentBuilder()
            .addPrinter(
                StarXpandCommand.PrinterBuilder()
                    .styleAlignment(.center)
                    .actionPrintText("MonoUni Coffee\n")
                    .styleAlignment(.left)
                    .actionPrintText("Coffee        500\n")
                    .actionPrintText("Total         500\n")
                    .actionCut(.partial)
            )
    )
    return builder.getCommands()
}

getCommands() が返すのはただの文字列です。作る処理と送る処理が分かれているので、印刷データを作る部分だけをテストしやすいのも嬉しいところです。

⚠️ 画像でしか印字できない機種もあります(サンプルのコメントでは TSP100III シリーズなど)。その場合は actionPrintText の代わりに actionPrintImage を使います。

3. 送って印刷する

StarPrinter に接続設定を渡して、開く → 送る → 閉じる の順です。

func printReceipt(settings: StarConnectionSettings) async {
    let printer = StarPrinter(settings)
    do {
        try await printer.open()
        defer { Task { await printer.close() } }
        try await printer.print(command: makeReceipt())
        print("印刷 OK")
    } catch {
        print("印刷できなかった:", error)
    }
}

// LAN の場合の例(identifier は検出で得た値)
let settings = StarConnectionSettings(interfaceType: .lan, identifier: "00:11:62:XX:XX:XX")

open() で失敗するのか、print() で失敗するのかで、原因の当たりがつけやすくなります。

  • open() で失敗 → つながっていない(電源・ネットワーク・Bluetooth のペアリング・別のアプリが使っている など)
  • print() で失敗 → つながってはいるが、印刷できない状態(紙切れ・カバー開き など)

4. 状態を取る

印刷の前後で getStatus() を呼ぶと、プリンターの状態が取れます。

let printer = StarPrinter(settings)
try await printer.open()
let status = try await printer.getStatus()
print("エラー:", status.hasError, "紙切れ:", status.paperEmpty, "カバー開き:", status.coverOpen)
await printer.close()

お店の方に「紙が切れています」「ふたが開いています」と具体的に出してあげるだけで、「壊れた?」という問い合わせはぐっと減ります。

状態の変化を待ち受けたい場合は、printer.printerDelegate を設定すると、紙切れ・カバー開き・通信エラーなどをイベントで受け取れます(サンプルアプリの MonitorView が参考になります)。

5. SwiftUI で使う

SwiftUI では、ここまでの処理を ViewModel にまとめて、画面は「探す」「選んで印刷」「結果を出す」だけにしておくと扱いやすいです(Swift 5 モードを前提にしています)。

import SwiftUI
import StarIO10

struct FoundPrinter: Identifiable {
    let id = UUID()
    let settings: StarConnectionSettings
    let label: String
}

@MainActor
final class PrinterViewModel: ObservableObject, StarDeviceDiscoveryManagerDelegate {
    @Published var printers: [FoundPrinter] = []
    @Published var message: String?
    private var manager: (any StarDeviceDiscoveryManager)?

    func discover() {
        printers = []
        do {
            manager?.stopDiscovery()
            manager = try StarDeviceDiscoveryManagerFactory.create(interfaceTypes: [.lan, .bluetooth])
            manager?.discoveryTime = 5_000
            manager?.delegate = self
            try manager?.startDiscovery()
            message = "探しています…"
        } catch {
            message = "探せませんでした: \(error)"
        }
    }

    // SDK からの通知はメインスレッドとは限らないので、受け取ってから MainActor へ戻す
    nonisolated func manager(_ manager: any StarDeviceDiscoveryManager, didFind printer: StarPrinter) {
        let settings = printer.connectionSettings
        let label = "\(printer.information?.model, default: "") \(settings.identifier)"
        Task { @MainActor in
            self.printers.append(FoundPrinter(settings: settings, label: label))
        }
    }

    nonisolated func managerDidFinishDiscovery(_ manager: any StarDeviceDiscoveryManager) {
        Task { @MainActor in
            self.message = self.printers.isEmpty ? "見つかりませんでした" : nil
        }
    }

    func printReceipt(_ settings: StarConnectionSettings) async {
        let printer = StarPrinter(settings)
        do {
            try await printer.open()
        } catch {
            message = "プリンターにつながりませんでした"
            return
        }
        defer { Task { await printer.close() } }

        do {
            try await printer.print(command: makeReceipt())
            message = "印刷しました"
        } catch {
            // 失敗したら状態を取りにいき、お店の人に分かる言葉で出す
            let status = try? await printer.getStatus()
            if status?.paperEmpty == true {
                message = "紙が切れています"
            } else if status?.coverOpen == true {
                message = "ふたが開いています"
            } else {
                message = "印刷できませんでした: \(error)"
            }
        }
    }
}

struct PrinterView: View {
    @StateObject private var viewModel = PrinterViewModel()

    var body: some View {
        List {
            Button("プリンターを探す") { viewModel.discover() }

            Section("見つかったプリンター") {
                ForEach(viewModel.printers) { printer in
                    Button(printer.label) {
                        Task { await viewModel.printReceipt(printer.settings) }
                    }
                }
            }

            if let message = viewModel.message {
                Text(message).foregroundStyle(.secondary)
            }
        }
    }
}

ポイントは 2 つです。

  • SDK のデリゲートは nonisolated で受けて、@MainActor に戻してから画面の値を書き換える(公式サンプルの DiscoveryView も同じ形です)
  • 印刷に失敗したら getStatus() を取りにいき、「紙が切れています」のように言い換えて出す。エラーをそのまま見せないだけで、現場の安心感がかなり違います

makeReceipt() は「2. 印刷データを作る」の関数をそのまま使っています。

まとめ

  • 導入は SPM、Info.plist の設定は接続方式ごとに忘れずに
  • 探す → 作る → 送る → 状態を見る の 4 つに分けて書くと、トラブルの切り分けがしやすい
  • 画面に状態を具体的に出すと、お店の方の「壊れた?」が減る
  • SwiftUI では ViewModel にまとめ、デリゲートは MainActor に戻してから画面を更新する

公式のサンプルアプリには、レシートやラベルの印刷例がたくさん入っています。まずはサンプルを動かしてみるのがおすすめです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?