🐔

📣RaspberryPi Picoとブラウザで通信する

2023/07/07に公開

RaspberryPi Picoに無線機能を搭載したRaspberryPi Pico Wが発売されましたね!
これを使えば、ウェブブラウザから簡単にラズピコを操作できるみたいです。

しかしここには普通のラズピコしかありませんので、音波通信でブラウザからラズピコを操作してみましょう!

完成図

電波の代わりに、音波を使って通信します。
速度は160bps。通信距離は音声周りのアナログ回路が貧弱な場合は数cmです。
※アナログ回路をきちんと作れば~1mでくらいは頑張れるとかも?

ブラウザは送信画面の為だけに利用して、ラズピコとブラウザは直接音波でお話しします。

楽しそうですね!

ラズピコ

まずラズピコ側から作っていきましょう。

ハードの準備

保護回路を経由して1枚の圧電素子をラズピコに直結します。
この貧相な回路は、本来別々に用意するべき音声入力/出力回路が一体化しており、とてもまともに動きそうに思えませんが、(非常にシビアですが)動作しました。

手元の装置では、圧電素子に秋月電子で購入したSPT08を使いましたが、その辺に落ちている直径2cmくらいのものなら何でもよろしいかと思います。

回路図はこのようになります。それっぽいオーディオ入力回路と過電圧保護回路です。
(アナログ回路のわかる方の突っ込みをお待ちしております。)

重要 たいせつなラズピコを破壊しないために、クリッピング用の保護ダイオードを忘れずに!

※過電圧分は3.3Vのラインに捨てていますが、ラズピコの回路的には少々危険な気がします。この場合どうしたらよいのか…。

圧電素子は両面テープでしっかりと固定してください。振動すると波形が乱れて良くありません。

ソフトの準備

ラスピコのソフトウェアは、TBSKmodemMicroのArudino版を使います。TBSKmodemMicroは32ビットマイコン向けにC++14で実装したライブラリです。

チュートリアルに従ってライブラリをArduinoIDEにセットアップしてください。

  1. https://github.com/nyatla/TBSKmodemMicro/releasesからTbskModemMicro-Ardiuno.zipをダウンロードします。
  2. Arduino IDEを起動し、スケッチ→ライブラリをインクルード→Zip形式のライブラリをインストール...を選択します。
  3. ダウンロードしたライブラリを選択します。
  4. ライブラリを有効化するためにArduino IDEを再起動してください。

送信テスト

送信テストをします。

サンプルにある、SendHelloを少し改造して使います。
SendHelloは、数秒間隔でメッセージを送信するビーコンのスケッチです。

ファール→スケッチ例→TBSKmodemMicro→SendHelloを選択します。
スケッチを開き、SPK_PINをAD0(26)に書き換えます。
コンパイルして、ラズピコにバイナリを書き込んでください。

#include "TbskModemMicro.h"
using namespace TBSKmodemMicro;

#define LED_PIN 25
#define SPK_PIN 26 //←ここ!!!
TbskPulseModulator<100> tpm;
void setup() {
  pinMode(LED_PIN, OUTPUT);  //LED
  pinMode(SPK_PIN, OUTPUT);   //OutputPin
}
void loop()
{
  digitalWrite(LED_PIN, HIGH);  // turn the LED on (HIGH is the voltage level)
  tpm.write(SPK_PIN,16000,"Hello arduino");  //send message via GPIO pulse audio.
  digitalWrite(LED_PIN, LOW);  // turn the LED on (HIGH is the voltage level)
  delay(1000);

}

ラズピコからジージーと音が鳴ったら、適当なスマホかPCでhttps://nyatla.jp/tbskmodem/ を開いてStartボタンを押します。

アプリ側ににメッセージが表示されればOKです!
(TBSK変調はエラー訂正がないので、文字化けが発生することがあります。また、生活音環境下では末尾にゴミが混じりやすくなります。)

https://www.youtube.com/watch?v=sXGtD-wOdPk

送受信テスト

送受信ができるかテストします。
圧電素子をセンサにした音波受信は結構シビアなので忍耐力が必要です。

サンプルにある、EchoBackのスケッチを使います。

#include "TbskModemMicro.h"
using namespace TBSKmodemMicro;

TbskPulseModulator<100> tpm;
TbskDemodulator<100> dem;

#define LED_PIN 25
#define AD_PIN 26
#define SP_PIN 26

void setup() {
  pinMode(LED_PIN, OUTPUT);  //LED
}

void loop()
{
  int r=-1;
	TMM_UINT8 recv[10] = {};  
  digitalWrite (LED_PIN,HIGH);
  analogReadResolution(12);

  while(r==-1){
     r=dem.read(AD_PIN,16000,recv,10,5000);
  }
  digitalWrite (LED_PIN,LOW);
  delay(1000);
  //send back data
  TMM_UINT8 send[16]={};
  memcpy(send,"echo:",5);
  memcpy(send+5,recv,r);
  pinMode(SP_PIN, OUTPUT);
  tpm.write(SP_PIN,16000,(const char*)send,r+5);

  return;
}

