ESP8266のmicropythonで天気予報を読み上げる

# はじめに

本記事はESP8266を使って天気予報読み上げスピーカーが動くまでの過程を記録したものです。興味本位にmicropythonを使ったことで無用な苦労をしたので、同じ轍を踏まないように記録を公開します。

試作版
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2064472/554b045a-6b0b-8c3f-3740-43fe1c62c5f5.png" width="250"/>

最終版
１００均ライトからケース、スイッチ、電池ボックスを流用してスピーカーとユニバーサル基板を格納しました。
試作版は１００均ライトに格納不能だったのでスピーカーも基板も試作版とは別ものを格納しています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2064472/e3d87d79-be31-fc5e-cf90-114902530e83.png" width="250"/>

# 準備する

前々回の記事　[ESP8266でmicropythonを使う](https://qiita.com/gonbe_sama/items/2f96abfe237e7172ee93)　を参照ください。

# 天気予報を取得する

[urequests](https://makeblock-micropython-api.readthedocs.io/en/latest/public_library/Third-party-libraries/urequests.html#module-urequests)を使って天気予報を取得します。

yahooの天気予報(RSS)を使うつもりでしたがhttpsのため情報が取得できません。

### httpsアクセス

```
>>> import urequests
>>> r = urequests.get('https://rss-weather.yahoo.co.jp/rss/days/4410.xml')
TLS buffer overflow, record size: 6073 (+5)                                                                                                           
Traceback (most recent call last):                                                                                                                    
  File "<stdin>", line 1, in <module>                                                                                                                 
  File "aa.py", line 3, in <module>                                                                                                                   
  File "urequests.py", line 116, in get                                                                                                               
  File "urequests.py", line 62, in request                                                                                                            
OSError: (-257, 'RECORD_OVERFLOW')         
```

httpで取得できるgooの天気予報(RSS)を使います。

### httpアクセス

``` test_rss.py
import urequests
# 東京
r = urequests.get('http://weather.goo.ne.jp/area/4410.rdf')

lines = str(r.content) 
line = lines.split("\\n")

description = line[10]
description = description.replace("<description>","")
description = description.replace("</description>","")
print(description)
```

~~暗号文~~EUC-JPで「晴れ　時々　曇り」という文字列が取得できました。

```
\xc0\xb2\xa4\xec \xbb\xfe\xa1\xb9 \xc6\xde\xa4\xea 
```

encodeでECU-JPからUTF-8に変換するのですがmicropythonではうまくいきません。

```
b'\xc0\xb2\xa4\xec \xbb\xfe\xa1\xb9 \xc6\xde\xa4\xea'                                                                                                 
Traceback (most recent call last):                                                                                                                    
  File "<stdin>", line 1, in <module>                                                                                                                 
  File "rss_test.py", line 39, in test                                                                                                                
  File "rss_test.py", line 88, in get_tenki                                                                                                           
UnicodeError:                          
```

ググっても原因が分からなかったので追試してみます。
pythonとmicropythonで動作を比較して原因を突き止めます。
まずはwindows上のpythonで下記のコードを実行します。

``` test.py
b_utf = "晴れ 時々 曇り".encode("UTF-8")
print(b_utf)
b_euc = "晴れ 時々 曇り".encode("EUC-JP")
print(b_euc)
str = b_euc.decode("EUC-JP")
print(str)
```

実行結果

```
b'\xe6\x99\xb4\xe3\x82\x8c \xe6\x99\x82\xe3\x80\x85 \xe6\x9b\x87\xe3\x82\x8a'
b'\xc0\xb2\xa4\xec \xbb\xfe\xa1\xb9 \xc6\xde\xa4\xea'
晴れ 時々 曇り
```

micropython で実行します。

``` test.py
b_utf = "晴れ 時々 曇り".encode("UTF-8")
print(b_utf)
b_euc = "晴れ 時々 曇り".encode("EUC-JP")
print(b_euc)
str = b_euc.decode("EUC-JP")
print(str)
```

実行結果

```
b'\xe6\x99\xb4\xe3\x82\x8c \xe6\x99\x82\xe3\x80\x85 \xe6\x9b\x87\xe3\x82\x8a'                                                                         
b'\xe6\x99\xb4\xe3\x82\x8c \xe6\x99\x82\xe3\x80\x85 \xe6\x9b\x87\xe3\x82\x8a'                                                                         
晴れ 時々 曇り                                                                                                                                              
>>>                                                                                                                                                   
```

ECU-JPとUTF-8が同じbytesに変換されています。
micropythonはUTF-8のみ実装されていると思われます。
真面目にEUC-JPからUTF-8に変換するのは難しそうなので、単語テーブル(ECU-JP)を使うことにします。


# 4. ソフトを作る
材料はそろったので、実験結果を元に、天気予報を読み上げるプログラムを作りました。
天気予報の読み上げだけでは寂しいので時間の読み上げ機能も付加しました
１００均ライトのケースを使用し、電源スイッチが入ったら時間と天気予報を読み上げます。
単語テーブルには直近の天気（晴れ、雨、曇り）しか入れてないので、後日テーブルに不足した天気を追加します。
（後日下記ソースを修正する）

``` main.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urequests
from dfplayermini import Player
from time import sleep
from machine import Pin
import ntptime
import time

HARE = "\\xc0\\xb2\\xa4\\xec"
KUMORI = "\\xc6\\xde\\xa4\\xea"
AME = "\\xb1\\xab"
TOKIDOKI = "\\xbb\\xfe\\xa1\\xb9"
NOCHI = "\\xa4\\xce\\xa4\\xc1"
ICHIJI = "\\xb0\\xec\\xbb\\xfe"

weather_view = ["一時", "後", "時々", "雨", "曇り", "晴れ"]
weather_table = [ICHIJI, NOCHI, TOKIDOKI, AME, KUMORI, HARE]
url = "http://weather.goo.ne.jp/area/4410.rdf"


# 時間を読み上げる
def talk_time(music):
    ntptime.settime()
    t = time.gmtime()
    h = (t[3] + 9) % 24  # GMT+9
    m = t[4]
    print("只今の時間", end="")
    music.play(8)
    sleep(2)
    print(" {0}時".format(h), end="")
    music.play(h + 9)
    sleep(1)
    print(" {0}分".format(m))
    music.play(m + 33)
    sleep(2)


# 天気予報を読み上げる
def talk_tenki(music):
    global weather_table, weather_view, url
    print("明日の天気は", end="")
    music.play(1)
    sleep(1.2)
    # 東京
    r = urequests.get(url)
    lines = str(r.content)
    line = lines.split("\\n")
    description = line[10]
    description = description.replace("<description>", "")
    description = description.replace("</description>", "")
    flg = True
    while flg:
        flg = False
        for i in range(len(weather_table)):
            if description.startswith(weather_table[i]):
                description = description.replace(weather_table[i], "")
                description = description.strip()
                print(" " + weather_view[i], end="")
                music.play(2 + i)
                sleep(0.7)
                flg = True
                break
    print("です。")
    music.play(2)
    sleep(2)


# メイン処理
if __name__ == '__main__':
    music = Player(1, 2)
    # music.module_wake()
    music.volume(20)
    while True:
        talk_time(music)  # 時間を読み上げる
        talk_tenki(music)  # 天気予報を読み上げる
        sleep(10)
```

# ５. 音声ファイルを用意する

私は音声ファイル作成に[VoiceText Web API](https://cloud.voicetext.jp/webapi)を利用しています。
ただし、こちらのサービスは 「無料版で作成した音声データの商用利用、二次利用及び配布する行為は禁止されております。」とのことで完成版の動画公開はできません。商用利用や動画投稿サイトへの公開を許可している合成音声サービスもありますが、VoiceTextの自然な音声合成が非常に好みなのでなかなか他のサービスに移行できません。

ちょっと脱線しましましたが、下記のような音声ファイルを用意します。（この作業が一番面倒です）
DFPlayer miniの再生はトラックID指定なので
SDをフォーマットして空にした後、バッチファイルで音声ファイルをコピーすると意図した順序で音声ファイルを配置できます。

``` wave_copy.bat
copy 明日の天気は.wav e:\
copy です.wav e:\
copy 一時.wav e:\
copy のち.wav e:\
copy 雨.wav e:\
copy 曇り.wav e:\
copy 晴れ.wav e:\
copy 只今の時間.wav e:\
copy ０時.wav e:\
copy １時.wav e:\
copy ２時.wav e:\
copy ３時.wav e:\
copy ４時.wav e:\
copy ５時.wav e:\
copy ６時.wav e:\
copy ７時.wav e:\
copy ８時.wav e:\
copy ９時.wav e:\
copy １０時.wav e:\
copy １１時.wav e:\
copy １２時.wav e:\
copy １３時.wav e:\
copy １４時.wav e:\
copy １５時.wav e:\
copy １６時.wav e:\
copy １７時.wav e:\
copy １８時.wav e:\
copy １９時.wav e:\
copy ２０時.wav e:\
copy ２１時.wav e:\
copy ２２時.wav e:\
copy ２３時.wav e:\
copy ０分.wav e:\
copy １分.wav e:\
copy ２分.wav e:\
copy ３分.wav e:\
copy ４分.wav e:\
copy ５分.wav e:\
copy ６分.wav e:\
copy ７分.wav e:\
copy ８分.wav e:\
copy ９分.wav e:\
copy １０分.wav e:\
copy １１分.wav e:\
copy １２分.wav e:\
copy １３分.wav e:\
copy １４分.wav e:\
copy １５分.wav e:\
copy １６分.wav e:\
copy １７分.wav e:\
copy １８分.wav e:\
copy １９分.wav e:\
copy ２０分.wav e:\
copy ２１分.wav e:\
copy ２２分.wav e:\
copy ２３分.wav e:\
copy ２４分.wav e:\
copy ２５分.wav e:\
copy ２６分.wav e:\
copy ２７分.wav e:\
copy ２８分.wav e:\
copy ２９分.wav e:\
copy ３０分.wav e:\
copy ３１分.wav e:\
copy ３２分.wav e:\
copy ３３分.wav e:\
copy ３４分.wav e:\
copy ３５分.wav e:\
copy ３６分.wav e:\
copy ３７分.wav e:\
copy ３８分.wav e:\
copy ３９分.wav e:\
copy ４０分.wav e:\
copy ４１分.wav e:\
copy ４２分.wav e:\
copy ４３分.wav e:\
copy ４４分.wav e:\
copy ４５分.wav e:\
copy ４６分.wav e:\
copy ４７分.wav e:\
copy ４８分.wav e:\
copy ４９分.wav e:\
copy ５０分.wav e:\
copy ５１分.wav e:\
copy ５２分.wav e:\
copy ５３分.wav e:\
copy ５４分.wav e:\
copy ５５分.wav e:\
copy ５６分.wav e:\
copy ５７分.wav e:\
copy ５８分.wav e:\
copy ５９分.wav e:\
```

# ６． 組付ける

## 6.1. main.pyを転送します。
- WebREPLのSend a File機能を使ってmain.pyを転送します。

## 6.2. ESP8266を再起動します
- WebREPLでCtrl+D（ソフトリセット） 、もしくはESP8266の電源を入れ直して再起動します。

## 6.3. main.pyを実行します。
- ESP8266起動時に自動的にmain.pyが実行されます。電源を入れると時間と天気予報を繰り返し読み上げてくれます。

## 6.4. これで天気予報読み上げスピーカーは完成です！

実用性は微妙ですが、なんとか完成しました。

あとは、RSSの取得でサーバーに負荷をかけないよう、天気予報をキャッシュする。
httpサーバー機能を付けて外部から設定できるようにして、ディープスリープモードを使って目覚まし機能や時報、アラーム機能などを付けたりしてみたいです。

- [5.3. シンプルなHTTPサーバー](https://micropython-docs-ja.readthedocs.io/ja/latest/esp8266/tutorial/network_tcp.html?highlight=http%20get#http-get-request)
- [ディープスリープモード](https://micropython-docs-ja.readthedocs.io/ja/latest/esp8266/quickref.html?highlight=%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%97%E3%83%A2%E3%83%BC%E3%83%89#deep-sleep-mode)


今回、ＡmazonでDFPlayer miniを4つ999円で購入したので、まだ３つも余っています。
昔買ったのが押し入れから出てきて、実際には４つも余っている。（DIYのあるある・・ですよね！？？）
次は何を作ろうかなぁ？

## おまけ

ESP8266 Flash1M版は標準でunrequestがインストールされていないようです。
upipでインストールできました。
upipについてはmicropythonの公式サイトの[このあたり](https://docs.micropython.org/en/latest/reference/packages.html)に記述があります。

```
MicroPython v1.17 on 2021-09-02; ESP module (1M) with ESP8266                                                                                 
Type "help()" for more information.                                                                                                           
>>>                                                                                                                                           
>>>                                                                                                                                           
>>>                                                                                                                                           
>>> import upip                                                                                                                               
>>> upip.install('urequests')                                                                                                                 
Installing to: /lib/                                                                                                                          
Warning: micropython.org SSL certificate is not validated                                                                                     
Installing urequests 0.6 from https://micropython.org/pi/urequests/urequests-0.6.tar.gz                                                       
>>> import urequests                                                                                                                          
>>>                                                                                                                                           
```

micropythonも[minidom](https://pypi.org/project/micropython-xml.dom.minidom/)が使えます。
ただしUTF-8でないとパースできないです。
windows上のpythonだとこんな感じで簡単なのですが・・・

```rss.py
import requests
from xml.dom import minidom

url = "http://weather.goo.ne.jp/area/4410.rdf"
r = requests.get(url)
rss_euc = r.content
rss = rss_euc.decode("EUC-JP")

dom = minidom.parseString(rss)
elems = dom.getElementsByTagName('description')
print(elems[0].childNodes[0].data)
```

```
晴れ 時々 曇り
```