【高先さんの電子工作】第19回：シフトレジスタとLINEを使ってLEDを操作する

# 概要

動画の中で実演しています。

https://youtu.be/UV53K3JNIIc

## 準備

### 1. LINE Notifyのアクセストークンを取得する

以下の記事を参考にすると良いと思います。

https://qiita.com/bobrock/items/25664686ce212c315dbd

### 2. 設定ファイルを準備する

config.jsonファイルにWi-Fiの接続情報とLINE Notifyのアクセストークンを書き込んでRaspberry Pi Pico W上に配置してください。

```json:config.json
{
    "ssid": "2.4GHz帯のSSID",
    "password": "Wi-Fiのパスワード",
    "access_token": "1で取得したLINE Notifyのアクセストークン"
}
```

### 3. ソースコードを配置する

以下のindex.htmlとmain.pyをRaspberry Pi Pico W上に配置する。

ブラウザから操作するためのWebページ。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
  <title>シフトレジスタを使ってLEDを操作する</title>
</head>
<body>

<div class="container">
  <div class="row">
    <div class="col-sm-3" style="display: flex; justify-content: center; align-items: center; margin-top: 30px;">
      <form action="./red">
        <button type="submit" class="btn btn-danger" style="font-size: 1.5em; padding: 10px 20px;">赤</button>
      </form>
    </div>
    <div class="col-sm-3" style="display: flex; justify-content: center; align-items: center; margin-top: 30px;">
      <form action="./green">
        <button type="submit" class="btn btn-success" style="font-size: 1.5em; padding: 10px 20px;">緑</button>
      </form>
    </div> 
    <div class="col-sm-3" style="display: flex; justify-content: center; align-items: center; margin-top: 30px;">
      <form action="./blue">
        <button type="submit" class="btn btn-primary" style="font-size: 1.5em; padding: 10px 20px;">青</button>
      </form>
    </div>
    <div class="col-sm-3" style="display: flex; justify-content: center; align-items: center; margin-top: 30px;">
      <form action="./yellow">
        <button type="submit" class="btn btn-warning" style="font-size: 1.5em; padding: 10px 20px;">黄</button>
      </form>
    </div>
  </div>
</div>
</body>
</html>
```

メインとなるプログラム。

```python:main.py
from machine import Pin, reset
import time
import socket
import network
import ujson
import urequests


sdi = machine.Pin(0, Pin.OUT)
rclk = machine.Pin(1, Pin.OUT)
srclk = machine.Pin(2,Pin.OUT)
ip = None


def get_line_config():
    """
    LINE Notifyの設定情報を取得する

    Returns:
        access_token(str): LINE Notifyのアクセストークン
    """
    with open('config.json', 'r') as f:
        data = ujson.load(f)
        return data['access_token']


def notify(message):
    """
    LINE Notifyを利用してトークルームにメッセージを送信する。

    Parameters:
        message(str): 送信するメッセージの内容

    Returns:
        response(Response) LINE Notifyのレスポンス
    """
    endpoint = 'https://notify-api.line.me/api/notify'
    access_token = get_line_config()
    headers = {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/x-www-form-urlencoded'
    }
    data = f'message={message}'.encode('utf-8')
    response = urequests.post(endpoint, headers=headers, data=data)
    return response


def hc595_shift(dat):
    """
    8ビットのデータを入力して8チャンネルを同時にON/OFFする。
    """
    rclk.low()
    time.sleep_ms(5)
    for bit in range(7, -1, -1):
        srclk.low()
        time.sleep_ms(5)
        value = 1 & (dat >> bit)
        sdi.value(value)
        time.sleep_ms(5)
        srclk.high()
        time.sleep_ms(5)
    time.sleep_ms(5)
    rclk.high()
    time.sleep_ms(5)


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

    # LINEにIPを通知する
    message = f'http://{ip}'
    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 '/red' in request:
            hc595_shift(0b00010001)
        elif '/green' in request:
            hc595_shift(0b00100010)
        elif '/blue' in request:
            hc595_shift(0b01000100)
        elif '/yello' in request:
            hc595_shift(0b10001000)


        # 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()


if __name__ == '__main__':
    try:
        # LEDを初期化する
        hc595_shift(0b00000000)

        # 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()

```

### 4. 電子回路を組む

電子回路は以下の記事を参考にしてください。


https://docs.sunfounder.com/projects/kepler-kit/ja/latest/pyproject/py_74hc595_led.html#py-74hc-led

### 5. モバイルバッテリーに接続する

モバイルバッテリーに接続するとLINEにRaspberry Pi Pico WにアクセスするためのURL（IP）が通知される。

![IMG_39E7A09B1AB2-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185370/7d38d538-4e20-e182-b483-be0e390feb2d.jpeg)

URLをクリックするとLEDを操作する画面が表示される。

![IMG_263F44DF42C1-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185370/21300b71-24b6-05d2-e6aa-62c47518d2e2.jpeg)

ボタンを押すと各色のLEDが点灯する。