[ラズパイ] ラズパイZeroでダッシュボード(情報表示板)を作る コーディング編

前の記事では組み立てまで書いたけど、とどのつまり

ラズパイをディスプレイ接続してみたっ!!

だけの話し。特に目新しいこともしてないし、商品紹介に終わってたけど、ここからが本番。ダッシュボードを作りにかかる。最初はコーディングする気なんて全くなかった。なぜなら、

上サイトみたいに

Webベースでやろうと思ってたからっ!!

つまりはサーバーを立てて、そこでダッシュボードの表示するサイトにブラウザでアクセスして表示するという方法。おそらく、デザインしやすいし汎用性が高いんでしょう。途中まで3b+でやってて、なんかもっさりしてるなぁと思って、嫌な予感はしていましたw。Zeroで試しに「smashing」とやらを動かし、ブラウザで表示させようとしたところ。。。

まず、クロームの起動がやたら遅い・・・。そのうえダッシュボードのアドレスを起動しても反応なし・・・w

ブラウザが重いのか、サーバーが重いのか・・・?。ブラウザを軽量のもの「Surf」に変えてみたけど、やっぱり表示には至らない。

これは・・・Zeroじゃ無理なんでは?

サーバーをPi3B+で起動して、再度軽量ブラウザで・・・これじゃぁ、手間だけかかって単品動作ができてない・・・。どうしようか・・・・ディスプレイに3b+を積みなおすか?(負け)www

そうだ!!ダメ元でPythonのGUIプログラムをつくって専用のソフトとして動かすしかない!!

というわけで、Python+PyGameにて制作に取り掛かる。もう、ここからが手間・・・。なんせPythonでクラスを複数作るようなアプリを作ったことがないから時間がかかる・・・。

無知からの愚痴だとわかっているものの。。インタープリタが故に走らせてみてから「値が入ってねー」だの「型が違う」だの・・・じゃぁ型推論とかやめよーぜ!ブツブツ。コードエディッタの補完機能がいまいちとか・・・。クラスでメソッド定義の時に引数にselfつけるの忘れて、エラーも出なく小一時間悩むとか(これなんか言っくれよ!!)・・・。

そうかと思えば、ふと・・・Pythonってガベージコレンション?とかメモリ管理も気になりすぎて、コーディングどころじゃなくなったりとか・・・・w。pythonはガベージコレクションなので循環参照しない限り大丈夫とのことです。

色々ココロが折れそうになったよ!!

ここを参考にしながら、サンプルコードを変えていく。まずは時計を表示してみたら。Zeroでも問題なく動くことが分かった。とりあえず、思いつくままでコーディングしてしまったので、後で整理して掲載しようと思うけど、時計の部分だけ掲載。

    def update_clock(_x,_y,width,height,back_color):
        
        last_min  = -1
        last_day  = -1
        last_sec  = -1
        
        while th_flags['clock_widget']:
            # 日時の取得
            _now = datetime.now()
            
            today = _now.strftime("%m/%d") + '('+yobi[_now.weekday()]+')'
            nowtime = _now.strftime("%H:%M")
            sectime = _now.strftime("%S")
            
            if last_min != _now.minute:
                clock_offset = (-60,-50)
                clock_st = clock_font.render(nowtime, True, (255,255,255))
                x = _x + (width - clock_st.get_width()) / 2 + clock_offset[0]
                y = _y + ((height - clock_st.get_height()) / 2) + clock_offset[1]
                pygame.draw.rect(screen, back_color, Rect(x,y,clock_st.get_width(),clock_st.get_height()))
                screen.blit(clock_st, (x,y))
                
                last_min = _now.minute
                # 日付も重なるため再描画
                last_day = -1 
            
            if last_sec != _now.second :
                sec_st_offset = (330,-10)
                sec_st = sec_font.render(sectime, True, (255,255,255))
                x = _x + (width - sec_st.get_width()) / 2 + sec_st_offset[0]
                y = _y + ((height - sec_st.get_height()) / 2) + sec_st_offset[1]
                pygame.draw.rect(screen, back_color, Rect(x,y,sec_st.get_width(),sec_st.get_height()))
                screen.blit(sec_st, (x,y))
                
                last_sec = _now.second
            
            if last_day != _now.day :
                day_color = (255,255,255)
                if _now.weekday()==6:
                    day_color = (255,100,100)
                if _now.weekday()==5:
                    day_color = (180,180,255)
                    
                date_offset = (-100,120)
                date_st = date_font.render(today, True, day_color)
                x = _x + (width - date_st.get_width()) / 2 + date_offset[0]
                y = _y + ((height - date_st.get_height()) / 2) + date_offset[1]
                
                pygame.draw.rect(screen, back_color, Rect(x,y,date_st.get_width(),date_st.get_height()))
                screen.blit(date_st, (x,y))
                
                r = odc.get_roku(_now.year,_now.month,_now.day)
                roku_color = (255,255,255)
                if r == '大安':
                    roku_color = (255,100,100)
                    
                roku_offset = (220,120)
                roku_st = date_font.render(r, True, roku_color)
                x = _x + (width - roku_st.get_width()) / 2 + roku_offset[0]
                y = _y + ((height - roku_st.get_height()) / 2) + roku_offset[1]
                
                pygame.draw.rect(screen, back_color, Rect(x,y,roku_st.get_width(),roku_st.get_height()))
                screen.blit(roku_st, (x,y))
                
                
                last_day = _now.day
            
            pygame.display.update()
            
            time.sleep(0.1)

            
            
    th_flags['clock_widget'] = True
    th = threading.Thread(target=update_clock, args=( x, y, width, height, back_color,))
    th.start()
    #threads.append(th)

いろいろ、ごちゃごちゃしているが、ざっくりいうと

  • スレッドを0.1s毎に呼び出して、時間の再描記を行っている
  • 0.1秒毎に全面書き換えてもチラつく(処理速度の関係)ので、分に変化があった時に必要な部分のみ再描記を行う

 

まぁ、六曜の計算(odcというクラスが担っている)とかも日付変更時のみに行うとかしたほうがイイと思う。まだ最適化してないので許して・・・。表示する項目が増えれば増えるほど、スレッド数が増えるし、動作も重くなるので簡潔に軽くコーディングするのが必須です(やってないけどw)。th_flagsとthreadsで管理してるけど、終わったらスレッドはちゃんと止めましょう。

基本は、この動作を平行に処理するだけ。時計は自動でデータ更新してくれる(datetimeクラスでnowを叩けばでる)けど、他はデータの更新が必要となる。

  • データ更新スレッド(天気、地震、消費電力、アメダスを定期的に読みに行くスレッド)
  • 表示更新スレッド(ディスプレイの表示を管理するスレッド)

 

この2本立てで管理していくような仕組みになっている。このデータ更新スレッドは注意が必要でWebスクレイピングという技術を使う。なるべく相手サーバーに負荷をかけないような頻度でデータ更新を行わなければならない。「1秒に1回程度が」行儀がイイと言われているけど、天気予報やアメダスは30~60分に一度程度でいいし、地震情報もせいぜい10秒に1度程度でよいだろう。気を付けて運用するべきところだと思う。

現在は、時計機能、天気予報(今日・明日・警報・注意報)、地震情報(NHK由来)、地震速報(HiNet由来)、現在の気温など(アメダス由来)、室温なと(BME280由来)、利用電力量(クランプメータ由来)を切り替えて表示している。汚すぎるコードなのできれいにしてからと思う。そして、それぞれクラス別に作ってるので記事にしていきたいと思う。