2023年3月28日火曜日

Raspberry Pi Pico W を使って Wifi 経由で回路を制御してみよう

0. はじめに

本書の電子工作パーツをRaspberry Pi Picoで利用する」にて、本書の公式パーツセットを Raspberry Pi Pico (以下 Pico)で利用する方法を紹介しました。

Wifi 機能のない Raspberry Pi Pico で実行できる演習は、 本書で Wifi 接続を用いない 1 章から 8 章の内容でした。

一方、2023年3月、Wifi 機能付きの Raspberry Pi Pico W (以下 Pico W)が日本で販売開始されました。Pico W を用いると、 Wifi 接続を用いる 9 章および 10 章の演習をある程度再現できるようになります。 具体的には、本ページでは下記の内容を取り扱います。
  • 9章の内容: PC やスマートフォンのブラウザで、Pico W に接続された LED / I2C温度センサ / RGB フルカラー LED / DCモータ / サーボモータ を制御
  • 10章の内容: PCやスマートフォンのブラウザで、Pico W を搭載したキャタピラ式模型を制御
10章のキャタピラ式模型を Pico W で実現した様子を示したのが下図です。もともと Raspberry Pi が配置されていたスペースが空いていますが、これは 回路と Pico W を全てブレッドボード上に配置できるからです。回路動作用のバッテリーが、大電流を流せないものでも良い、というメリットもあります。
そして、このキャタピラ式模型の動作の様子が下記の YouTube 動画です。



なお、Wifi 経由での Pico W の制御には欠点もあり、回路への制御信号の送信が、ブラウザのページ再読み込みのタイミングでしか行えないため、ブラウザに対して行ったアクションへのレスポンスが悪い、という問題があります。

さて、本ページでは以上の内容の実現方法を解説していきます。

その内容に進む前に、「本書の電子工作パーツをRaspberry Pi Picoで利用する」を参考に、 下記の内容を進めておいてください。Pico W でも、Pico 用の演習をすべて実行することができます。Pico W を購入できるサイトもリンク先で紹介されています。
  • Pico W への MicroPython 環境のインストール (Pico と Pico W では UF2 ファイルが異なるので注意)
  • Windows などへの、開発環境 Thonny のインストール
  • Pico W で Python プログラムを実行する方法の習得

1. ブラウザのボタンによるLEDの点灯

まずは、PCやスマートフォンのブラウザから、Pico W に接続された LED のオン / オフを切り替える演習を行ってみましょう。9.3 章の内容です。必要な回路は下図の通りです。
見てわかる通り、LED以外に I2C 接続の小型 LCD も接続されています。これは、Pico W に割り当てられた IP アドレスやエラーメッセージを表示するためのものです。この LCD を接続しなくても演習は実行可能ですが、その場合、Pico W からのメッセージを Windows などの PC 上で見るしかなくなります。LCD を接続すること、すなわち Windows などの PC なしでも Pico W を動作させられるようにすることを強く推奨します。

さて、この回路に対して実行するプログラムは以下の通りです。なお、本ページで紹介するプログラムはすべてこちらのページの解説を参考に作成しました。
import time
import sys
import network
import socket
from time import sleep
from machine import Pin, I2C

led = Pin(16, Pin.OUT)
i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

##### for LCD
def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。30から40程度を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()
##### end of LCD

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

html = """<!DOCTYPE html><html>
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<title>1 - LEDの点灯制御</title>
<style>button {
    width: auto;
    height: auto;
    background: %s;
    font-weight: normal;
    font-size: 14pt;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 14px;
    color: #ffffff;
    padding: 10px 20px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: 1px solid #003366;
    -moz-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    -webkit-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    text-shadow:
        0px -1px 0px rgba(000,000,000,0.7),
        0px 1px 0px rgba(255,255,255,0.3);
}
</style></head>
<p>下のボタンを押すごとに、LEDの点灯状態が変化します。</p>
<div align="center">
<form><button name="led" value="toggle" type="submit">LED</button></form>
</div>
</body></html>
"""

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    time.sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(addr)
except OSError as e:
    print(e)
    s.close()
    s = None
    write_string('socket error') #showing on LCD
    sys.exit()

s.listen(1)
print('listening on', addr)

ledState = 0
buttonColor = '#003366'

