読者です 読者をやめる 読者になる 読者になる

なんとなく

誰得感満載な記事が多いかも。Mono関係とLinuxのサーバ関係、レビューとか。

RaspberryPiとLCDで天気予報を表示

RapsberryPi

はじめに

ちょっと使わずにいたRaspberryPiがあって、有効に使いたいなぁと思っていた。そこで、 よく天気予報が気になって、その都度、検索するのも面倒なので常にRaspberryPiに表示させておいて、それを見ることにするのがいいだろうと思い実装してみた。

f:id:takeshich:20160106153341j:plain

材料

Raspberry Pi Type B 512MB

Raspberry Pi Type B 512MB

PiTFTエンクロージャ (Raspberry Pi Model B用)

PiTFTエンクロージャ (Raspberry Pi Model B用)

3M スコッチ クッションゴム 12.7×3.6mm 14粒 CC-05

3M スコッチ クッションゴム 12.7×3.6mm 14粒 CC-05

すでに持っていた古いType Bに合わせて液晶などを購入したので、現状ならRaspberryPi2 に対応したのを購入したほうが良い。 それとエンクロージャーのバランスが悪かったり、滑ったりするので、ゴム足的なもので対応。

簡単な設計とか

天気予報のAPI

天気予報については個人では無料で使えるAPI複数あったのだが、降水確率を提供しているところがなかった。 降水確率は傘を準備するかどうかを判断する材料になるので、必須と考えていた。 さらに調べていると

http://www.drk7.jp/weather/

が見つかったので、こちらを利用することにした。 個人的に対応されているサイトのようなので、永続性は微妙だけれどもすぐに終わりそうな感じもないのでこちらを採用した。

UI

UIについては、調べたところ

  • X windowを起動して、ブラウザを全画面表示させて内容を表示させる方法
  • pygameを使用して内容を表示させる方法

があった。

自分の自由が効くように細かく作りこみたいと思っていたので、それができそうなpygameを使用して内容を表示させる方法を採用した。 同じようなことを考える人はいるようで、ベースはそちらを使わせてもらった。

github.com

プラグインとして、作成した。そのうち綺麗にしてforkしたのをgithubにあげておこうと思っているけど、してない。

UIと処理が混ざってちょっと気持ち悪さも感じながらもゴリゴリ座標を指定して、動くようにした。

ソース

ライセンスはGPLv3

screen.py

# -*- coding: utf-8 -*-
import os
import pygame
import locale
import re
import json
from time import strftime
from displayscreen import PiInfoScreen
from pygame.locals import *
from datetime import datetime

