Raspberry Pi + SSD1306 128*32 OLEDで遊ぶ

#はじめに
2回目の投稿です。引き続きRaspberry Piで遊びます。
今回はひねくれず、Raspberry Pi OSでRaspberry Pi動かします。

##背景
Raspberry Piって、ディスプレイ繋げないで運用する時、DHCP利用だとIPわからなくていろいろ困ることあるよね
できる限り安いディスプレイを使ってIPだけでも表示できるとありがたいよね…
というところがスタートです。

##用意したもの　＋　出来上がるもの
・Raspberry Pi 4B 2GBモデル＋microSD＋電源
・Aliexpress.jpで送料込み200円以下の0.91インチ、128×32のOLED 液晶
・適当なジャンパケーブルとブレッドボード

↓こんな感じなものが最終形です。
![raspi-oled.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513230/d12d8e77-e0e5-48b6-2ea5-572240f35983.jpeg)




##ぱぱっと調べて分かったこと
OLEDのコントローラはSSD1306というものを使っている。
AdafruitsのSSD1306用Pythonライブラリを使うと画像(ppm形式)やテキストが簡単に表示できるらしい。
Python使ってRaspberry PiのIP取得する方法は過去にどっかで調べた。普通にやろうとするとループバックIPが戻ってきて泣いた記憶。
　→　参考サイト最下段

##やったこと
とりあえずaptのアップデートとかはしてある前提で
・`sudo raspi-config`から`Interfacing Options`の`I2C`を`Yes`する
・`sudo apt-get install python3-pip i2c-tools`
・`sudo pip3 install adafruit-circuitpython-ssd1306`
・表示用に`sudo apt-get install python3-pil`
・OLEDとRaspberry Piの接続（I2C)
・Raspberry PiのI2C通信の速度？周波数？が初期設定値だと100000になっており遅いので
　（後に判明し変えた）`/boot/config.txt`に`dtparam=i2c_arm=on,i2c_arm_baudrate=1000000`を追記。


|Raspberry Pi PIN(BCM)|OLED|
| :-- | :-: | --: |
|1 (3.3V)| VCC |
|3 (GPIO2)|SDA |
|5 (GPIO3)|SCL|
|6 (GND) |GND|　

・`sudo i2cdetect -y 1`を叩くと、つながっているデバイスのIC2アドレスが分かる。今回は`3C`でした

##書いたコード

```python:blank.py
#!/usr/bin python3
# -*- coding: utf-8 -*-
import time
from board import SCL, SDA
import busio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306

# Create I2C interface.
i2c = busio.I2C(SCL, SDA)
# Create the SSD1306 OLED class. (x size, y size, 'i2c')
disp = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)


while True:
    # Clear display.
    disp.fill(0)
    disp.show()
    time.sleep (1)
    
    disp.fill(1)
    disp.show()
    time.sleep (1)
```

これで実行すれば、1秒画面全体点灯、1秒画面全体消灯のいわゆるLチカになる。

また、適当な画像をGIMP等便利なアプリで`.ppm`形式に変換して、ここでは`testlogo.ppm`と名付けたとすると

```python:show_img.py
#!/usr/bin python3
# -*- coding: utf-8 -*-
from board import SCL, SDA
import busio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306

# 128x32 display with hardware I2C:
WIDTH = 128
HEIGHT = 32
i2c = busio.I2C(SCL, SDA)

oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)

# Clear display.
oled.fill(0)
oled.show()

# Load image based on OLED display height.  Note that image is converted to 1 bit color.
image = Image.open('testlogo.ppm').resize((WIDTH, HEIGHT), Image.ANTIALIAS).convert('1')
# Display image.
oled.image(image)
oled.show()
```

でWIDTHとHEIGHTをしっかりと設定しておけば勝手にサイズ変換して表示してくれる。モノクロ逆になる。

次は文字を表示する。これができればIPも表示できるだろう。押しボタンを片側5Vに、片側7番ピン（GPIO4）に接続し

```python:wait4button.py
#!/usr/bin python3
# -*- coding: utf-8 -*-
# before use, do
# sudo apt-get -y install python3-dev
# sudo apt-get -y install python3-pip

import time
from board import SCL, SDA
import busio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306
import RPi.GPIO as GPIO

# generate random integer values
from random import randint


# Create I2C interface.
i2c = busio.I2C(SCL, SDA)
# Create the SSD1306 OLED class. (x size, y size, i2c)
disp = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)

PIN_IN1 = 4
fontsize = 36
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_IN1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

########################generate random n digit number#######################
def random_with_N_digits(n):
    range_start = 10**(n-1)
    range_end = (10**n)-1
    return randint(range_start, range_end)

def main():

    try:
        while True:
            time.sleep(0.05)     # to avoid false positive at the first time python runs
            flag1 = GPIO.input(PIN_IN1) == GPIO.HIGH
        
            if flag1 == True:
                ########################when the button is pressed...#######################
                value = str(random_with_N_digits(8))
                image = Image.new("1", (128, 32))
                draw = ImageDraw.Draw(image)
                # Load default font.
                font = ImageFont.truetype("neon_pixel.ttf",fontsize)
                draw.text((2,5), value, font=font, fill=255)
                # Display image.
                disp.image(image)
                disp.show()
            
            else:
            ########################when the button is NOT pressed#######################
                disp.fill(0)
                disp.show()
    except KeyboardInterrupt:
        pass

    finally:
        GPIO.cleanup()

if __name__ == '__main__':
    main()
```
これでボタンを押すとランダムな数字が8桁表示されて世界線を移動できそうな感じが楽しめる。
（ニキシー管っぽいフォントを探したが、monospaceなフォントが無いのとあっても有料そうだったので適当な無料フォントをダウンロードして突っ込んでます。どんなフォントでも`.ttf`ならなんとかなるっぽい。すごい。フォントサイズも指定できていい感じの大きさにできる。

