reTerminalでmicropython + LVGL　のGUIアプリを作ろう

#はじめに
Raspberry Pi Compute Module 4が入っているSeeed reTerminalにはGUIアプリケーションを作るのに
・[Qt forPython](https://wiki.seeedstudio.com/reTerminal-build-UI-using-Qt-for-Python/)
・[Flutter](https://wiki.seeedstudio.com/reTerminal-build-UI-using-Flutter/)
・[Electron](https://wiki.seeedstudio.com/reTerminal-build-UI-using-Electron/)
・[LVGL](https://wiki.seeedstudio.com/reTerminal-build-UI-using-LVGL/)
がSeeed公式wikiで紹介されています
その中で私はLVGLが[サンプル](https://docs.lvgl.io/master/examples.html#)や[デモ](https://lvgl.academy/lvgl/Introduction/lvgl-demo)が豊富で良さそうなので使って見たいと思いましたがC/C++が理解できない・・・
そこでpython(Cpython)に文法的によく似たmicropythonでLVGLが使えるというのでやってみました


# インストール
まず必要なパッケージをインストールします

```
sudo apt update
sudo apt-get install build-essential libreadline-dev libffi-dev git pkg-config libsdl2-2.0-0 libsdl2-dev  parallel git
```

lv_micropythonリポジトリをクローンします。

```
git clone --recurse-submodules https://github.com/lvgl/lv_micropython.git
```

- **追記2024.7**
lv_micropythonは記事を書いた頃より大幅にバージョンアップしていますので動かない場合以下を試してみてください

```
git checkout d8a68915baa797d02b24f12c2112ff02ac201681
git submodule update --init --recursive
```



デフォルトフォントを大きくします

```/home/pi/lv_micropython/lib/lv_bindings/lv_conf.h
331 #define LV_FONT_MONTSERRAT_48 1
348 /*Always set a default font*/
349 #define LV_FONT_DEFAULT &lv_font_montserrat_48
```
画面サイズにあわせます

```/home/pi/lv_micropython/lib/lv_bindings/driver/SDL/lv_drv_conf.h

86  #define MONITOR_HOR_RES     1280
87  #define MONITOR_VER_RES     720

```
フルスクリーン表示にします

```/home/pi/lv_micropython/lib/lv_bindings/driver/SDL/SDL_monitor.c
129 window = SDL_CreateWindow("TFT Simulator",
130                              SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
131                              MONITOR_HOR_RES * MONITOR_ZOOM, MONITOR_VER_RES * MONITOR_ZOOM,SDL_WINDOW_FULLSCREEN);       /*last param. SDL_WINDOW_BORDERLESS to hide borders*/
```

そのままだとエラーが出るのでLong型でキャストします

```/home/pi/lv_micropython/lib/lv_bindings/lvgl/src/extra/libs/qrcode/qrcodegen.c
856　if (result > (long)INT16_MAX)
```

```
cd ~/lv_micropython
make -C mpy-cross -j4
make -C ports/unix/ -j4
```
正常に終わると実行ファイルが出来上がりmicropythonのREPL対話モードが立ち上がります

```
./ports/unix/micropython
```

/usr/local/bin/　辺りにリンクを作っておけば```micropython```のコマンドで動きます

```
sudo ln -s /home/pi/lv_micropython/ports/unix/micropython /usr/local/bin/micropython
```

```
pi@raspberrypi:~ $ micropython
MicroPython v1.17-587-g009190923-dirty on 2021-11-29; linux version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> help("modules")
SDL               gc                ucryptolib        ure
__main__          imagetools        uctypes           uselect
_thread           lodepng           uerrno            usocket
btree             lv_timer          uhashlib          ussl
builtins          lv_utils          uheapq            ustruct
cmath             lvgl              uio               usys
display_driver    math              ujson             utime
display_driver_utils                micropython       umachine          utimeq
evdev             termios           uos               uwebsocket
fb                uarray            upip              uzlib
ffi               ubinascii         upip_utarfile
fs_driver         ucollections      urandom
Plus any modules on the filesystem
>>>
```
# サンプル

最低限必要なコード

```py
import lvgl as lv
import SDL
# Register SDL display driver.
SDL.init()

# LVGL init
lv.init()
draw_buf = lv.disp_draw_buf_t()
buf1_1 = bytearray(1280*10)
draw_buf.init(buf1_1, None, len(buf1_1)//4)
disp_drv = lv.disp_drv_t()
disp_drv.init()
disp_drv.draw_buf = draw_buf
disp_drv.flush_cb = SDL.monitor_flush
disp_drv.hor_res = 1280
disp_drv.ver_res = 720
disp_drv.register()

# Regsiter SDL mouse driver

indev_drv = lv.indev_drv_t()
indev_drv.init()
indev_drv.type = lv.INDEV_TYPE.POINTER
indev_drv.read_cb = SDL.mouse_read
indev_drv.register()



```
これに```/home/pi/lv_micropython/lib/lv_bindings/lvgl/examples```　フォルダに有るサンプルを参考にデモを作ってみました。

```lv_exsample_meter_1_test.py
#!/usr/local/bin/micropython
import utime as time
import lvgl as lv
import SDL
# Register SDL display driver.
SDL.init()
# LVGL init
lv.init()
draw_buf = lv.disp_draw_buf_t()
buf1_1 = bytearray(1280*10)
draw_buf.init(buf1_1, None, len(buf1_1)//4)
disp_drv = lv.disp_drv_t()
disp_drv.init()
disp_drv.draw_buf = draw_buf
disp_drv.flush_cb = SDL.monitor_flush
disp_drv.hor_res = 1280
disp_drv.ver_res = 720
disp_drv.register()

#
# A simple meter
#
meter = lv.meter(lv.scr_act())
meter.center()
meter.set_size(600, 600)

# Add a scale first
scale = meter.add_scale()
meter.set_scale_ticks(scale, 51, 2, 10, lv.palette_main(lv.PALETTE.GREY))
meter.set_scale_major_ticks(scale, 10, 4, 15, lv.color_black(), 10)

indic = lv.meter_indicator_t()

# Add a blue arc to the start
indic = meter.add_arc(scale, 3, lv.palette_main(lv.PALETTE.BLUE), 0)
meter.set_indicator_start_value(indic, 0)
meter.set_indicator_end_value(indic, 20)

# Make the tick lines blue at the start of the scale
indic = meter.add_scale_lines(scale, lv.palette_main(lv.PALETTE.BLUE), lv.palette_main(lv.PALETTE.BLUE), False, 0)
meter.set_indicator_start_value(indic, 0)
meter.set_indicator_end_value(indic, 20)

# Add a red arc to the end
indic = meter.add_arc(scale, 3, lv.palette_main(lv.PALETTE.RED), 0)
meter.set_indicator_start_value(indic, 80)
meter.set_indicator_end_value(indic, 100)

# Make the tick lines red at the end of the scale
indic = meter.add_scale_lines(scale, lv.palette_main(lv.PALETTE.RED), lv.palette_main(lv.PALETTE.RED), False, 0)
meter.set_indicator_start_value(indic, 80)
meter.set_indicator_end_value(indic, 100)

# Add a needle line indicator
indic = meter.add_needle_line(scale, 4, lv.palette_main(lv.PALETTE.GREY), -10)

val = 0
while True:
    meter.set_indicator_value(indic, int(val%100))
    val += 1
    time.sleep(0.05)

```

```
chmod +x  ./lv_exsample_meter_1_test.py
DISPLAY=:0 ./lv_exsample_meter_1_test.py
```
<iframe width="560" height="315" src="https://www.youtube.com/embed/M9ljN6eTlZg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>



# モジュールの追加
足りないモジュールは随時 MicroPython のパッケージマネージャー**upip**でインストールします

```
micropython
>>> import upip
>>> upip.install("machine")
>>> upip.install("glob")
>>> upip.install("requests")
>>> upip.install("socket")
```

serialモジュールはupipでは見つからなかったので別に入れます

```
git clone https://github.com/iRobotEducation/micropython-serial.git
cd micropython-serial/
./install.sh 
```

# 入出力

### GPIO 

```
from machine import Pin
led=Pin(7,Pin.OUT)
led.value(1)
```
### serial(USB/UART通信）

```
import serial
ser=serial.Serial("/dev/ttyACM0", 9600, timeout=1)
ser.open()
s_read = ser.read() #byte型
ser.close()

```

### request（インターネット）
requestモジュールを使ってAmbientに送受信するライブラリはmicropython互換なのでそのまま使用できます

https://github.com/AmbientDataInc/ambient-python-lib
からambient.pyファイルを　/home/pi/.micropython/lib　などインポートできる場所に置きます

```
import request
import ambient
am = ambient.Ambient(チャネルID,"リードキー","ライトキー")
a_read = am.read(n=1)
```

### socket（socket通信）

```
import socket
addr = socket.getaddrinfo('0.0.0.0', 50001)[0][-1] #socker Server 
s = socket.socket()
s.bind(addr)
s.listen(1)
cl, addr = s.accept()
c_read = cl.recv()
cl.close()
```
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">arduino -&gt;(USB)-&gt;reTerminal -&gt;(gpio)-&gt;LED<br>センサーからデータを入力して表示＆処理して外部出力。こういう使い方によさそう<a href="https://twitter.com/hashtag/reTerminal?src=hash&amp;ref_src=twsrc%5Etfw">#reTerminal</a> <a href="https://twitter.com/hashtag/lvgl?src=hash&amp;ref_src=twsrc%5Etfw">#lvgl</a> <a href="https://twitter.com/hashtag/micropython?src=hash&amp;ref_src=twsrc%5Etfw">#micropython</a> <a href="https://t.co/cPDoYuFZKn">pic.twitter.com/cPDoYuFZKn</a></p>&mdash; mnl_t (@mnlt18) <a href="https://twitter.com/mnlt18/status/1462631983918444550?ref_src=twsrc%5Etfw">November 22, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

** 参考 **
各送受信はbyte単位なのでint型やstr型は```struct.pack()``` ```struct.unpack()```　で変換します
https://micropython-docs-ja.readthedocs.io/ja/latest/library/struct.html
https://docs.python.org/ja/3/library/struct.html


```int.from_bytes()``` ``` int.to_bytes()```  ```bytes()``` などを使うときは引数の渡し方に注意

int.from_bytes(b'\x00\x10', byteorder='little', signed=True)  -> error
int.from_bytes(b'\x00\x10', 'little')　　　　　　　　　　　 -> ok  signed引数はない

bytes('abc', encoding='utf8')　-> error
bytes('abc', 'utf-8')　　　　　-> ok 


https://micropython-docs-ja.readthedocs.io/ja/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented



# できてないこと
### タッチスクリーン対応
マウス入力には反応していますがなぜかタッチスクリーンには反応しません。どこか設定で動くのか知っている方がいたら教えて下さい。
・試してみたこと　->  動きません

```/home/pi/lv_micropython/lib/lv_bindings/driver/SDL/lv_drv_conf.h
202 /*-------------------------------------------------
203  * Mouse or touchpad as evdev interface (for Linux based systems)
204  *------------------------------------------------*/
205 #define USE_EVDEV    1
206 #if USE_EVDEV
207 #define EVDEV_NAME   "/dev/input/event1"    
```

### reTerminalハードウェア対応
ユーザーボタンやLED、各センサーについてpython（Cpython)で扱うサンプルは[公式](https://wiki.seeedstudio.com/reTerminal-hardware-interfaces-usage/#python-library-for-reterminal) にありますが、そのままではmicropythonでは動きません

やや強引ですがCpythonで取得した値をsocket通信でmicropythonに送り表示することはできました


<iframe width="560" height="315" src="https://www.youtube.com/embed/5HuRdHtLt08" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

<iframe width="560" height="315" src="https://www.youtube.com/embed/YagtOmNrbaU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

### C/C++への移植
micropython版はオリジナルC版から作られていて関数名もそれを踏襲していているので
プロトタイプとしてmicropythonで作ってそれをC/C++版に移植するのは**簡単に**できるそうです。（但し、個人の能力による）
また計算能力の高いラズパイのようなプロセッサで試作を作って、ESP32やラズパイPICOのようなマイコンで量産という流れがおすすめとSeeedの中の人も話していました。そういった使い方にも移植性の高いLVGLは向いていそう。

# リンク
### LVGL（Light and Versatile Graphics Library）HOME

https://docs.lvgl.io/master/index.html

### Micropython + LittlevGL　ブログ

https://blog.lvgl.io/2019-02-20/micropython-bindings

### Github

** LVGL **　:　GUIライブラリ

https://github.com/lvgl/lvgl

** lv_micropython **　:　Micropython + LVGL

https://github.com/lvgl/lv_micropython

** lv_binding_micropython **　:　lv_micropythonのサブモジュール

https://github.com/lvgl/lv_binding_micropython

# ユーザーボタン入力（2021/12/4追記）
こちらの記事を参考にしたところmicropythonでもユーザーボタン・タッチパネル・加速度センサー値を取得できました。

https://qiita.com/koara-local/items/6484723d29afad4c3afb

```key_input.py
#!/usr/local/bin/micropython

import struct

EVENT_FORMAT = "llHHI"; # long, long, unsigned short, unsigned short, unsigned int
EVENT_SIZE = struct.calcsize(EVENT_FORMAT)
def get_userkeys():
    infile_path = "/dev/input/by-path/platform-gpio_keys-event"
    with open(infile_path, "rb") as file:
        event = file.read(EVENT_SIZE)
        while event:
            (tv_sec, tv_usec, type, code, value) = struct.unpack(EVENT_FORMAT, event)
            #print(struct.unpack(EVENT_FORMAT, event))
            if code != 0:
                #print(f"code={code} value={value}")
                if value != 0:
                    return code
            event = file.read(EVENT_SIZE)
```
<iframe width="560" height="315" src="https://www.youtube.com/embed/ieLluw2EW0g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# 日本語フォントの表示方法　（2021/12/30追記)
micropythonでのサンプルはこちらにあります
https://github.com/lvgl/lv_binding_micropython/blob/620a3de2408d2ccb283c2e710c77c6cf006b42e3/examples/Dynamic_loading_font_example.py

FS driver init.　辺りをSDL display driverに適宜合わせて動かしてみると表示はされますが、
フォントサイズが小さく、ひらがなのみしか表記できない。そこで

公式ドキュメント
https://docs.lvgl.io/latest/en/html/overview/font.html#add-new-font
を見ながら新しくフォントを加えます

## まずお好きなフォントを選びます
Windowsに入っているお好きなフォントをC:\Windows\Fontsからコピーする

(例：dfgoteb.ttc　DF特太ゴシック体　標準）
ファイル形式を見ると.ttcだったのでこちらの記事を参考に.tffに変換する
TTCフォントをTTFに分解する方法。
https://qiita.com/maboy/items/9f64d406c830aab548b1
```windows PS
（解凍フォルダ）\unitettc　>unitettc64 dfgoteb.ttc
```
dfgoteb001.ttf dfgoteb002.ttf
が出来上がる

## 次にtffからbinファイルを作る
Converts TTF/WOFF fonts to compact bitmap format（tff-> bin オフラインコンバーター）
https://github.com/lvgl/lv_font_conv
node.jsを使うのでここから**ラズパイ（reTerminal）**上で行う
node.js（v10～） と　npmが入っていないときはインストールしておく
（たまたま前に入れてたのでつまずきはなかったが　ラズパイ　Node.js インストールでググる）

```
~$ mkdir myfont && cd myfont 
~/myfont$ npm init --yes
~/myfont$ sudo npm i lv_font_conv -g
~/myfont$ sudo npm i lvgl/lv_font_conv -g
```
このフォルダに先程のtffファイルを移動しておく


コマンドの詳しい説明はREADME.mdにかいてあります
https://github.com/lvgl/lv_font_conv/blob/master/README.md

range オプションについて
https://jrgraphix.net/research/unicode.php

**0x21-0x7f　　ASCII
0x3040-0x09F  	ひらがな
0x30A0-0x30FF  	カタカナ
0x2190-0x21FF  	矢印
0x3300-0x33FF  	CJK互換文字
0xFF00-0xFFE　　半角カナ**
とりあえず使いそうなこのあたり
漢字については CJK統合漢字（0x4E00-0x9FFF) 全部を入れるとメモリが足りないので使う文字だけ一つずつ追加しました。
（どこかの設定でメモリを増やせば全部入る？）

```
 lv_font_conv --size 48 --format bin --bpp 1 --font "/home/pi/myfont/dfgoteb001.ttf" --range 0x21-0x7f,0x3042-0x30FF,0x2190-0x21ff,0x3300-0x33ff,0xFF00-0xFFEF,0x4E16,0x754C --no-compress -o font-jp-48.bin
```

## 作ったフォントを使う
あとは最初のサンプルを見ながら適宜置き換えていきます

```unicodefont.py
#!/usr/local/bin/micropython

import usys as sys
sys.path.append('') # See: https://github.com/micropython/micropython/issues/6419

import lvgl as lv
import SDL,time
SDL.init()
lv.init()

# display driver init...

import fs_driver
fs_drv = lv.fs_drv_t()
fs_driver.fs_register(fs_drv, 'S')

# Register SDL display driver.

draw_buf = lv.disp_draw_buf_t()
buf1_1 = bytearray(1280*10)
draw_buf.init(buf1_1, None, len(buf1_1)//4)
disp_drv = lv.disp_drv_t()
disp_drv.init()
disp_drv.draw_buf = draw_buf
disp_drv.flush_cb = SDL.monitor_flush
disp_drv.hor_res = 1280
disp_drv.ver_res = 720
disp_drv.register()

#font
myfont_jp = lv.font_load("S:/home/pi/myfont/font-jp-48.bin")
scr = lv.scr_act()
scr.clean()
label1 = lv.label(scr)
label1.set_style_text_font(myfont_jp, 0)  # set the font
label1.set_text("Hello! ハロー こんにちは↑ ｺﾝﾆﾁﾊ世界㎡㌶")  
label1.align(lv.ALIGN.CENTER, 0, 0)
while 1:
    time.sleep(1)
```
![IMG_20211230_193533.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261581/2de8fb40-12f4-50b3-d689-5d04beb33c81.jpeg)