佐藤さとる

佐藤さとる

梶研 [リアルタイムでスマホの姿勢を表示するアプリ]

2024年07月16日

リアルタイムでスマホの姿勢を表示するアプリ

出席率

スケジュール

短期的な予定

長期的な予定

進捗報告

ST-GCN

進捗ありません

---

リアルタイムでスマホの姿勢を表示するアプリ

オーキャンでリアルタイムで表示できるアプリあれば嬉しいかも

技術構成

スマホ側 (モバイルアプリ)

React Native (expo): 使ってみたかった

PC側 (デスクトップアプリ)

Tauri (Rust, React - Vite)

通信

候補

Web Bluetooth API 使ってみる

ブラウザでBLE通信ができる WebAPI(ブラウザーAPI)

    // デバイスをフィルター
    const options: RequestDeviceOptions = {
      filters: [
        {
          namePrefix: "デバイス名のPrefix",
        },
      ],
    };

    // デバイスを探す & 接続
    const device = await navigator.bluetooth
      .requestDevice(options)
      .then((device) => {
        return device;
      })
      .catch((error) => {
        console.error(error);
        return null;
      });

    // 通信のためのあれこれ
    const server = await device?.gatt?.connect();
    const service = await server?.getPrimaryService('battery_service'); // PrimaryServiceの指定
    const characteristic = await service?.getCharacteristic(service.uuid);

    console.log(characteristic);

    // 受信時のイベント
    characteristic?.addEventListener('characteristicvaluechanged', (e) => {
      console.log(e);
    })

相手もブラウザの場合、PrimaryService ってなに...?

const service = await server?.getPrimaryService('battery_service');

Web Bluetooth API [MDN]

どうやら、BLE通信でセンサー等としか接続できないらしい?

少なくともブラウザ同士でやるものじゃない

Bluetooth やめた

Rust側 が面倒そう.

Socket通信(TCP) 使ってみる

接続すれば双方向から通信できるいいやつ

(HTTP はサーバーから送信はできない)

Rust(Tauri)で実装(サーバ側)

// サーバー起動
pub fn start_server(app: &mut App) {
    let app_handle = app.app_handle();

    thread::spawn(move || {
        let port_number = 12345;
        let listener = TcpListener::bind(SocketAddr::new(
            IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
            port_number,
        ))
        .expect("Failed to bind to address");

        print!("Server started");
        emit_socket_payload(
            &app_handle,
            SocketState::Started,
            "Server started".to_string(),
        );

        recv(listener, app_handle);
    });

    print!("Server Stop");
}
// 接続待ち
fn recv(listener: TcpListener, app_handle: AppHandle) {
    loop {
        match listener.accept() {
            Ok((mut socket, addr)) => {
                emit_socket_payload(
                    &app_handle,
                    SocketState::Connected,
                    format!("Accepted connection from {:?}", addr),
                );
                read(&mut socket, &app_handle);
            }
            Err(e) => {
                eprintln!("Accept error: {}", e);
                std::process::exit(1);
            }
        }
    }
}
// 受信しまくる
fn read(socket: &mut std::net::TcpStream, app_handle: &AppHandle) {
    loop {
        let mut buffer = [0; 1024];
        match socket.read(&mut buffer) {
            Ok(0) => {
                emit_socket_payload(
                    app_handle,
                    SocketState::Disconnected,
                    "Client disconnected".to_string(),
                );

                break;
            }
            Ok(bytes_read) => {
                let message = String::from_utf8_lossy(&buffer[..bytes_read]);
                emit_socket_payload(app_handle, SocketState::Received, message.to_string());
            }
            Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
                continue;
            }
            Err(e) => {
                emit_socket_payload(
                    app_handle,
                    SocketState::Error,
                    format!("Error reading from client: {}", e).to_string(),
                );
                break;
            }
        }
    }

    // Close the connection if it's still open.
    if let Err(e) = socket.shutdown(Shutdown::Both) {
        emit_socket_payload(
            app_handle,
            SocketState::Error,
            format!("Shutdown failed: {}", e).to_string(),
        );
    }
}
// React(フロント)に送信
fn emit_socket_payload(app_handle: &AppHandle, state: SocketState, data: String) {
    let payload = SocketPayload { state, data };
    app_handle.emit_all("socket", payload.clone()).unwrap();
}

簡単にできた.

React

いい感じにした

export const useSocket = (handler: recvHandler, event: EventName = 'socket') => {
  const isListening = useRef(false);

  useEffect(() => {
    if (isListening.current) return;
    isListening.current = true;

    (async () => {
      console.log('Listening to socket events');
      const unlisten = await listen(event, (event: SocketEvent) => {
        handler(event.payload.state, event.payload.data);
      });

      return () => {
        unlisten();
        isListening.current = false;
      };
    })();
  }, []);
};

React Native

react-native-tcp-socket というライブラリがあった

// 接続
const client = TcpSocket.createConnection({ port: portNum, host: ip }, () => {});
// 送信
client?.write(sendData);
// 切断
client?.destroy();

楽にできた

センシングする

expo には DeviceMotion っていう便利なやつがいた.

加速度, 線形加速度, 角速度, 回転量, デバイスの向き を扱える

export const useSensor = (handler?: Handler, sampleingRate = 1 / 60) => {
  const [data, setData] = useState<DeviceMotionMeasurement>();
  const [subscription, setSubscription] = useState<Subscription>();

  /**
   * センサーの読み込みを開始する
   */
  const subscribe = () => {
    const deviceMotion = DeviceMotion.addListener((data) => setData(data));
    DeviceMotion.setUpdateInterval(sampleingRate);
    setSubscription(deviceMotion);
  };

  /**
   * センサーの読み込みを停止する
   */
  const unsubscribe = () => {
    subscription?.remove();
    setSubscription(undefined);
  };

  if (handler && data) handler(data);

  return [data, { subscribe, unsubscribe }] as const;
};

簡単にできた

<iframe width="1052" height="788" src="https://www.youtube.com/embed/jKNEi2YUSuA" title="リアルタイムでスマホの姿勢を表示したい (センサーデータ付き)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

スマホを表示する

Three.js でできた

<iframe width="1052" height="788" src="https://www.youtube.com/embed/TLSF8Vi4zUI" title="リアルタイムでスマホの姿勢を表示したい (自動回転)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

センサーの値にフィルターを掛ける

データ

フィルター

相補フィルターでやるつもり

  1. 加速度のノルムの分散を出す
  2. 分散が 0.05 以上 であれば動いているとみなす
    • 動いている場合
      • 現在の姿勢(回転量) に足していく(積分)
    • 動いていない場合
      • 重力加速度から下方向を出す

現状

お家では動いたけど...

ReactNative から socket が繋がらない...

(ping は飛ばせる)

動画撮ってなかった...

TODO

進路関係

pluszero (長期インターン) が幸福です

余談

余談はありません.

梶研入ってから1週目(2023/4/11 ~ 4/18)以外は欠かさずに余談を生成していたので悲しいです.

余談は任意(強制)です
by makino