Processing(Java)からSimpleBLEを使う

JavaからBluetoothを扱う方法を調べてもなかなか前例がなかったので、 JNAを使ってSimpleBLE を使ってみたのでメモ。

そのうちラッパーとか作るかも。

手順

まずはReleasesから 実行環境にあった共有ライブラリ simpleble_shared_${OS}-${ARCH}.zip をダウンロードしてくる。

runner/work/SimpleBLE/SimpleBLE/build/install/lib の中に共有ライブラリ(macOSの場合はdylib)が入っているので、 適当なところに配置する。 以降、このパスを ${DYNAMIC_LINK_LIBRARY_PATH} とする。

以下のコードを実行したらBLEデバイスのスキャンを開始し、 画面に見つかったデバイスの情報を表示する。

※ 「実行時に開発源が未確認〜」と表示されたら、 システム環境設定 > プライバシーとセキュリティ から実行を許可する。

import com.sun.jna.Library;
import com.sun.jna.Structure;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.PointerType;
import com.sun.jna.IntegerType;
import com.sun.jna.Pointer;
import com.sun.jna.Callback;
import com.sun.jna.Platform;

// 検出したデバイスの情報をとりあえず文字列で格納するArrayList
ArrayList<String> devices = new ArrayList<String>();

void setup() {
  // 共有ライブラリの検索パスを追加して読み込み
  String nativeLibraryPath = "${DYNAMIC_LINK_LIBRARY_PATH}";
  NativeLibrary.addSearchPath("simpleble-c", nativeLibraryPath);
  SimpleBLE simpleBle = (SimpleBLE)Native.load("simpleble-c", SimpleBLE.class);
  // BLEアダプタが利用できない or BLEアダプタが無い場合は終了
  if (!simpleBle.simpleble_adapter_is_bluetooth_enabled() || simpleBle.simpleble_adapter_get_count().intValue() == 0) {
    exit();
  }
  // 0番目のBLEアダプタを取得
  simpleble_adapter_t adapter = simpleBle.simpleble_adapter_get_handle(new size_t(0));
  if (adapter == null) {
    println("No adapter was found.\n");
    exit();
  }
  // スキャンを開始した際のコールバックを登録
  simpleBle.simpleble_adapter_set_callback_on_scan_start(
    adapter,
    new simpleble_adapter_set_callback_on_scan_start_callback() {
    public void invoke(simpleble_adapter_t adapter, userdata userdata) {
      println(adapter);
      println(userdata);
    }
  }
  , null);
  // 周辺機器が見つかった際のコールバックを登録
  // 見つかったらdevicesに追加
  simpleBle.simpleble_adapter_set_callback_on_scan_found(
    adapter,
    new simpleble_adapter_set_callback_on_scan_found_callback() {
    public void invoke(simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, userdata userdata) {
      String adapter_identifier = simpleBle.simpleble_adapter_identifier(adapter);
      String peripheral_identifier = simpleBle.simpleble_peripheral_identifier(peripheral);
      String peripheral_address = simpleBle.simpleble_peripheral_address(peripheral);

      if (adapter_identifier == null || peripheral_identifier == null || peripheral_address == null) {
        return;
      }

      String device = String.format("Adapter %s\nfound device: %s [%s]\n", adapter_identifier, peripheral_identifier, peripheral_address);
      println(device);
      devices.add(device);

      simpleBle.simpleble_free(peripheral);
      simpleBle.simpleble_free(peripheral_address);
      simpleBle.simpleble_free(peripheral_identifier);
    }
  }
  , null);
  // 周辺機器のスキャンを開始
  simpleBle.simpleble_adapter_scan_for(adapter, 5000);
  size(500, 500);
}

void draw() {
  background(100);
  for (int i = 0; i < devices.size(); i++) {
    text(devices.get(i), 10, i * 35 + 20);
  }
}

/**
 * C用のSimpleBLEで使われる関数を呼び出すためのインタフェース
 */
interface SimpleBLE extends Library {
  boolean simpleble_adapter_is_bluetooth_enabled();
  size_t simpleble_adapter_get_count();
  simpleble_adapter_t simpleble_adapter_get_handle(size_t i);
  int simpleble_adapter_set_callback_on_scan_start(simpleble_adapter_t handle, simpleble_adapter_set_callback_on_scan_start_callback callback, userdata userdata);
  int simpleble_adapter_scan_for(simpleble_adapter_t handle, int timeout_ms);
  int simpleble_adapter_set_callback_on_scan_found(simpleble_adapter_t handle, simpleble_adapter_set_callback_on_scan_found_callback callback, userdata userdata);
  String simpleble_adapter_identifier(simpleble_adapter_t handle);
  String simpleble_peripheral_identifier(simpleble_peripheral_t peripheral);
  String simpleble_peripheral_address(simpleble_peripheral_t peripheral);
  void simpleble_free(handle handle);
  void simpleble_free(String handle);
}

/**
 * size_t用のクラス
 */
public static class size_t extends IntegerType {
  private static final long serialVersionUID = 1L;
  public size_t() {
    this(0);
  }
  public size_t(long value) {
    super(Native.SIZE_T_SIZE, true);
    setValue(value);
  }
}

/**
 * simpleble_adapter_set_callback_on_scan_startの引数に渡すコールバックのインタフェース
 */
public interface simpleble_adapter_set_callback_on_scan_start_callback extends Callback {
  public void invoke(simpleble_adapter_t adapter, userdata userdata);
}
/**
 * simpleble_adapter_set_callback_on_scan_foundの引数に渡すコールバックのインタフェース
 */
public interface simpleble_adapter_set_callback_on_scan_found_callback extends Callback {
  public void invoke(simpleble_adapter_t adapter, simpleble_peripheral_t peripheral, userdata userdata);
}
/**
 * アダプタを抽象化したポインタ
 */
public static class simpleble_adapter_t extends handle {
  public simpleble_adapter_t() {
    super();
  }

  public simpleble_adapter_t(Pointer p) {
    super(p);
  }
}
/**
 * userdataを抽象化したポインタ(まだ用途がわからない)
 */
public static class userdata extends handle {
  public userdata() {
    super();
  }

  public userdata(Pointer p) {
    super(p);
  }
}
/**
 * 周辺機器を抽象化したポインタ
 */
public static class simpleble_peripheral_t extends handle {
  public simpleble_peripheral_t() {
    super();
  }

  public simpleble_peripheral_t(Pointer p) {
    super(p);
  }
}
/**
 * simpleble_free用に一応定義しておいた
 */
public static class handle extends PointerType {
  public handle() {
    super();
  }

  public handle(Pointer p) {
    super(p);
  }
}

参考文献


Processing(Java)からSimpleBLEを使う

By Katsuya Endoh, 2023-12-31