ラズパイでバスの接近情報を電光表示板に表示する

![IMG_3090 (3).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/2a176cc5-f7ac-c16f-61a6-938d1f17d95b.jpeg)

Raspberry Piでバス会社のリアルタイムの運行情報をスクレイピングして、運行状況を電光表示板に表示します。朝の忙しいときに、ささっと遅延状況を確認できるので便利です。

#使用機材
- Raspberry Pi 2 Model B
- OLEDモジュール: SO1602AWWB-UC-WB （16ｘ2行 白色) 

表示板は明るくてコントラスが高い有機EL表示器を使用しました。離れたところからもくっきりと見やすいです。[秋月電子](http://akizukidenshi.com/catalog/g/gP-08277/)で購入しました。


#配線図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/d8c88324-61d6-bfe6-bdfe-eb53cfe33185.png)


|Raspberry Pi|SO1602             |
|------------|-------------------|
|1(3.3V)     |2(VDD), 4(SA0)     |
|3(SDA)      |8(SDAin), 9(SDAout)|
|5(SCL)      |7(SLC)             |
|6(GND)      |1(VSS), 3(/CS)     |

#OLEDモジュールのPythonライブラリー
OLEDモジュール(SO1602)用のPython用のライブラリーは[こちら](https://github.com/YoutechA320U/RaspberryPi_Python_so1602_lib/blob/master/so1602.py)を使わせていただきました。ただし、たまにwrite時にI/O Errorが発生することがあり、write前後のsleepの値をいろいろ変更しても回避できなかっため、try-exceptでリトライするようにしました。もしかすると、作成中の環境では電源容量が足りていなかったせいかもしれません。現在は安定しています。

```python:so1602.py
# -*- coding: utf-8 -*-
import unicodedata
import smbus
import time
import sys

i2c = smbus.SMBus(1)

CHAR_TABLE = {
    u'†': [0x11],
    u'§': [0x12],
    u'¶': [0x13],
    u'Γ': [0x14],
    u'Δ': [0x15],
    u'θ': [0x16],
    u'Λ': [0x17],
    u'Ξ': [0x18],
    u'Π': [0x19],
    u'Σ': [0x1a],
    u'Φ': [0x1c],
    u'Ψ': [0x1d],
    u'Ω': [0x1e],
    u'α': [0x1f],

    u' ': [0x20],
    u'!': [0x21],
    u'"': [0x22],
    u'#': [0x23],
    u'$': [0x24],
    u'%': [0x25],
    u'&': [0x26],
    u"'": [0x27],
    u'(': [0x28],
    u')': [0x29],
    u'*': [0x2a],
    u'+': [0x2b],
    u',': [0x2c],
    u'-': [0x2d],
    u'.': [0x2e],
    u'/': [0x2f],

    u'0': [0x30],
    u'1': [0x31],
    u'2': [0x32],
    u'3': [0x33],
    u'4': [0x34],
    u'5': [0x35],
    u'6': [0x36],
    u'7': [0x37],
    u'8': [0x38],
    u'9': [0x39],
    u':': [0x3a],
    u';': [0x3b],
    u'<': [0x3c],
    u'=': [0x3d],
    u'>': [0x3e],
    u'?': [0x3f],

    u'@': [0x40],
    u'A': [0x41],
    u'B': [0x42],
    u'C': [0x43],
    u'D': [0x44],
    u'E': [0x45],
    u'F': [0x46],
    u'G': [0x47],
    u'H': [0x48],
    u'I': [0x49],
    u'J': [0x4a],
    u'K': [0x4b],
    u'L': [0x4c],
    u'M': [0x4d],
    u'N': [0x4e],
    u'O': [0x4f],

    u'P': [0x50],
    u'Q': [0x51],
    u'R': [0x52],
    u'S': [0x53],
    u'T': [0x54],
    u'U': [0x55],
    u'V': [0x56],
    u'U': [0x55],
    u'V': [0x56],
    u'W': [0x57],
    u'X': [0x58],
    u'Y': [0x59],
    u'Z': [0x5a],
    u'[': [0x5b],
    u'¥': [0x5c],
    u']': [0x5d],
    u'^': [0x5e],
    u'_': [0x5f],

    u'`': [0x60],
    u'a': [0x61],
    u'b': [0x62],
    u'c': [0x63],
    u'd': [0x64],
    u'e': [0x65],
    u'f': [0x66],
    u'g': [0x67],
    u'h': [0x68],
    u'i': [0x69],
    u'j': [0x6a],
    u'k': [0x6b],
    u'l': [0x6c],
    u'm': [0x6d],
    u'n': [0x6e],
    u'o': [0x6f],

    u'p': [0x70],
    u'q': [0x71],
    u'r': [0x72],
    u's': [0x73],
    u't': [0x74],
    u'u': [0x75],
    u'v': [0x76],
    u'w': [0x77],
    u'x': [0x78],
    u'y': [0x79],
    u'z': [0x7a],
    u'{': [0x7b],
    u'|': [0x7c],
    u'}': [0x7d],
    u'→': [0x7e],
    u'←': [0x7f],

    u'。': [0xa1],
    u'「': [0xa2],
    u'」': [0xa3],
    u'、': [0xa4],
    u'・': [0xa5],
    u'ヲ': [0xa6],
    u"ァ": [0xa7],
    u'ィ': [0xa8],
    u'ゥ': [0xa9],
    u'ェ': [0xaa],
    u'ォ': [0xab],
    u'ャ': [0xac],
    u'ュ': [0xad],
    u'ョ': [0xae],
    u'ッ': [0xaf],

    u'ー': [0xb0],
    u'ア': [0xb1],
    u'イ': [0xb2],
    u'ウ': [0xb3],
    u'エ': [0xb4],
    u'オ': [0xb5],
    u'カ': [0xb6],
    u'キ': [0xb7],
    u'ク': [0xb8],
    u'ケ': [0xb9],
    u'コ': [0xba],
    u'サ': [0xbb],
    u'シ': [0xbc],
    u'ス': [0xbd],
    u'セ': [0xbe],
    u'ソ': [0xbf],

    u'タ': [0xc0],
    u'チ': [0xc1],
    u'ツ': [0xc2],
    u'チ': [0xc1],
    u'ツ': [0xc2],
    u'テ': [0xc3],
    u'ト': [0xc4],
    u'ナ': [0xc5],
    u'ニ': [0xc6],
    u'ヌ': [0xc7],
    u'ネ': [0xc8],
    u'ノ': [0xc9],
    u'ハ': [0xca],
    u'ヒ': [0xcb],
    u'フ': [0xcc],
    u'ヘ': [0xcd],
    u'ホ': [0xce],
    u'マ': [0xcf],

    u'ミ': [0xd0],
    u'ム': [0xd1],
    u'メ': [0xd2],
    u'モ': [0xd3],
    u'ヤ': [0xd4],
    u'ユ': [0xd5],
    u'ヨ': [0xd6],
    u'ラ': [0xd7],
    u'リ': [0xd8],
    u'ル': [0xd9],
    u'レ': [0xda],
    u'ロ': [0xdb],
    u'ワ': [0xdc],
    u'ン': [0xdd],
    u'゛': [0xde],
    u'゜': [0xdf],

    u'＇':[0xf0],
    u'"':[0xf1],
    u'°':[0xf2],
    u'×': [0xf7],
    u'÷': [0xf8],
    u'≧': [0xf9],
    u'≦': [0xfa],
    u'≪': [0xfb],
    u'≫': [0xfc],
    u'≠': [0xfd],
    u'√': [0xfe],
    u'￣': [0xff],

    u'ガ': [0xb6, 0xde],
    u'ギ': [0xb7, 0xde],
    u'グ': [0xb8, 0xde],
    u'ゲ': [0xb9, 0xde],
    u'ゴ': [0xba, 0xde],
    u'ザ': [0xbb, 0xde],
    u'ジ': [0xbc, 0xde],
    u'ズ': [0xbd, 0xde],
    u'ゼ': [0xbe, 0xde],
    u'ゾ': [0xbf, 0xde],
    u'ダ': [0xc0, 0xde],
    u'ヂ': [0xc1, 0xde],
    u'ヅ': [0xc2, 0xde],
    u'デ': [0xc3, 0xde],
    u'ド': [0xc4, 0xde],
    u'バ': [0xca, 0xde],
    u'ビ': [0xcb, 0xde],
    u'ブ': [0xcc, 0xde],
    u'ベ': [0xcd, 0xde],
    u'ボ': [0xce, 0xde],
    u'パ': [0xca, 0xdf],
    u'ピ': [0xcb, 0xdf],
    u'プ': [0xcc, 0xdf],
    u'ペ': [0xcd, 0xdf],
    u'ポ': [0xce, 0xdf],
}
def setaddr( address ):
        global addr
        addr = address

def command( code ):
        global addr
        count = 0
        while count < 10:
            try:
                i2c.write_byte_data(addr, 0x00, code)
                time.sleep(0.0002)
                break
            except Exception as e:
                print(e)
                count += 1

def write( message ):
    global addr
    if sys.version_info.major == 2 :
       message = message.decode('utf-8')
    mojilist = []
    for char in message:
        mojilist += CHAR_TABLE[char]

    count = 0
    while count < 10:
        try:
            i2c.write_i2c_block_data(addr, 0x40, mojilist)
            time.sleep(0.0002)
            break
        except Exception as e:
            print(e)
            count += 1
```

#バスの運行情報をスクレイピング
神奈中バスのサイトの接近情報をスクレイピングします。OLEDモジュールの表示桁数が少ないため、遅延の有り無しで表示量を調整しています。最後にOLEDモジュールに表示内容を書き出したら、完成です。

```Python:bus.py
import urllib.request
import cchardet
import re
import so1602
from bs4 import BeautifulSoup

def getBusTime(url):
    with urllib.request.urlopen(url) as res:
        byte = res.read()
        html = byte.decode(cchardet.detect(byte)['encoding'])
        soup = BeautifulSoup(html, 'html.parser')

        #title = soup.head.title
        #print('[title]:', title.text, '\n')

        delay = ''
        for block in soup.find_all('div', {'class':{'frameBox03'}}):
            items = block.text.split()
            #print('[frameBox03]:', items)
            # 0: 'xxxにあとmm分で到着'
            # 1: '運行中'
            # 2: '(mm分遅れ)'
            if len(items) > 2:
                #print('[items]:', items[2])
                text = items[2] # '(mm分遅れ)'
                text = text.strip('（） ()') # 括弧を除去

                pattern = r'[0-9]*'
                result = re.search(pattern, text)
                delay = result.group(0)
                #print('遅延:', delay)
            break
        
        arrivals = []
        for block in soup.find_all('div', {'class':{'placeArea01 departure'}}):
            #print('[placeArea01]:', block.text)
            items = block.text.split()
            text = items[1]  #'（hh:mm着予定）'
            text = text.strip('()') # 括弧を除去

            pattern = r'[0-9]{1,2}:[0-9]{1,2}'
            result = re.search(pattern, text)
            arrivals.append(result.group(0))
            #print('到着時間:', result.group(0))

            if len(delay) > 0:
                break

    return arrivals, delay

def getBusTimeLines(urls):
    buses = []
    for url in urls:
        results = getBusTime(url)
        arrivals = results[0]
        delay = results[1]
        count = 0
        text = ''
        for arrival in arrivals:
            if count > 0:
                text += ' '
            text += arrival
            if count == 0 and len(delay) != 0:
                text += '(-' + delay + ')'
            count += 1
            if count >= 2:
                break
        
        buses.append(text)

    return buses

def display(addr, line1, line2):
    so1602.setaddr(addr)

    # LCD初期化
    so1602.command(0x0c)
    so1602.command(0x01)

    # LCD表示
    # 1行目
    so1602.command(0x80)
    so1602.write(line1)

    # 2行目
    so1602.command(0xa0)
    so1602.write(line2)

if __name__ == '__main__':
    urls = [
        'http://real.kanachu.jp/pc/displayapproachinfo?fNO=11240&tNO=11223',
        'http://real.kanachu.jp/pc/displayapproachinfo?fNO=11240&tNO=11221'
    ]
    buses = getBusTimeLines(urls)

    line1 = 'H→K ' + buses[0]
    line2 = 'N→H ' + buses[1]
    print(line1)
    print(line2)
    display(0x3d, line1, line2)

```

#表示例
```bash
$ python3 bus.py
```

うまく表示できたら、cronにセットします。

```
*/1 * * * * python3 /home/pi/programs/bus.py
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/d8a1da7b-dfa6-28cf-0e09-4b1b7f1da1d2.png)

#さいごに
表示板に常時表示されているので、朝の身支度をしながらチラ見することができて、とても便利です。
[次回](https://qiita.com/yuppejp/items/a4becb2a55104edfb444)はOLEDモジュールをもう一つ使って、現在の天気と予想気温を表示してみたいと思います。お出かけ前に服装を決めるのに便利そうかなと思います。

###参考
- [ラズパイで作る「気温・湿度・気圧センサー」　有機ELディスプレイを取り付け](https://www.itmedia.co.jp/news/articles/1902/22/news025.html)
- [ラズパイで天気情報を電光表示板に表示する](https://qiita.com/yuppejp/items/a4becb2a55104edfb444)