Raspberry Pi pico WからOSCを使ってVRChatの操作をした話（2）ポテンショメータとfloat値編

# はじめに
この記事は、ポテンショメータを用いてアバターに設定しているfloat値をOSCで操作しようとしたものです。

## おことわり
この記事では、Raspberry Pi picoとはなんぞや、といったところにはあまり踏み込みません。
想定読者としては、VRChatでOSC通信を使って操作ができる、Raspberry Pi picoの初期セットアップができる（Thonnyでmicropythonのプログラムを書き込むことができる）人を想定しています。
詳しく知らない人は一度調べてみてください。詳しい記事がたくさん出てきます。

# まえがき
当記事を読んでいるみなさま、ご無沙汰しております。
前回の記事では、スイッチを用いてアバターのアクセサリをOSCでオンオフする方法について書きました。

https://qiita.com/knrotze/items/611f732c459d792911b6

これで、OSCで送れるもののうち、int型（と、bool型）は実装できました。
今回は、ポテンショメータ（つまみ）を用いてfloat型をうまく送り付けていきたいと思います。
ゴールとしては、Expression Menuで弄るパラメータの、下図のようなものを操作できるようにしていこうと思います。
![VRChat 2025_06_05 3_02_34.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264970/52d4976a-41a9-497c-8eb4-a117f0580b4f.png)

# 実装
## Raspberry Pi pico Wの配線
早速、ポテンショメータとは何かに軽く触れながら、実際に配線をしていきましょう。
ポテンショメータとは、可変抵抗と呼ばれるものの一種でボリュームとも呼ばれるものです。

https://akizukidenshi.com/catalog/g/g117390/

つまみの部分をひねることで抵抗の値が変化し、流れる電流の値が変わるという仕組みです。
ポテンショメータからは3つの端子が出ているので、左の端子をGND、右を電源、真ん中をGPIOにつなぐ形で配線していきます。今回使うGPIOは前回と同じ28番を使うことにしました。前回は適当にGPIOを決めましたが、今回は理由があって28番を選んでいます(後述します。)。また、前回の配線図とは電源とGNDの配線の仕方が異なっていますが、図の見た目をよくするために今回は電源用のラインとGND用のラインをつなぐ形にしています。
直接接続しても問題ありません。
![Untitled Sketch 2_ブレッドボード3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264970/d95259af-22ec-415b-b346-ffb405382e42.png)

## micro pythonの実装
次はRaspberry Pi pico Wに書き込むスクリプトを作成していきます。
### VRChatで操作する対象の設定
まずはVRChatに対して何を操作するのかを決めましょう。
アバターは前回同様猫山宙ちゃんを使っていきます。
この中でfloat値で調整できるパラメータを調査してみたところ、アバターの胸の小ささを調整できるパラメータがあったので、今回はそれをいじってみます。

```json
      "name": "Breast",
      "input": {
        "address": "/avatar/parameters/Breast",
        "type": "Float"
      },
      "output": {
        "address": "/avatar/parameters/Breast",
        "type": "Float"
      }
```
ネットワーク設定の部分もあわせて記載していくと、以下のようになります。
```python
import network

ssid = 'YOUR_SSID'
password = 'YOUR_PASSWORD'

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

ip = 'YOUR_IP'  # 送信先IP
port = 9000  # 送信先ポート
address = '/avatar/parameters/Breast'  # OSCアドレスパス

```
ここら辺は前回と変わらないですね。```ssid```、```password```には無線LANのSSID、パスワード、```ip```にはVRChatを起動しているPCのIPアドレスを記載してください。

### socket通信部の実装
次はソケット通信を行うためのコードを準備していきます。
とはいっても、コードのほとんどは前回の記事からコピーしてきてしまいます。
```python
import usocket as socket
import struct


# OSCメッセージの生成
def create_osc_message(address, *args):
    # OSCアドレスのパディング
    def pad_string(data):
        padding = (4 - len(data) % 4) % 4
        return data.encode('utf-8') + b'\0' * padding
    
    # OSCの引数型を決定するタグ
    types = "," + "".join('i' if isinstance(arg, int) else 'f' if isinstance(arg, float) else 's' for arg in args)
    
    # メッセージを組み立てる
    message = pad_string(address) + pad_string(types)
    
    # 引数をパック
    for arg in args:
        if isinstance(arg, int):
            message += struct.pack('>i', arg)
        elif isinstance(arg, float):
            message += struct.pack('>f', arg)
        elif isinstance(arg, str):
            message += pad_string(arg)
    
    return message

# OSCメッセージを送信する関数
def send_osc_message(ip, port, address, *args):
    osc_message = create_osc_message(address, *args)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # UDP
    sock.sendto(osc_message, (ip, port))
    sock.close()

```
この部分ですね。これは使いまわせてしまう部分なので流用してしまいましょう。
前回はスイッチのオンオフに対応させる形で残りを書いていましたが、今回はそこの部分をポテンショメータからの値を読み込むように設定していきます。