このスケッチは、音波通信を受信すると、その内容に"echo:"プレフィクスをつけてそのまま送り返してくれます。

コンパイルして、ラズピコにバイナリを書き込んでください。
ラスピコ基板にあるLEDが点灯したら準備完了です。

送信側は、https://nyatla.jp/tbskmodem/ を使用します。
ブラウザで開いてStartボタンを押したら、送信欄に"test"を入力して送信ボタンを押します。

さて、ここで1つ大きな問題があります。
圧電素子をマイクの代わりに使う回路では、数センチの至近距離で圧電素子と水平に送信側の出力を配置し、大きめの音で送信しないと、音波がラズピコのセンサに到達しません。

送受信機は出来るだけ水平に向かい合わせで配置する必要があります。

何度か音を出して受信してくれる配置を探すことを繰り返してください。ラズピコが受信に成功すると、LEDが一旦消灯し、信号を送り返してくれるはずです。

https://www.youtube.com/watch?v=B9rHDmMfdlc

数十回試してもダメな場合は、別にマイクモジュールを用意して、入力と出力回路の分離を検討しましょう。

ラズピコのファームを作る

Echobackのコードを基に、"ON"を受信したらLEDをON、"OFF"を受信したらLEDをOFFするように改造します。

#include "TbskModemMicro.h"
using namespace TBSKmodemMicro;

TbskPulseModulator<100> tpm;
TbskDemodulator<100> dem;
#define LED_PIN 25
#define AD_PIN 26
#define SP_PIN 26

bool current_led=true;
void setup() {
  pinMode(LED_PIN, OUTPUT);  //LED
  pinMode(AD_PIN, INPUT);
  pinMode(SP_PIN, OUTPUT);
  analogReadResolution(12);
}
void loop()
{
  int r=-1;
  TMM_UINT8 recv[10] = {};
  digitalWrite (LED_PIN,current_led?HIGH:LOW);

  pinMode(AD_PIN, INPUT);
  while(r==-1){
     r=dem.read(AD_PIN,16000,recv,10,5000);
  }
  char status[7+3+1];
  if(memcmp(recv,"on",2)==0){
    //on
    sprintf(status,"status:on\0");    
    current_led=true;
  }else if(memcmp(recv,"off",3)==0){
    //off
    sprintf(status,"status:off\0");    
    current_led=false;
  }else{
    sprintf(status,"ERROR");
  }
  delay(.5);
  pinMode(SP_PIN, OUTPUT);
  tpm.write(SP_PIN,16000,(const char*)status);
  return;
}

このコードは、受信したデータをif文判定し、フラグを切り替えています。
LED切替だけだとつまらないので、ついでに応答も返すようにしました。

やたらと同じピンのpinModeを操作していますが、これは圧電素子を入出力兼用しているためです。ADCピンとDIGITALOUTを1ピンで切り替える場合、pinモードをこの順番で操作しないとADCが正常動作しないことがあります。

コンパイルして、ラズピコに書き込んでおきましょう。

ブラウザアプリ

次は操作するためのブラウザアプリケーションを作ります。
ライブラリは、Javascript用のTbskmodemJSを使います。

ライブラリの準備

https://github.com/nyatla/TBSKmodemJS/releases からTBSKmodemJS.zipをダウンロードします。

TBSKmodemJS.zipを展開したら、サンプルを起動しましょう。
サンプルの起動にはWebサーバが必要です。ここではPythonのウェブサーバを利用します。

$cd TBSKmodemJS
$python -m http.server 8000

http://127.0.0.1:8000 にアクセスしてください。getstarted/*.htmlにスタンドアロン版のサンプルがあります。

続いて、tbsksocket_send.htmlを開きます。これは送信ボタンを押すと短いメッセージを送信するサンプルアプリケーションです。

Startボタンを押した後にSendボタンを押すと、ジジジジジジと音波が送信されます。
途中でマイクアクセスを求める通知がありますので、許可してください。
(ディベロッパーツールのConsoleを見ると賑やかになります。)

これでブラウザアプリの実行環境が整いました。

注意 WebAudioのAPIは、HTTPSサーバ、もしくはローカルPC上のHTTPサーバでしか動作しません。これはブラウザの仕様です。

LEDをつけたり消したりするアプリを作る

tbsksocket_send.htmlをベースにして、ラズピコのLEDをON/OFFするアプリを作っていきます。

はい!できました!

<!doctype html>
<html lang=en-us>
<head>
  <meta charset=utf-8>
  <meta content="text/html; charset=utf-8" http-equiv=Content-Type>
  <title>TBSKmodemJS</title>