class myScreen(PiInfoScreen):
    refreshtime = 1
    displaytime = 30
    pluginname = "Clock"
    plugininfo = "Basic digital clock"

    def setPluginVariables(self):
        self.clockfont = os.path.join(self.plugindir, "resources", "SFDigitalReadout-Medium.ttf")
        self.takaofont = os.path.abspath("/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf")
        self.myfont = pygame.font.Font(self.clockfont, 200)
        self.myfontsmall = pygame.font.Font(self.clockfont, 80)
        self.myfontmoresmall = pygame.font.Font(None, 75)
        self.myfonttakao = pygame.font.Font(self.takaofont, 60)
        self.myfonttakao50 = pygame.font.Font(self.takaofont, 50)
        self.myfonttakao40 = pygame.font.Font(self.takaofont, 40)
        self.myfonttakao35 = pygame.font.Font(self.takaofont, 35)
        self.myfonttakaosmall = pygame.font.Font(self.takaofont, 30)
        self.weatherfont = pygame.font.SysFont(None, 80)
        self.weatherfont50 = pygame.font.SysFont(None, 50)
        self.weatherfontsmall = pygame.font.SysFont(None, 40)
        self.weathersource = self.pluginConfig["Weather"]["weatherurl"]
        self.weatherrefresh = int(self.pluginConfig["Weather"]["weatherrefresh"]) * 60
        self.cacheFile = os.path.join(self.plugindir, "resources", "cachedWeather.json")

    def updateWeather(self):
        weather = json.loads(re.sub(r'([a-zA-Z_0-9\.]*\()|(\);?$)','',self.getPage(self.weathersource)))
        self.cacheWeather(weather)
        return weather

    def cacheWeather(self, weather):
        cache = {}
        cache['timestamp'] = int(datetime.now().strftime("%s"))
        cache['weather_info'] = weather
        with open(self.cacheFile, 'w') as outfile:
            json.dump(cache, outfile)

    def loadWeather(self):
        try:
            raw = open(self.cacheFile, 'r')
            cached = json.load(raw)
        except:
            cached = False

        if cached:
            if int(datetime.now().strftime("%s")) < int(cached['timestamp']) + self.weatherrefresh:
                weather = cached['weather_info']
            else:
                weather = self.updateWeather()
        else:
            weather = self.updateWeather()

        return weather

    def drawClock(self):

        clockSurface = pygame.Surface((694,177))
        clockSurface.fill([0,0,0])

        mytime = strftime("%H:%M")
        mysecs = strftime("%S")
        myyear = strftime("%Y/%m/%d")
        weekday = (u'日曜日',u'月曜日',u'火曜日',u'水曜日',u'木曜日',u'金曜日',u'土曜日')
        mydayw = weekday[int(strftime("%w"))]

        clocklabel = self.myfont.render(mytime, 1, [255,255,255])
        #secondlabel = self.myfontsmall.render(mysecs, 1, [255,255,255])
        yearlabel = self.myfontmoresmall.render(myyear, 1, [255,255,255])
        if int(strftime("%w")) == 0:
            daywlabel = self.myfonttakao.render(mydayw, 1, [255,0,0])
        elif int(strftime("%w")) == 6:
            daywlabel = self.myfonttakao.render(mydayw, 1, [0,0,255])
        else:
            daywlabel = self.myfonttakao.render(mydayw, 1, [255,255,255])

        textpos = clocklabel.get_rect()
        textpos.topleft = clockSurface.get_rect().topleft
        #secpos = [ textpos[0] + textpos[2] + 10, textpos[1] + 70 ]
        yearpos = yearlabel.get_rect()
        yearpos.topright = clockSurface.get_rect().topright
        daywpos = daywlabel.get_rect()
        daywpos.right = clockSurface.get_rect().right
        daywpos.top = yearpos.bottom + 1

        #self.surface.blit(secondlabel, secpos)
        self.surface.blit(clocklabel, textpos)
        self.surface.blit(yearlabel, yearpos)
        self.surface.blit(daywlabel, daywpos)

        pygame.draw.rect(clockSurface, (0, 0, 0), (0, 0, 694, 177), 1)
        return clockSurface

    def renderTodayWeather(self, daily):
        todayweatherrect = pygame.Surface((347,256))
        todayweatherrect.fill([255,255,255])
        self.myfonttakao50.set_bold(True)

        #weatherdate = daily['date']
        weatherdate = datetime.strptime(daily['date'],'%Y/%m/%d')
        weathertext = daily['weather']
        weathericon = daily['img']
        weekday = (u'日',u'月',u'火',u'水',u'木',u'金',u'土')
        mydayw = weekday[int(weatherdate.strftime("%w"))]
        yesterday = weatherdate.replace(day=1)

        datelabeltext = "X" + weatherdate.strftime('%d') + mydayw
        datelabeltext = datelabeltext.replace('X0','X').replace('X','')

        if int(weatherdate.strftime("%w")) == 0:
            datelabel = self.myfonttakao50.render(datelabeltext, 1, (255, 0, 0))
        elif int(weatherdate.strftime("%w")) == 6:
            datelabel = self.myfonttakao50.render(datelabeltext, 1, (0, 0, 255))
        else:
            datelabel = self.myfonttakao50.render(datelabeltext, 1, (0, 0, 0))

        self.myfonttakao50.set_bold(False)
        weatherimage = pygame.transform.scale(self.LoadImageFromUrl(weathericon),(200, 128))
        weatherlabel = self.myfonttakao50.render(weathertext, 1, (0, 0, 0))

        temperature = daily['temperature']['range']
        for i,t in enumerate(temperature):
            templabel = self.weatherfont.render(t['content'] + u'℃', 1, (0, 0, 0))
            temppos = templabel.get_rect()
            temppos.y = templabel.get_rect().bottom*i
            temppos.right = todayweatherrect.get_rect().right
            todayweatherrect.blit( templabel, temppos )
            #print '%s %s' % (t['centigrade'],t['content'])

        rainfallchance = daily['rainfallchance']['period']
        for i,r in enumerate(rainfallchance):
            #pophourlabel = self.weatherfontsmall.render(r['hour'] , 1, (0, 0, 0))
            #pophourpos = pophourlabel.get_rect()
            #pophourpos.centerx = todayweatherrect.get_rect().right/4 * i + todayweatherrect.get_rect().right/8
            #pophourpos.y = datelabel.get_rect().centery + weatherimage.get_rect().bottom + weatherlabel.get_rect().bottom

            poplabel = self.weatherfont50.render(r['content'] + "%", 1, (0, 0, 0))
            poppos = poplabel.get_rect()
            poppos.centerx = todayweatherrect.get_rect().right/4 * i + todayweatherrect.get_rect().right/8
            #poppos.y = datelabel.get_rect().centery + weatherimage.get_rect().bottom + weatherlabel.get_rect().bottom
            poppos.bottom = todayweatherrect.get_rect().bottom

            #todayweatherrect.blit(pophourlabel, pophourpos)
            todayweatherrect.blit(poplabel, poppos)
            #todayweatherrect.blit(poplabel, (86.75*i, datelabel.get_rect().bottom+weatherimage.get_rect().bottom+weatherlabel.get_rect().bottom))
            #print '%s:%s%%' % (r['hour'],r['content'])
            #print datelabel.get_rect().bottom+weatherimage.get_rect().bottom

        imagepos = weatherimage.get_rect()
        imagepos.right = todayweatherrect.get_rect().centerx
        imagepos.y = datelabel.get_rect().centery
        imagepos.x = 1

        weatherlabelpos = weatherlabel.get_rect()
        weatherlabelpos.centerx = todayweatherrect.get_rect().centerx
        weatherlabelpos.y = imagepos.bottom + 1
        todayweatherrect.blit(weatherimage, imagepos)
        todayweatherrect.blit(datelabel, (0, 0))
        todayweatherrect.blit(weatherlabel, weatherlabelpos)

        pygame.draw.rect(todayweatherrect, (0, 0, 0), (0, 0, 347, 256), 1)

        return todayweatherrect

    def renderWeeklyWeather(self, weekly):
        weeklyrect = pygame.Surface((138.8,66))
        weeklyrect.fill([255,255,255])
        weatherdate = datetime.strptime(weekly['date'],'%Y/%m/%d')
        weathericon = weekly['img']
        weekday = (u'日',u'月',u'火',u'水',u'木',u'金',u'土')
        mydayw = weekday[int(weatherdate.strftime("%w"))]

        weatherimage = pygame.transform.scale(self.LoadImageFromUrl(weathericon),(92, 59))
        datelabeltext = "X" + weatherdate.strftime('%d') + mydayw
        datelabeltext = datelabeltext.replace('X0','X').replace('X','')
        self.myfonttakao35.set_bold(True)
        if int(weatherdate.strftime("%w")) == 0:
            datelabel = self.myfonttakao35.render(datelabeltext, 1, (255, 0, 0))
        elif int(weatherdate.strftime("%w")) == 6:
            datelabel = self.myfonttakao35.render(datelabeltext, 1, (0, 0, 255))
        else:
            datelabel = self.myfonttakao35.render(datelabeltext, 1, (0, 0, 0))

        daywpos = datelabel.get_rect()
        daywpos.right = weeklyrect.get_rect().right

        imagepos = weatherimage.get_rect()
        imagepos.top = weeklyrect.get_rect().top
        imagepos.left = weeklyrect.get_rect().left

        weeklyrect.blit(weatherimage, imagepos)
        weeklyrect.blit(datelabel, daywpos)
        pygame.draw.rect(weeklyrect, (0, 0, 0), (0, 0, 138.8, 66), 1)

        return weeklyrect

    def showScreen(self):
        self.surface.fill([0,0,0])

        # Draw the clock
        self.surface.blit(self.drawClock(), (694,0))
        #self.drawClock()

        # Draw the weather
        weather = self.loadWeather()
        obj = weather['pref']['area'][u'東京地方']['info']

        #今日明日の天気
        for i in range(0,2):
            forecast = obj[i]
            self.surface.blit(self.renderTodayWeather(forecast), (347*i, 144))

        #明後日以降の天気
        for i in range(2,7):
            forecast = obj[i]
            self.surface.blit(self.renderWeeklyWeather(forecast), (138.8 *(i-2), 400))


        # Scale our surface to the required screensize before sending back
        scaled = pygame.transform.scale(self.surface,self.screensize)
        self.screen.blit(scaled,(0,0))

        return self.screen

