M5Stack でテキストをスクロール表示(MicroPython)

 M5Stack で今後色々作っていきたいと思ってるわけですが、デジタルサイネージっぽくスクロール表示させたいケースがあり、どうやったらスクロール表示できるかやってみました。簡易的な実装なのでカクカクしますが、一応スクロールするようになったので内容載せておきます。

表示位置の指定

 M5Stack で長い文章を表示する場合、普通に lcd.print() 等を使用すると、画面の端まで行くと折り返して次の行に表示されます。

f:id:akanuma-hiroaki:20181006223121j:plain

 表示開始位置はデフォルトでは左上になりますが、 lcd.print() 等のテキスト表示用メソッドの引数に座標情報を渡すと表示開始位置を指定することができます。

 また、デフォルトでは表示領域(Window)が全画面に設定されていますが、画面の一部を表示領域とすることもできますので、下記のようなメソッドで表示領域を指定し、その中での相対座標で表示位置を指定することもできます。

 今回は画面の下端1行分を表示領域として、はみ出るテキストをスクロールして表示する形にしてみます。表示領域を指定するには lcd.setwin(x, y, x1, y1) メソッドを使って、左上の座標(x, y)と右下の座標(x1, y1)の矩形で指定します。

 まずフォントサイズとスクリーンサイズを取得し、画面左下からフォントの高さ分を引いたところを左上の座標とし、画面右下を表示領域の右下の座標とします。また、座標は0スタートなので、サイズの値からそれぞれ -1 しています。

from m5stack import lcd

lcd.clear()
lcd.setCursor(0, 0)
lcd.setColor(lcd.WHITE)
lcd.font(lcd.FONT_DejaVu24)

text = "Hello World! This is a sample code of text scrolling."

fw, fh = lcd.fontSize()
sw, sh = lcd.screensize()

lcd.setwin(0, (sh - fh) - 1, sw - 1, sh - 1)

lcd.print(text, 0, 0)

 これを実行すると下記のように画面下端に1行分テキストが表示されます。

f:id:akanuma-hiroaki:20181006223150j:plain

スクロール表示

 今回はカクついてても最低限のスクロール表示ができればOKという考え方で、シンプルな実装にしています。方針としては、テキストの長さが表示領域の幅よりも大きい時は、テキストの先頭1文字を削除して再度表示するだけの単純なものです。先ほどのコードに time モジュールの import と、 time.sleep_ms(3000) 以降のループ処理を追加しています。

from m5stack import lcd

import time

lcd.clear()
lcd.setCursor(0, 0)
lcd.setColor(lcd.WHITE)
lcd.font(lcd.FONT_DejaVu24)

text = "Hello World! This is a sample code of text scrolling."

fw, fh = lcd.fontSize()
sw, sh = lcd.screensize()

lcd.setwin(0, (sh - fh) - 1, sw - 1, sh - 1)
lcd.print(text, 0, 0)

time.sleep_ms(3000)
while lcd.textWidth(text) > 0:
    text = text[1:]
    lcd.clearwin()
    lcd.print(text, 0, 0)
    time.sleep_ms(200)

 これを実行すると下記のような表示になります。

天気情報の表示サンプル

 ではもう少しデジタルサイネージっぽく、以前の記事で使った天気情報を、1行でスクロール表示するようにしてみたいと思います。

blog.akanumahiroaki.com

 天気情報APIからのデータ取得については上記記事をご参照いただくとして、今回は下記のような実装にしています。

from m5stack import lcd

import time
import ujson
import urequests

class Weather:
    def __init__(self):
        self.base_url    = 'http://api.openweathermap.org/data/2.5/weather?q={},jp&appid={}'
        self.api_key     = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
        self.prefectures = ['Tokyo', 'Saitama', 'Nagoya']

    def get_weather(self, prefecture):
        response = urequests.get(self.base_url.format(prefecture, self.api_key))
        json = response.json()
        main = json['main']
        self.temp     = main['temp']
        self.pressure = main['pressure']
        self.humidity = main['humidity']
        self.temp_min = main['temp_min']
        self.temp_max = main['temp_max']

    def text(self, prefecture):
        return "[{}] temp: {} pressure: {} humidity: {} temp_min: {} temp_max: {}".format(
            prefecture, self.temp, self.pressure, self.humidity, self.temp_min, self.temp_max
        )

def print_scroll(text):
    lcd.clearwin()
    lcd.print(text, 0, 0)
    time.sleep_ms(3000)
    while lcd.textWidth(text) > 0:
        text = text[1:]
        lcd.clearwin()
        lcd.print(text, 0, 0)
        time.sleep_ms(200)

lcd.setCursor(0, 0)
lcd.setColor(lcd.WHITE)
lcd.font(lcd.FONT_DejaVu24)
lcd.clear()

fw, fh = lcd.fontSize()
ww, wh = lcd.winsize()

weather = Weather()
while True:
    for prefecture in weather.prefectures:
        weather.get_weather(prefecture)
        text = weather.text(prefecture)
        lcd.setwin(0, (wh - fh) - 1, ww - 1, wh - 1)
        print_scroll(text)
        time.sleep_ms(2000)

 Weather クラスの内容はデータ取得処理等はほとんど変わっていませんが、画面表示用の処理は外に切り出し、データを1行にまとめたテキストを返すようにしています。そしてそのテキストを先ほどのスクロール処理で表示しています。これを実行すると下記のように天気情報がスクロールで表示されます。

まとめ

 とりあえずのスクロール処理は思ったより簡単に実装できましたが、もっと滑らかにスクロールしようと思うと単純なテキスト処理だけだとできなそうな気がするので、結構ハードルが高そうではあります。また、今回はテキスト表示の処理しかしてませんが、他の内容を画面上部に表示しつつ下部にスクロール表示となると、スレッド処理的なものが必要そうになるので難しそうですが、今後トライしてみようと思います。