# Listen for connections, serve client
while True:
    try:
        cl, addr = s.accept()
        #print('client connected from', addr)
        request = cl.recv(1024)
        #print("request:")
        #print(request)
        request = str(request)
        led_toggle = request.find('led=toggle')

        #print( 'led_toggle = ' + str(led_toggle))

        if led_toggle == 8:
            #print("led toggle")
            ledState = 1 - ledState
            led.value(ledState)
            if ledState == 0:
                buttonColor = '#003366'
            else:
                buttonColor = '#26a1ff'

        response = html % buttonColor
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        print('connection closed')

    except KeyboardInterrupt as e:
        break

s.close()
s = None
このプログラムを実行するための注意をいくつか記します。本ページのプログラムにはすべて下記のように、用いている Wifi の SSID (アクセスポイント名) とそのパスワードを記す項目があります。 ここを皆さんの環境における SSID とパスワードに変更しないと本ページのプログラムは全て動作しませんのでご注意ください。なお、2.4GHzの周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。
ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'
また、本ページの回路を LCD なしで実行する場合、プログラム中の下記の部分を消し、
##### for LCD
(省略)
##### End of LCD
下記の内容で差し替える必要があります。この変更も、本ページのすべてのプログラムで共通です。
def write_string(s):
    pass
さらに、プログラムの書き換え時は、Pico W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

