【高先さんの電子工作】第25回：LINE連携で曲を切り替える

# 概要

Raspberry Pi Pico Wとパッシブブザーを使って曲を鳴らしていますが、
LINEと連携して鳴らす曲を選択できるようにしています。
以下の動画では「私は最強」と「ひろがるスカイ!プリキュア~Hero Girl~」を鳴らしています。

https://youtu.be/Sv40vn0ASiI

## 電子回路

電子回路の組み方は第14回で実演しています。

https://qiita.com/smart_agri_lab/items/6ed51d5c0d3c18492e00


## ソースコード

* index.htmlをRaspberry Pi Pico W上に配置してください。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LINE連携で曲を切り替える</title>
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <style>
        /* 選択された行のスタイル */
        .selected-row {
            background-color: orange;
        }
    </style>
</head>
<body>
    <div class="container mt-5">
        <table class="table">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>曲名</th>
                </tr>
            </thead>
            <tbody>
                <tr data-id="1">
                    <td>1</td>
                    <td>私は最強</td>
                </tr>
                <tr data-id="2">
                    <td>2</td>
                    <td>ひろがるスカイ!プリキュア~Hero Girl~</td>
                </tr>
            </tbody>
        </table>
    </div>

    <!-- jQuery and Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>

    <script>
        // テーブルの行がクリックされたときにAPIを呼び出す
        $(document).ready(function () {
            $("table tbody tr").click(function () {
                // すべての行から選択された行のスタイルを削除
                $("table tbody tr").removeClass("selected-row");
                // クリックされた行に選択された行のスタイルを適用
                $(this).addClass("selected-row");
                var id = $(this).data("id"); // 行のデータ属性からIDを取得
                // APIを呼び出すためのコードをここに追加
                // 例：APIを呼び出すためのAjaxリクエスト
                $.ajax({
                    url: "/melody?id=" + id,
                    method: "GET",
                    success: function (response) {
                        // APIのレスポンスを処理するコードをここに追加
                        console.log("API response:", response);
                        // すべての行から選択された行のスタイルを削除
                        $("table tbody tr").removeClass("selected-row"); 
                    },
                    error: function (error) {
                        console.error("API error:", error);
                    }
                });
            });
        });
    </script>
</body>
</html>

```

* config.pyにWi-Fiの接続情報をLINE Notifyのアクセストークンを記述して、Raspberry Pi Pico W上に配置して下さい。

```python:config.py
SSID = '2.4GHz帯のSSID'
PASSWORD = 'Wi-Fiのパスワード'
ACCESS_TOKEN = 'LINE Notifyのアクセストークン'
```

* メインプログラムです。

```python:main.py
from machine import Pin, PWM
from config import ACCESS_TOKEN, SSID, PASSWORD
import network
from utime import sleep
import urequests
import time
import socket
import ujson


# NPNトランジスタのベース
buzzer = PWM(Pin(15))

# 音の周波数
REST=    0
B4  =  494
Db5 =  554
D5  = 587
E5  =  659
Gb5 =  740
G5  =  784
Ab5 =  831
A5  =  880
Bb5 =  932
B5  =  988
C6  = 1047
Db6 = 1109
D6  = 1175
Eb6 = 1245
E6  = 1319


def tone(frequency, duration):
    """
    パッシブブザーで音を鳴らす。

    Parameters:
        frequency(int): 周波数
        duration(float): 音の長さ
    """
    if frequency == REST:
        sleep(duration)
        return
    buzzer.freq(frequency)
    buzzer.duty_u16(30000)
    sleep(duration)
    buzzer.duty_u16(0)
    sleep(0.002)


def watashihasaikyo():
    BPM = 192
    BEAT = 60 / BPM
    melody = [
        [E5, 3*BEAT],[Gb5, BEAT],[A5, BEAT],[Ab5, BEAT],[E5, BEAT],[Db5, BEAT],[B4, 3*BEAT],
        [B4, BEAT/2],[B4, BEAT/2],[A5, BEAT],[Ab5, BEAT],[E5, BEAT],[Db5, BEAT],[E5, 3*BEAT],
        [B5, BEAT],[B5, BEAT],[A5, BEAT],[Ab5, BEAT],[Gb5, BEAT],[E5, 2*BEAT],
        [E5, BEAT],[A5, BEAT],[Ab5, BEAT/2],[Gb5, BEAT/2],[E5, BEAT/2],[Gb5, BEAT],[E5, BEAT/2],[Gb5, BEAT],
        [E5, 2*BEAT],[E5, BEAT],[E5, BEAT/2],[E5, BEAT/2],[E5, BEAT],[B5, BEAT],[B5, BEAT],[Db6, BEAT],[B5, 1.5*BEAT],
        [B4, BEAT/2],[B5, BEAT/2],[A5, BEAT/2],[Ab5, BEAT/2],[Gb5, BEAT/2],[E5, BEAT],[Db5, BEAT],[E5, BEAT],[A5, BEAT],[Ab5, 3*BEAT],
        [E6, BEAT/2],[E6, BEAT/2],[E6, BEAT],[Eb6, BEAT],[Db6,BEAT],[B5, BEAT],[E5, 2*BEAT],
        [E5, BEAT],[A5, BEAT],[Ab5, BEAT],[Gb5, BEAT],[E5, 2*BEAT],[E6, 3*BEAT]

    ]
    for m in melody:
        tone(m[0], m[1])