最後にらずぱい自身のIPを表示させるプログラムを書いてみる。

```python:showip.py
#!/usr/bin python3
# -*- coding: utf-8 -*-
# before use, do
# sudo apt-get -y install python3-dev
# sudo apt-get -y install python3-pip

import time
from board import SCL, SDA
import busio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306

import RPi.GPIO as GPIO

# generate random integer values
from random import randint


# Create I2C interface.
i2c = busio.I2C(SCL, SDA)
# Create the SSD1306 OLED class. (x size, y size, i2c)
disp = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)

PIN_IN1 = 4
fontsize = 36
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_IN1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

########################generate random n digit number#######################
def random_with_N_digits(n):
    range_start = 10**(n-1)
    range_end = (10**n)-1
    return randint(range_start, range_end)

def main():

    try:
        while True:
            time.sleep(0.05)     # to avoid false positive at the first time python runs
            flag1 = GPIO.input(PIN_IN1) == GPIO.HIGH
        
            if flag1 == True:
                ########################when the button is pressed...#######################
                value = str(random_with_N_digits(8))
                image = Image.new("1", (128, 32))
                draw = ImageDraw.Draw(image)
                # Load default font.
                font = ImageFont.truetype("neon_pixel.ttf",fontsize)
                draw.text((2,5), value, font=font, fill=255)
                # Display image.
                disp.image(image)
                disp.show()
            
            else:
            ########################when the button is NOT pressed#######################
                disp.fill(0)
                disp.show()
    except KeyboardInterrupt:
        pass

    finally:
        GPIO.cleanup()

if __name__ == '__main__':
    main()
```

ボタンはそのうち押したらIPとなにか別の表示を切り替えできるようにする…予定。
と思ってsystemdで管理しているサービスが生きているかどうかをOLEDに表示できるようにこんなものも作ってみた
（自分でサービス作っといて生きてるか死んでるかの判別はできるようになる。需要は無いだろうけど…）

```python:sysctlchk.py
#!/usr/bin python3
# -*- coding: utf-8 -*-

#sysctlchk.pyの引数にSystemctlのステータスを表示したいものを入れると返してくれる。
#例えば sysctlchk.py systemd-timesyncdでNTPサービスが生きてるか死んでるかがわかる

import subprocess
from subprocess import PIPE
import time
import sys
from board import SCL, SDA
import busio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306
import RPi.GPIO as GPIO
args = sys.argv

# Create I2C interface.
i2c = busio.I2C(SCL, SDA)
# Create the SSD1306 OLED class. (x size, y size, i2c)
oled = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)

WIDTH = 128
HEIGHT = 32
#HEIGHT = 64
BORDER = 1

# Clear display.
oled.fill(0)
oled.show()

# Load default font.
fontsize = 30
font = ImageFont.truetype("neon_pixel.ttf",fontsize)
# Create blank image for drawing.
image = Image.new("1", (oled.width, oled.height))
draw = ImageDraw.Draw(image)
# Draw a white frame
def frame():
    draw.rectangle((0, 0, oled.width, oled.height), outline=255, fill=255)
    draw.rectangle((BORDER, BORDER, oled.width - BORDER - 1, oled.height - BORDER - 1), outline=0, fill=0,)

def main():
    try:
        #################### main ####################
        while True:
            proc = subprocess.run("sudo systemctl status " + args[1], shell=True, stdout=PIPE, stderr=PIPE, text=True)
            status = proc.stdout
            #print(status)
            k = status.split().index("Active:") + 1
            l = status.split()[k]
            #print(l)
            #print("Status of " + args[1] + ": "+ l)
            #print('STDOUT: {}'.format(status))
            
            frame()
            draw.text((3,3), l, font=font, fill=255)
            oled.image(image)
            oled.show()
            time.sleep(0.9)
            
            oled.fill(0)
            frame()
            oled.image(image)
            oled.show()
            time.sleep(0.1)
        
        
    except KeyboardInterrupt:
        pass
    finally:
        pass
if __name__ == '__main__':
    main()
```

ここらへんまでやって力尽きました。
後から備忘な感じでまとめているので簡単風に書いていますがド初心者の僕はあちこちでつまづきまくってます。
改善点あったらどんどん教えて下さい。


##参考にさせていただいたWebサイト
>https://learn.adafruit.com/adafruit-pioled-128x32-mini-oled-for-raspberry-pi/usage
↑ここが一番参考になった。（importとか初期設定が美しい気がする）
>https://www.raspberrypi-spy.co.uk/2018/04/i2c-oled-display-module-with-raspberry-pi/
>http://ytkyk.info/blog/2016/06/19/raspberry-pi%E3%81%A7128x64%E3%81%AEoled%E3%81%AB%E6%97%A5%E6%9C%AC%E8%AA%9E%E3%82%92%E8%A1%A8%E7%A4%BA%E7%BE%8E%E5%92%B2%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88/
>https://stackoverflow.com/questions/19332554/importerror-no-module-named-netifaces