記述できたら Pico W でプログラムを実行してみましょう。LCD 上に(または Thonny のコンソール上に)Pico W に割り当てられた IP アドレス(例えば、 192.168.1.40 のようなもの)が表示されれば、実行が成功した可能性が高いでしょう。下図のような状態です。
そうしたら、Pico W と同じネットワーク内に存在する PC やスマートフォンのブラウザでアドレス(例えば 192.168.1.40 の場合、 http://192.168.1.40/ )にアクセスしてみましょう。下図のようなページが現れるはずです。
ブラウザ上のボタンを押すことで Pico W に接続された LED の点灯/消灯が切り替わります。LED 点灯時の画面の様子は下記の通りで、ボタンの色が変わります。これは、本書の Raspberry Pi 版のプログラムと同じ挙動です。ただし、上で既に述べたように、回路のアクションはブラウザ上のページの再読み込みのタイミングでしか起こりませんので、反応が遅いという問題がありますのでご了承ください。これは、本ページの以下のプログラム全てに当てはまります。
なお、ここでの解説がよくわからないという場合、本書 9 章の記述を読み直してみることを推奨します。本ページは、本書読者の方を対象としているため、本書と重複する内容の記述は最低限にとどめています。

2. ブラウザへの温度センサの値の表示

PCやスマートフォンのブラウザに、Pico W に接続された I2C 温度センサの値を表示する演習を行ってみましょう。9.4 章の内容です。必要な回路は下図の通りです。
そして、この回路上で実行すべきプログラムは下記の通りです。
import time
import sys
import network
import socket
from time import sleep
from machine import Pin, I2C

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()

##### for temperature sensor
def read_adt7410():
    word_data = int.from_bytes(i2c.readfrom_mem(address_adt7410, register_adt7410, 4), 'little')
    data = (word_data & 0xff00)>>8 | (word_data & 0xff)<<8
    data = data>>3 # 13ビットデータ
    if data & 0x1000 == 0:  # 温度が正または0の場合
        temperature = data*0.0625
    else: # 温度が負の場合、 絶対値を取ってからマイナスをかける
        temperature = ( (~data&0x1fff) + 1)*-0.0625
    return temperature

address_adt7410 = 0x48
register_adt7410 = 0x00
##### end of temperature sensor

##### for LCD
def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。30から40程度を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()
##### end of LCD

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

# refresh per 5 sec.
html = """<!DOCTYPE html><html>
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta http-equiv="refresh" content="5">
<title>2 - I2C温度センサ(ADT7410)による温度の取得</title>
</head>
<div align="center">
<p>温度: %s</p>
</div>
</body></html>
"""

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    time.sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(addr)
except OSError as e:
    print(e)
    s.close()
    s = None
    write_string('socket error') #showing on LCD
    sys.exit()

s.listen(1)
print('listening on', addr)

# Listen for connections, serve client
while True:
    try:
        cl, addr = s.accept()
        #print('client connected from', addr)
        request = cl.recv(1024)
        #print("request:")
        #print(request)
        request = str(request)
        inputValue = read_adt7410()
        inputStr = '{:.1f}'.format(inputValue)

        response = html % inputStr
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        print('connection closed')

    except KeyboardInterrupt as e:
        break

s.close()
s = None
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

以上のプログラムを実行し、ブラウザでアクセスすると下記のようなページが開きます。
5秒おきにページが再読み込みされ、センサの値がページ上で更新されるようになっています。 これは、プログラム中の下記の部分で設定されています。あまり頻繁に通信が行われるのも良くないですので、この部分を小さな値に変更することはお勧めしません。
<meta http-equiv="refresh" content="5">


3. ブラウザのボタンによるRGBフルカラーLED の制御

PCやスマートフォンのブラウザで、Pico W に接続された RGBフルカラーLED の色を制御する演習を行ってみましょう。9.5 章の内容です。

なお、Rapsberry Pi を対象とした本書では、RGBフルカラーLEDの色の制御に、ブラウザのスライダを用いました。 しかし、Pico W ではスライダを用いるのは難しそうに思えましたので(専門的に言えば、JavaScript で取得したスライダの値を回路に反映する方法が思いつかない)、簡易的にボタンによる制御を行うことにしました。

必要な回路は下図の通りです。
そして、この回路上で実行すべきプログラムは下記の通りです。
import time
import sys
import network
import socket
from time import sleep
from machine import Pin, PWM, I2C

pwm1 = PWM(Pin(16))
pwm2 = PWM(Pin(17))
pwm3 = PWM(Pin(18))
pwm1.freq(100)
pwm2.freq(100)
pwm3.freq(100)
pwm1.duty_u16(0)
pwm2.duty_u16(0)
pwm3.duty_u16(0)
# アノードコモンの場合、下記の3行を有効に   
#pwm1.duty_u16(65535)
#pwm2.duty_u16(65535)
#pwm3.duty_u16(65535)

i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

##### for LCD
def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。30から40程度を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()
##### end of LCD

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

html = """<!DOCTYPE html><html>
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<title>3 - RGBフルカラーLEDの制御</title>
<style>button {
    width: auto;
    height: auto;
    background: #003366;
    font-weight: normal;
    font-size: 14pt;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 14px;
    color: #ffffff;
    padding: 10px 20px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: 1px solid #003366;
    -moz-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    -webkit-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    text-shadow:
        0px -1px 0px rgba(000,000,000,0.7),
        0px 1px 0px rgba(255,255,255,0.3);
}
</style></head>
<p>ボタンをクリックすることで、RGBフルカラーLEDの色が変わります。</p>
<p>どの行のボタンが赤、青、緑の何色に対応するかは用いるRGBフルカラーLEDの種類によって異なります。</p>
<div align="center">
<form>色 1 <button name="col1_0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="col1_1" value="on" type="submit" style="background: %s;"> 1 </button>
<button name="col1_2" value="on" type="submit" style="background: %s;"> 2 </button>
<button name="col1_3" value="on" type="submit" style="background: %s;"> 3 </button>
<button name="col1_4" value="on" type="submit" style="background: %s;"> 4 </button>
</form>
<br /><br />
<form>色 2 <button name="col2_0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="col2_1" value="on" type="submit" style="background: %s;"> 1 </button>
<button name="col2_2" value="on" type="submit" style="background: %s;"> 2 </button>
<button name="col2_3" value="on" type="submit" style="background: %s;"> 3 </button>
<button name="col2_4" value="on" type="submit" style="background: %s;"> 4 </button>
</form>
<br /><br />
<form>色 3 <button name="col3_0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="col3_1" value="on" type="submit" style="background: %s;"> 1 </button>
<button name="col3_2" value="on" type="submit" style="background: %s;"> 2 </button>
<button name="col3_3" value="on" type="submit" style="background: %s;"> 3 </button>
<button name="col3_4" value="on" type="submit" style="background: %s;"> 4 </button>
</form>
<br /><br />
</div>
</body></html>
"""

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    time.sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(addr)
except OSError as e:
    print(e)
    s.close()
    s = None
    write_string('socket error') #showing on LCD
    sys.exit()

s.listen(1)
print('listening on', addr)

onCol = '#26a1ff'
offCol = '#003366'
col1_strs = (onCol, offCol, offCol, offCol, offCol)
col2_strs = (onCol, offCol, offCol, offCol, offCol)
col3_strs = (onCol, offCol, offCol, offCol, offCol)
colall_strs = col1_strs + col2_strs + col3_strs
buttonValMax = 4
# Listen for connections, serve client
while True:
    try:
        cl, addr = s.accept()
        #print('client connected from', addr)
        request = cl.recv(1024)
        #print("request:")
        #print(request)
        request = str(request)
        col1_0_on = request.find('col1_0=on')
        col1_1_on = request.find('col1_1=on')
        col1_2_on = request.find('col1_2=on')
        col1_3_on = request.find('col1_3=on')
        col1_4_on = request.find('col1_4=on')
        col2_0_on = request.find('col2_0=on')
        col2_1_on = request.find('col2_1=on')
        col2_2_on = request.find('col2_2=on')
        col2_3_on = request.find('col2_3=on')
        col2_4_on = request.find('col2_4=on')
        col3_0_on = request.find('col3_0=on')
        col3_1_on = request.find('col3_1=on')
        col3_2_on = request.find('col3_2=on')
        col3_3_on = request.find('col3_3=on')
        col3_4_on = request.find('col3_4=on')

        if col1_0_on == 8:
            col1_strs = (onCol, offCol, offCol, offCol, offCol)
            duty = 0
        if col1_1_on == 8:
            col1_strs = (offCol, onCol, offCol, offCol, offCol)
            duty = 1
        if col1_2_on == 8:
            col1_strs = (offCol, offCol, onCol, offCol, offCol)
            duty = 2
        if col1_3_on == 8:
            col1_strs = (offCol, offCol, offCol, onCol, offCol)
            duty = 3
        if col1_4_on == 8:
            col1_strs = (offCol, offCol, offCol, offCol, onCol)
            duty = 4

        if col2_0_on == 8:
            col2_strs = (onCol, offCol, offCol, offCol, offCol)
            duty = 0
        if col2_1_on == 8:
            col2_strs = (offCol, onCol, offCol, offCol, offCol)
            duty = 1
        if col2_2_on == 8:
            col2_strs = (offCol, offCol, onCol, offCol, offCol)
            duty = 2
        if col2_3_on == 8:
            col2_strs = (offCol, offCol, offCol, onCol, offCol)
            duty = 3
        if col2_4_on == 8:
            col2_strs = (offCol, offCol, offCol, offCol, onCol)
            duty = 4

        if col3_0_on == 8:
            col3_strs = (onCol, offCol, offCol, offCol, offCol)
            duty = 0
        if col3_1_on == 8:
            col3_strs = (offCol, onCol, offCol, offCol, offCol)
            duty = 1
        if col3_2_on == 8:
            col3_strs = (offCol, offCol, onCol, offCol, offCol)
            duty = 2
        if col3_3_on == 8:
            col3_strs = (offCol, offCol, offCol, onCol, offCol)
            duty = 3
        if col3_4_on == 8:
            col3_strs = (offCol, offCol, offCol, offCol, onCol)
            duty = 4

        # アノードコモンの場合、下記の行を有効に   
        #duty = buttonValMax - duty

        if col1_0_on == 8 or col1_1_on == 8 or col1_2_on == 8 or col1_3_on == 8 or col1_4_on == 8:
            pwm1.duty_u16(int(duty*65535/buttonValMax))
        if col2_0_on == 8 or col2_1_on == 8 or col2_2_on == 8 or col2_3_on == 8 or col2_4_on == 8:
            pwm2.duty_u16(int(duty*65535/buttonValMax))      
        if col3_0_on == 8 or col3_1_on == 8 or col3_2_on == 8 or col3_3_on == 8 or col3_4_on == 8:
            pwm3.duty_u16(int(duty*65535/buttonValMax))

        colall_strs = col1_strs + col2_strs + col3_strs

        response = html % colall_strs 
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        print('connection closed')

    except KeyboardInterrupt as e:
        break

s.close()
s = None
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

以上のプログラムを実行じ、ブラウザで LCD 上のアドレスにアクセスすると、下記のようなページが開きます。スライダの代わりに、ボタンによってRGBフルカラーLEDの3つの色の明るさを調整する仕組みです。


4. ブラウザのボタンによる DC モーターの速度制御

PCやスマートフォンのブラウザで、Pico W に接続された DC モーターの回転速度を制御する演習を行ってみましょう。9.6 章の内容です。

なお、Rapsberry Pi を対象とした本書では、DC モーターの速度制御に、ブラウザのタッチイベントを用いました。 しかし、Pico W ではタッチイベントを用いるのは難しそうに思えましたので(専門的に言えば、JavaScript で取得したタッチイベントの情報を回路に反映する方法が思いつかない)、簡易的にボタンによる制御を行うことにしました。

必要な回路は下図の通りです。
import time
import sys
import network
import socket
from time import sleep
from machine import Pin, PWM, I2C

pwm1 = PWM(Pin(16))
pwm2 = PWM(Pin(17))
pwm1.freq(100)
pwm2.freq(100)
pwm1.duty_u16(0)
pwm2.duty_u16(0)

i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

##### for LCD
def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。30から40程度を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()
##### end of LCD

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

html = """<!DOCTYPE html><html>
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<title>4 - ボタンによるDCモーターの速度制御</title>
<style>button {
    width: auto;
    height: auto;
    background: #003366;
    font-weight: normal;
    font-size: 14pt;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 14px;
    color: #ffffff;
    padding: 10px 20px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: 1px solid #003366;
    -moz-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    -webkit-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    text-shadow:
        0px -1px 0px rgba(000,000,000,0.7),
        0px 1px 0px rgba(255,255,255,0.3);
}
</style></head>
<p>押したボタンにより、モーターが回転を開始します。中央のボタンをクリックしないとモーターは止まりません。</p>
<div align="center">
<form><button name="vel_n2" value="on" type="submit" style="background: %s;"> -2 </button>
<button name="vel_n1" value="on" type="submit" style="background: %s;"> -1 </button>
<button name="vel_0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="vel_p1" value="on" type="submit" style="background: %s;"> +1 </button>
<button name="vel_p2" value="on" type="submit" style="background: %s;"> +2 </button>
</form>
<br /><br />
</div>
</body></html>
"""

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    time.sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(addr)
except OSError as e:
    print(e)
    s.close()
    s = None
    write_string('socket error') #showing on LCD
    sys.exit()

s.listen(1)
print('listening on', addr)

onCol = '#26a1ff'
offCol = '#003366'
vel_strs = (offCol, offCol, onCol, offCol, offCol)

duty_max = 0.7

# Listen for connections, serve client
while True:
    try:
        cl, addr = s.accept()
        #print('client connected from', addr)
        request = cl.recv(1024)
        #print("request:")
        #print(request)
        request = str(request)
        vel_n2_on = request.find('vel_n2=on')
        vel_n1_on = request.find('vel_n1=on')
        vel_0_on = request.find('vel_0=on')
        vel_p1_on = request.find('vel_p1=on')
        vel_p2_on = request.find('vel_p2=on')

        if vel_n2_on == 8:
            vel_strs = (onCol, offCol, offCol, offCol, offCol)
            pwm2.duty_u16(0)
            pwm1.duty_u16(int(65535*duty_max))
        if vel_n1_on == 8:
            vel_strs = (offCol, onCol, offCol, offCol, offCol)
            pwm2.duty_u16(0)
            pwm1.duty_u16(int(65535*duty_max/2))
        if vel_0_on == 8:
            vel_strs = (offCol, offCol, onCol, offCol, offCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(0)
        if vel_p1_on == 8:
            vel_strs = (offCol, offCol, offCol, onCol, offCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(int(65535*duty_max/2))
        if vel_p2_on == 8:
            vel_strs = (offCol, offCol, offCol, offCol, onCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(int(65535*duty_max))

        response = html % vel_strs 
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        print('connection closed')

    except KeyboardInterrupt as e:
        break

s.close()
s = None
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

以上のプログラムを実行し、LCD上のアドレスにブラウザでアクセスすると、下記のようなページが開きます。タッチの代わりに、ボタンでモーターの回転の向きと速度を調整する仕組みです。


5. ブラウザのボタンによるサーボモーターの角度制御

PCやスマートフォンのブラウザで、Pico W に接続されたサーボモーターの角度を制御する演習を行ってみましょう。9.7 章(付録 PDF)の内容です。

なお、Rapsberry Pi を対象とした本書では、サーボモーターの角度制御に、ブラウザのスライダを用いました。 しかし、Pico W ではスライダを用いるのは難しそうに思えましたので(専門的に言えば、JavaScript で取得したスライダの値を回路に反映する方法が思いつかない)、簡易的にボタンによる制御を行うことにしました。

必要な回路は下図の通りです。
import time
import sys
import network
import socket
from time import sleep
from machine import Pin, PWM, I2C

def servo_duty_hwpwm_web(val):
    val_min = 0
    val_max = 4
    servo_min = 0.035   # 最小デューティ比3.5%
    servo_max = 0.1  # 最大デューティ比10%
    if val < val_min:
        val = val_min
    duty = (servo_min-servo_max)*(val-val_min)/(val_max-val_min) + servo_max
    # 一般的なサーボモーターはこちらを有効に
    #duty = (servo_max-servo_min)*(val-val_min)/(val_max-val_min) + servo_min
    return duty

pwm1 = PWM(Pin(16))
pwm1.freq(50)
pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(2)))

i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

##### for LCD
def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。30から40程度を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()
##### end of LCD

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

html = """<!DOCTYPE html><html>
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<title>7 - サーボモーターの制御</title>
<style>button {
    width: auto;
    height: auto;
    background: #003366;
    font-weight: normal;
    font-size: 14pt;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 14px;
    color: #ffffff;
    padding: 10px 20px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: 1px solid #003366;
    -moz-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    -webkit-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    text-shadow:
        0px -1px 0px rgba(000,000,000,0.7),
        0px 1px 0px rgba(255,255,255,0.3);
}
</style></head>
<p>押したボタンにより、サーボモーターの位置が決まります。</p>
<div align="center">
<form><button name="pos0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="pos1" value="on" type="submit" style="background: %s;"> 1 </button>
<button name="pos2" value="on" type="submit" style="background: %s;"> 2 </button>
<button name="pos3" value="on" type="submit" style="background: %s;"> 3 </button>
<button name="pos4" value="on" type="submit" style="background: %s;"> 4 </button>
</form>
<br /><br />
</div>
</body></html>
"""

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    time.sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(addr)
except OSError as e:
    print(e)
    s.close()
    s = None
    write_string('socket error') #showing on LCD
    sys.exit()

s.listen(1)
print('listening on', addr)

onCol = '#26a1ff'
offCol = '#003366'
pos_strs = (offCol, offCol, onCol, offCol, offCol)

# Listen for connections, serve client
while True:
    try:
        cl, addr = s.accept()
        #print('client connected from', addr)
        request = cl.recv(1024)
        #print("request:")
        #print(request)
        request = str(request)
        pos0_on = request.find('pos0=on')
        pos1_on = request.find('pos1=on')
        pos2_on = request.find('pos2=on')
        pos3_on = request.find('pos3=on')
        pos4_on = request.find('pos4=on')

        if pos0_on == 8:
            pos_strs = (onCol, offCol, offCol, offCol, offCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(0)))
        if pos1_on == 8:
            pos_strs = (offCol, onCol, offCol, offCol, offCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(1)))
        if pos2_on == 8:
            pos_strs = (offCol, offCol, onCol, offCol, offCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(2)))
        if pos3_on == 8:
            pos_strs = (offCol, offCol, offCol, onCol, offCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(3)))
        if pos4_on == 8:
            pos_strs = (offCol, offCol, offCol, offCol, onCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(4)))

        response = html % pos_strs 
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        print('connection closed')

    except KeyboardInterrupt as e:
        break