</head>
<body>
    <h1>LED ON/OFF.</h1>
    <hr/>
    <script async src="../dist/tbskmodem.js"></script>
    <script>

        window.addEventListener('load', ()=>
        {
          window.addEventListener('unload',()=>{
            console.log("Shutdown sequence.");
            shutdown=true;
            socket.close();
          });
          let started=false;
          document.getElementById("start").addEventListener("click",()=>{
            if(started){
              return;
            }
            started=true;
            let env=TBSKmodemJS.checkEnvironment();
            if(env.success!=true){
              alert("Insufficient environment required for TBSKmodemJS");
              console.log(JSON.stringify(env)+"\n");
              throw Error();
            }
            console.log("env",env);
            
            //tbsk setup
            TBSKmodemJS.load().then((tbsk)=>{
              console.log(tbsk.version);
              socket=new tbsk.misc.TbskSocket({carrier:16000,encoding:"utf8"});
              socket.addEventListener("open",(event)=>{
                console.log("Socket open!");
                document.getElementById("on").addEventListener("click",()=>{
                  socket.send("on");
                });
                document.getElementById("off").addEventListener("click",()=>{
                  socket.send("off");
                });
              });
              let msg;
              socket.addEventListener("detected",(event)=>{
                msg="";
              });
              socket.addEventListener("message",(event)=>{
                msg+=event.data;
              });
              socket.addEventListener("lost",(event)=>{
                alert(msg);
              });
            });
        });
      });
  </script>
  <style>
  body{text-align:center;}
  #start,#on,#off{font-size:3em;width:5em;margin:.2em auto;}
  </style>
<div>
	<button id="start">START</button><br/>
	<button id="on">ON</button><br/>
	<button id="off">OFF</button>
</div>
</body>
</html>

主な変更点はボタンの追加と、その中でイベントを処理する部分です。

ファイルを保存して、サーバーに配置してからブラウザから開きましょう。Startボタンを押すとアプリケーションが開始され、ON/OFFボタンを押すとジジジジと音が出ると思います。

サーバに配置するのが面倒な方はこちらからどうぞ。
https://nyatla.jp/tbskmodem/v0.4.1/getstarted/_on_off_sample_for_zenn.html

コードの説明

まずライブラリを初期化します。
TBSKmodemJS.load関数は非同期関数なので解決してから次に移ります。

TBSKmodemJS.load().then((tbsk)=>{

搬送波周波数を指定してTbskSocketのインスタンスを生成します。
TbskSocketクラスは通信システムの中核です。Websocketと同じ(ような)インタフェイスで音波通信を使うことができます。

socket=new tbsk.misc.TbskSocket({carrier:16000,encoding:"utf8"});

インスタンスを生成したら、イベントハンドラを設定していきます。
openイベントはソケットが有効になると呼び出されるハンドラです。有効になるまではボタンを無効化しておきたいので、このハンドラの中でボタンのイベントを設定しました。

socket.addEventListener("open",(event)=>{

detected,message,lostはメッセージの受信に関するイベントです。TbskSocketの通信速度は遅いので、1つのメッセージを受信し終わるのに長い時間がかかります。

そこで、まず初めにdetected(検出)イベントを呼び出し、一文字づつmessageで通知して、最後に信号を見失ったことを伝えるlostイベントを呼び出すようになっています。

detectedは必ずlostイベントで終了します。その間にあるmsgを結合して、メッセージ全体を復元しています。

let msg;
socket.addEventListener("detected",(event)=>{
  msg="";
});
socket.addEventListener("message",(event)=>{
  msg+=event.data;
});
socket.addEventListener("lost",(event)=>{
  alert(msg);
});

テスト

作成したWebアプリをスマホで開いて、ラズピコの近くでをボタンを押すと、LEDをつけたり消したりできます。やったね!

https://youtu.be/i5aVXhBLn18

設置済みのスマホアプリ

https://nyatla.jp/tbskmodem/v0.4.1/getstarted/_on_off_sample_for_zenn.html

あとがき

性能の改善

今回のハードウェアは、単発の圧電素子でデジタルデータ通信ができるとカッコ良さそうだったのでこの形になりました。が……、実用上はかなり辛い感じです。(特に受信側)

入出力回路を分離して、入力をコンデンサマイク、出力を小型のスピーカーに置き換えれば、送受信の性能はかなり良くなると思います。

使いどころ

TBSKの音波通信は、普及している通信方式と比べると速度も遅く到達距離も短いので、他の技術の代わりに!のような目的には適しません。

しかしながら、単純なソフトウェアと規制のない音波だけで実装できるため、特殊なトランシーバーデバイスやそのための複雑怪奇な環境設定が不要で、電波と違って技適の心配もありませんし。

ブラウザ経由でちょっとしたデータをオンラインと交換したり、他の無線方式の初期コンフィギュレーション交換、無線機能の無いデバイスでシリアル回線を生やしたい時等に、便利に使えると思います。

ちなみに、ラズパイから送信だけであれば、MicroPythonでも可能です。
詳しくはこちらをご覧ください。
https://github.com/nyatla/TBSKmodemMicro/blob/main/MicroPython/README.ja.md

Discussion