削除し忘れて普通に動いてたところがあって、DoSになりかねないバグだったので修正(2016/01/7 追記)

JSONP形式を読み込んでいろいろと処理している形。 takaoフォントを使用しているので、インストールする必要がある。

sudo apt-get install fonts-takao

もしくは、好みのフォントに変更してほしい。

config/screen.ini

[Weather]
# JSON link for weather.
# You'll need to register for a WeatherUnderground API key
# available from http://www.wunderground.com/weather/api/d/pricing.html
# weathersource=http://api.wunderground.com/api/[API-KEY]/hourly/q/UK/London.json
weatherurl=http://www.drk7.jp/weather/json/13.js
# Interval in minutes to refresh weather data
weatherrefresh=5

LoadImageFromUrlについては、キャッシュではないが、resourceに対象のファイルが無いか確認して、ある場合は、ファイルから、ない場合は画像ファイルを書き込むという修正を加えた。

違う地域にしたいなら

違う地域にしたい場合は、screen.pyで

obj = weather['pref']['area'][u'東京地方']['info']

としていることを適宜変更するのと、 config/screen.ini で

weatherurl=http://www.drk7.jp/weather/json/13.js

としているのを適宜変更してください。

例えば大阪なら

obj = weather['pref']['area'][u'大阪府']['info']

config/screen.ini で

weatherurl=http://www.drk7.jp/weather/json/27.js

かな。

最後に

プロセスのCPU使用率が9割となっていてどうかな~と思うところはある。 液晶が2.8インチと小さいのでちょっと見づらいかもしれない。 とりあえず1週間程度動かしてみておかしくはなっていないが、もう少し様子を見てみたい。