s.close()
s = None
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

以上のプログラムを実行し、LCD上のアドレスにブラウザでアクセスすると、下記のようなページが開きます。スライダの代わりに、ボタンでサーボモーターの位置を調整する仕組みです。


6. ブラウザのボタンによるキャタピラ式模型の制御

PCやスマートフォンのブラウザで、Pico W に接続されたキャタピラ式模型を制御する演習を行ってみましょう。10 章の内容です。

ここまでの演習同様、「前進」、「後退」、「右旋回」、「左旋回」、「静止」という 5 つのボタンによる簡易的な制御ですのでご了承ください。

import time
import sys
import network
import socket
from time import sleep
from machine import Pin, PWM, I2C

pwm1 = PWM(Pin(16))
pwm2 = PWM(Pin(17))
pwm3 = PWM(Pin(18))
pwm4 = PWM(Pin(19))
pwm1.freq(100)
pwm2.freq(100)
pwm3.freq(100)
pwm4.freq(100)
pwm1.duty_u16(0)
pwm2.duty_u16(0)
pwm3.duty_u16(0)
pwm4.duty_u16(0)

i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

##### for LCD
def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。30から40程度を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()
##### end of LCD

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

html = """<!DOCTYPE html><html>
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<title>5 - クローラーの操作</title>
<style>button {
    width: auto;
    height: auto;
    background: #003366;
    font-weight: normal;
    font-size: 14pt;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 14px;
    color: #ffffff;
    padding: 10px 20px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: 1px solid #003366;
    -moz-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    -webkit-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    text-shadow:
        0px -1px 0px rgba(000,000,000,0.7),
        0px 1px 0px rgba(255,255,255,0.3);
}
</style></head>
<p>押したボタンにより、クローラーが移動します。中央のボタンをクリックしないとクローラーは止まりません。</p>
<div align="center">
<table border="0">
<tr align="center">
<td></td>
<td><form><button name="forward" value="on" type="submit" style="background: %s;"> ↑ </button></form></td>
<td></td>
</tr>
<tr align="center">
<td><form><button name="rot_l" value="on" type="submit" style="background: %s;"> ← </button></form></td>
<td><form><button name="stop" value="on" type="submit" style="background: %s;"> 〇 </button></form></td>
<td><form><button name="rot_r" value="on" type="submit" style="background: %s;"> → </button></form></td>
</tr>
<tr align="center">
<td></td>
<td><form><button name="backward" value="on" type="submit" style="background: %s;"> ↓ </button></form></td>
<td></td>
</tr>
</table>
<br /><br />
</div>
</body></html>
"""

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    time.sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(addr)
except OSError as e:
    print(e)
    s.close()
    s = None
    write_string('socket error') #showing on LCD
    sys.exit()