def puricure():
    BPM = 184
    BEAT = 60/ BPM
    melody = [
        [D6, BEAT/2],[C6, BEAT/2],[B5, BEAT/2],[A5, 1.5*BEAT],[G5, BEAT],
        [D6, BEAT/2],[C6, BEAT/2],[B5, BEAT/2],[A5, 1.5*BEAT],[G5, BEAT],
        [D6, BEAT/2],[C6, BEAT/2],[B5, BEAT/2],[A5, 1.5*BEAT],[A5, BEAT/2],[B5,BEAT],
        [Gb5, BEAT],[Gb5, BEAT],[G5, BEAT/2],[G5, 2.5*BEAT],
        [G5, BEAT/2],[C6, BEAT/2],[B5, BEAT/2],[A5, BEAT/2],[B5, 2*BEAT],
        [G5, BEAT/2],[E6, BEAT/2],[D6, BEAT/2],[C6, BEAT/2],[D6, 1.5*BEAT],
        [G5, BEAT],[A5, 1.5*BEAT],[D6, BEAT/2],[D6, 4*BEAT],
        [D6, BEAT/2],[C6, BEAT/2],[B5, BEAT/2],[A5, 1.5*BEAT],[G5, BEAT],
        [D6, BEAT/2],[C6, BEAT/2],[B5, BEAT/2],[A5, 1.5*BEAT],[G5, BEAT],
        [D6, BEAT/2],[C6, BEAT/2],[B5, BEAT/2],[A5, 1.5*BEAT],[A5, BEAT/2],[B5,BEAT],
        [Gb5, BEAT],[G5, 2.5*BEAT],
        [A5, BEAT],[B5,BEAT],[Db6, 1.5*BEAT],[A5, BEAT/2],[A5, 3*BEAT],
        [D5, BEAT],[D5, BEAT],[G5,BEAT/2],[A5, BEAT],[B5,BEAT/2],[C6, BEAT],[B5,3*BEAT],
        [D5, BEAT],[D5, BEAT],[G5,BEAT/2],[D6, BEAT],[C6,BEAT/2],[B5, BEAT/2],[C6,BEAT/2],[B5,BEAT/2],[A5, BEAT/2],[G5,2*BEAT],
        [G5,BEAT/2],[Gb5,BEAT/2],[G5,BEAT],[D6, BEAT/2],[C6,4*BEAT],
        [D6, BEAT],[C6,BEAT],[B5,BEAT],[REST, BEAT],[D6, 3*BEAT]
    ]
    for m in melody:
        tone(m[0], m[1])


def connect():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PASSWORD)

    while not wlan.isconnected():
        print('Waiting for connection...')
        sleep(1)
        
    ip = wlan.ifconfig()[0]
    print(f'Connected on {ip}')
    return ip


def line_notify(message):
    """
    LINEにメッセージを通知する。
    
    Paratemers:
        message(str): 通知するメッセージ
    """
    endpoint = 'https://notify-api.line.me/api/notify'
    headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': f'Bearer {ACCESS_TOKEN}'}
    data = f'message={message}'.encode('utf-8')
    response = urequests.post(endpoint, headers=headers, data=data)
    response.close()


def open_socket(ip):
    """
    ソケットを開く。このソケットオブジェクトを介してクライアントとの通信を行う。

    Parameters:
        ip: WLAN上で自分自身を表すIPアドレス

    Returns:
        connection(socket.socket): ソケット接続
    """
    address = (ip, 80) # 80ポートでソケット通信を行う
    connection = socket.socket()
    connection.bind(address)
    connection.listen(1) # 同時接続の最大数1
    return connection


def serve(connection):
    """
    WEBサーバーを起動する。

    Parameters:
        connection(socket.socket): ソケット接続
    """
    print('Starting Web Server...')

    # LINEにIPを通知する
    message = f'http://{ip}'
    line_notify(message)

    while True:
        
        # 接続があるまで待機し、クライアントのソケットオブジェクトを取得する
        client = connection.accept()[0]
        # クライアントからのデータを受信する（最大1024バイト）
        request = client.recv(1024)
        # 受信したバイナリデータを文字列にする
        request = str(request)
        try:
            # リソースのパスを取得する
            request = request.split()[1]
            print(request)
        except IndexError:
            pass
  
        if '/melody' in request:
            params = request.split('?')[1]
            id = int(params.split('=')[1])
            if id == 1:
                watashihasaikyo()
            elif id == 2:
                puricure()
            response = ujson.dumps({'message': 'ok'})
            client.send("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" + response)
            client.close()
            continue


        # HTTPレスポンスを生成する
        response = 'HTTP/1.1 200 OK\r\n'
        response += 'Content-Type: text/html\r\n\r\n'

        with open('index.html', 'r') as f:
            response += f.read()

        # HTTPレスポンスを送信する
        client.send(response)
        # クライアントソケットを閉じる
        client.close()

ip = connect()
connection = open_socket(ip)
try:
    serve(connection)
except Exception as e:
    print(e)
    connection.close()
    machine.soft_reset()
    buzzer.duty_u16(0)

```