【高先さんの電子工作】第13回：PWMを使ってLEDを調光する

https://youtu.be/KjZIKGQNL3g

Raspberry Pi Pico WでWeb Serverを起動して、ブラウザからLEDを調光するプログラムです。

```python:main.py
from machine import Pin, PWM, reset
import socket
import network
import ujson
from utime import sleep


class WiFiConnectError(Exception):
    pass


def connect():
    """
    Wi-Fiに接続する。

    Returns:
        ip (str): IPアドレス
    Raises:
        WiFiConnectError: Wi-Fiに接続できなかった場合
    """
    # Wi-Fiの接続情報を取得する
    config = {}
    with open('config.json', 'r') as f:
        config = ujson.load(f)

    # Wi-Fiに接続する
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(config['ssid'], config['password'])

    timeout = 10
    while timeout > 0:
        timeout -= 1
        print('Waiting for connection...')
        if wlan.isconnected():
            ip = wlan.ifconfig()[0]
            print(f'Connected: {ip}')
            return ip
        sleep(1)
    
    raise WiFiConnectError('Wi-Fiに接続できませんでした。')


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...')

    while True:
        # 接続があるまで待機し、クライアントのソケットオブジェクトを取得する
        client = connection.accept()[0]
        # クライアントからのデータを受信する（最大1024バイト）
        request = client.recv(1024)
        # 受信したバイナリデータを文字列にする
        request = str(request)
        try:
            # リソースのパスを取得する
            # request.split()[0]: HTTPメソッド
            # request.split()[1]: リソースのパス
            request = request.split()[1]
            print(request)
        except IndexError:
            pass

        # スライドバーの値を取得する        
        if '/brightness?value=' in request:
            value = int(request.split('=')[1])
            brightness = 65535 - value
            rate = int(value * 100 / 65535)
            print(f'{rate}%')
            response = ujson.dumps({'rate': rate})
            client.send("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" + response)
            client.close()
            # LEDを調光する
            led.duty_u16(brightness)
            continue

        # HTTPレスポンスを生成する
        response = 'HTTP/1.1 200 OK\r\n'
        response += 'Content-Type: text/html\r\n\r\n'
        response += """
            <!DOCTYPE html>
            <html lang="ja">
            <head>
              <meta charset="UTF-8">
              <title>PWMでLEDを調光する</title>
            </head>
            <body style="display: flex;justify-content: center;align-items: center;height: 100vh;margin: 0;">
              <div id="slider-container" style="text-align: center;">
                <h1 style="font-family: Arial, sans-serif;">0%</h1>
                <input type="range" id="brightnessSlider" min="0" max="65535" style="width: 80%;">
              </div>

              <script>
                const brightnessSlider = document.getElementById('brightnessSlider');
                const h1Element = document.querySelector('h1');

                brightnessSlider.addEventListener('change', function() {
                    const brightnessValue = brightnessSlider.value;
                    console.log('選択された明るさ値: ' + brightnessValue);

                    fetch(`/brightness?value=${brightnessValue}`)
                        .then(response => {
                            if (!response.ok) {
                                throw new Error('API呼び出しエラー');
                            }
                            return response.json();
                        })
                        .then(data => {
                            console.log(data)
                            h1Element.textContent = `${data.rate}%`;
                        })
                        .catch(error => {
                            console.error('API呼び出しエラー:', error);
                        });
                });
              </script>
            </body>
            </html>
        """
        # HTTPレスポンスを送信する
        client.send(response.encode('utf-8'))
        # クライアントソケットを閉じる
        client.close()      


if __name__ == '__main__':
    try:
        # LEDを操作するピンをインスタンス化する
        led = PWM(Pin(15))
        led.freq(1000)
        brightness = 65535
        led.duty_u16(brightness)

        # Wi-Fiに接続する
        ip = connect()

        # ソケットを開く
        connection = open_socket(ip)

        # Web Serverを起動する
        serve(connection)
    except WiFiConnectError as e:
        print(f'Error: {e}')
    except Exception as e:
        # 再起動する
        reset()

```

Wi-Fiの接続情報を書き込む設定ファイル。

```json:config.json
{"ssid": "Your SSID", "password": "Your Password"}
```