s.listen(1)
print('listening on', addr)

onCol = '#26a1ff'
offCol = '#003366'
move_strs = (offCol, offCol, onCol, offCol, offCol)

duty_max = 0.7

# Listen for connections, serve client
while True:
    try:
        cl, addr = s.accept()
        #print('client connected from', addr)
        request = cl.recv(1024)
        #print("request:")
        #print(request)
        request = str(request)
        forward_on = request.find('forward=on')
        rot_l_on = request.find('rot_l=on')
        stop_on = request.find('stop=on')
        rot_r_on = request.find('rot_r=on')
        backward_on = request.find('backward=on')

        if forward_on == 8:
            move_strs = (onCol, offCol, offCol, offCol, offCol)
            pwm2.duty_u16(0)
            pwm1.duty_u16(int(65535*duty_max))
            pwm4.duty_u16(0)
            pwm3.duty_u16(int(65535*duty_max))
        if rot_l_on == 8:
            move_strs = (offCol, onCol, offCol, offCol, offCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(int(65535*duty_max))
            pwm4.duty_u16(0)
            pwm3.duty_u16(int(65535*duty_max))
        if stop_on == 8:
            move_strs = (offCol, offCol, onCol, offCol, offCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(0)
            pwm3.duty_u16(0)
            pwm4.duty_u16(0)
        if rot_r_on == 8:
            move_strs = (offCol, offCol, offCol, onCol, offCol)
            pwm2.duty_u16(0)
            pwm1.duty_u16(int(65535*duty_max))
            pwm3.duty_u16(0)
            pwm4.duty_u16(int(65535*duty_max))
        if backward_on == 8:
            move_strs = (offCol, offCol, offCol, offCol, onCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(int(65535*duty_max))
            pwm3.duty_u16(0)
            pwm4.duty_u16(int(65535*duty_max))

        response = html % move_strs 
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        print('connection closed')

    except KeyboardInterrupt as e:
        break

s.close()
s = None
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W 上で動作しているプログラムを Thonny から一旦停止する必要があることもご注意ください。

以上のプログラムを実行し、LCD上のアドレスにブラウザでアクセスすると、下記のようなページが開きます。タッチの代わりに、ボタンでキャタピラ式模型の移動方法を変更する仕組みです。

0 件のコメント:

コメントを投稿