### ポテンショメータの値を読み取れるようにする
ではどう読み取っていくのかという部分なのですが、これを読み取っていくにはADCについて知っておく必要があります。
#### ADC(Analog Digital Converter)
ADC(Analog Digital Converter)とは、アナログ信号をデジタル信号に変換するための仕組みです。今回の場合だと、ポテンショメータから入力されてくる電気的なアナログ信号をRaspberry Pi picoで扱えるデジタルな値にするためのものですね。
この仕組み自体はRaspberry Pi picoの中に組み込まれているので、特に追加で何か用意する必要がないのですが、使う際には一つ注意点があります。それはADCが使えるGPIOは限られているということです。
下図はRaspberry Pi pico wのピンアサインです。31番、32番、34番のピンのところに注目してみてください。ADC0~2とそれぞれ記載されています。ADCは対応しているGPIO26~28でのみ利用することができるので、配線する際には注意してください。今回の配線時にGP28を選択したのもそういった理由があったからなのですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264970/258516c0-d144-413b-ba55-66037811698a.png)
（イメージは下記サイトから拝借しています。）
https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html

#### プログラム
気を取り直して、プログラムの部分を見てみましょう。下のプログラムを追記します。
```python
from machine import Pin, ADC

# 中略

adc = ADC(Pin(28)) #GP28をアナログ信号のインプットとして使用

# 中略

while True:
  value=adc.read_u16()
  degree=value*1/65535
  print(degree)
  send_osc_message(ip, port, address, degree)  # 引数を浮動小数点として送信
  sleep(0.01)
```
今回はADCを使うのでmachineからインポートします。
また、GPIO28番を使うことを宣言しましょう。

```while True```以下は以下のようになっています。
```value=adc.read_u16()```は読み取った値を0~65535の値で読み取ります。
```degree=value*1/65535```はVRChatではfloat値を0~1で読み取るので、その範囲で数値を送れるように調整しています。
``` send_osc_message(ip, port, address, degree)```で0~1の値を実際にVRChatに送信しています。

## 完成
ここまでのプログラムをまとめると、下記の通りになると思います。
```python:VRC_float.py
import usocket as socket
import struct
import network

from machine import Pin, ADC
from time import sleep

ssid = 'YOUR_SSID'
password = 'YOUR_PASSWORD'

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

# OSCメッセージの生成
def create_osc_message(address, *args):
    # OSCアドレスのパディング
    def pad_string(data):
        padding = (4 - len(data) % 4) % 4
        return data.encode('utf-8') + b'\0' * padding
    
    # OSCの引数型を決定するタグ
    types = "," + "".join('i' if isinstance(arg, int) else 'f' if isinstance(arg, float) else 's' for arg in args)
    
    # メッセージを組み立てる
    message = pad_string(address) + pad_string(types)
    
    # 引数をパック
    for arg in args:
        if isinstance(arg, int):
            message += struct.pack('>i', arg)
        elif isinstance(arg, float):
            message += struct.pack('>f', arg)
        elif isinstance(arg, str):
            message += pad_string(arg)
    
    return message

# OSCメッセージを送信する関数
def send_osc_message(ip, port, address, *args):
    osc_message = create_osc_message(address, *args)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # UDP
    sock.sendto(osc_message, (ip, port))
    sock.close()

adc = ADC(Pin(28)) #GP28をアナログ信号のインプットとして使用

# 実際にOSCメッセージを送信する
ip = 'YOUR_IP'  # 送信先IP
port = 9000  # 送信先ポート
address = '/avatar/parameters/Breast'  # OSCアドレスパス

while True:
  value=adc.read_u16()
  degree=value*1/65535
  print(degree)
  send_osc_message(ip, port, address, degree)  # 引数を整数、浮動小数点、文字列として送信
  sleep(0.01)
```
## 確認
上記プログラムを書き込んで実施してみましょう。下の動画のようになるはずです。

https://x.com/knrotze/statuses/1842661155204149692



ポテンショメータをひねると胸のサイズが変わることが確認できると思います。
# あとがき
いかがでしたでしょうか。今回は胸の大きさを変更してみましたが、パラメータ次第ではいろいろなものを変えることができると思います。
これでいったんアウトプットとして書いておきたい記事は作成できたのですが、機会があればVRChatユーザ向けにRaspberry Pi Picoのセットアップなど、ここまでの記事を読んでRaspberry Pi picoに興味を持って下さったかた向けの記事を書いてみようかな、と思います。

是非皆さんもRaspberry Pi pico Wを使ってOSCコントローラーを作ってみてください。その際にこの記事がさんこうになれば幸いです。

# 参考
https://micropython-docs-ja.readthedocs.io/ja/latest/library/machine.ADC.html