日記/2020-7

<< 2020-7 >>
S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

2020-7-23

ベランダの花・続続続続

 


2020/06/07

Image00001.jpg

2020/06/10

Image00002.jpg

2020/06/12

Image00003.jpg

2020/06/17

Image00004.jpg

2020/06/21

Image00005.jpg

2020/06/24

Image00006.jpg

2020/07/23

Image00007.jpg


2020-7-12

室内のCO2濃度が見たい・続続

 

 「室内のCO2濃度が見たい」、「室内のCO2濃度が見たい・続」の続き。
 室内のCO2濃度をLametric Timeで表示できるようになったが、自己満足ついでに値の推移をグラフで見たくなった。
 昨日掲載したスクリプト(room.py)は、cronで5分おきに実行しているのだが、ログとして、室温、湿度、CO2濃度をroom.logへ出力している。

 */5 * * * * cd /home/pi/lametric/;./room.py >> /home/pi/lametric/room.log 2> /dev/null

 room.logの内容は以下のような感じ。

 date,temperature,humidity,co2
 2020-07-11 13:50,27,68,513
 2020-07-11 13:55,27,68,519
 2020-07-11 14:00,27,68,507
 2020-07-11 14:05,27,68,508
 2020-07-11 14:10,27,68,505

 これをグラフ化してみた。

 #!/usr/bin/env python3
 import pandas as pd
 import matplotlib.pyplot as plt
 import re
 import pprint
 
 df = pd.read_csv('room.log', index_col='date')
 
 period = int(-1 * (60 / 5) * 24 * 1)
 ltst = df[period:].interpolate()
 data1 = ltst.loc[:, 'temperature']
 data2 = ltst.loc[:, 'humidity']
 data3 = ltst.loc[:, 'co2']
 
 xt = []
 xl = []
 idx = ltst.index.values.tolist()
 for i in idx:
     if '00:00' in i:
         xt.append(i)
         xl.append(re.search(r'(\d\d-\d\d) ', i).group())
     elif '12:00' in i:
         xt.append(i)
         xl.append('')
     else:
         xt.append('')
         xl.append('')
 
 plt.style.use('seaborn-darkgrid')
 
 fig, [ax1, ax2, ax3] = plt.subplots(3, 1, sharex='col')
 fig.set_figwidth(12.8)
 fig.set_figheight(9.6)
 
 ax1.plot(data1, color='indianred')
 ax1.set_ylabel('temperature')
 
 ax2.plot(data2, color='royalblue')
 ax2.set_ylabel('humidity')
 
 ax3.plot(data3, color='seagreen')
 ax3.set_ylabel('co2')
 ax3.set(xticks=xt, xticklabels=xl)
 
 plt.tight_layout()
 plt.savefig('graph.png')

 以下ができたグラフ(graph.png)。一日分のグラフで、9行目で変数periodの値を決めている、右辺の最後の数字が日数を表している。

 12時過ぎのCO2濃度の急上昇は、昼飯でコンロに火を付けたためと思われる。換気したら値が一気に下がり、一緒に温度、湿度も急降下していることが見て取れる。
 だからなに、って話ですが。
 

参考


2020-7-11

室内のCO2濃度が見たい・続

 

 先日書いた「室内のCO2濃度が見たい」の続き。
 2020/06/20にBanggood.comに発注したCO2濃度センサー「MH-Z19」だが、

 昨日(2020/07/10)届いた。発注から到着まで20日間かかったことになる(納期は10日から30日となっていた)。
 なお、届いたのは「MH-Z19B」だった。

 早速、こちらのページを参考に、もともと近所のアメダスの情報やNature Remoの内蔵センサーの値をLametric Timeへ表示するために使っていたRaspberry Pi 3Bに接続してみた。金属製の台の上に置いてあるため、センサーの下にダンボールを貼り付けてある。

mh_z19b.jpg

 センサーの値も問題なく取得できたので、これをLametric Timeに表示してみた。

 #!/usr/local/bin/python3
 import requests
 import json
 import datetime
 import subprocess
 
 url = 'https://api.nature.global/1/devices'
 
 headers =  {
     'contentType': 'application/json',
     'Authorization': 'Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
 }
 
 res = requests.get(url, headers=headers)
 data = res.json()
 
 hum = str(data[0]['newest_events']['hu']['val'])
 temp = str(round(data[0]['newest_events']['te']['val'], 1))
 
 mh = subprocess.check_output(['sudo', 'python3', '-m', 'mh_z19']).decode('utf-8')
 mh = json.loads(mh)
 co2 = str(mh['co2'])
 
 print(f"{datetime.datetime.today().strftime('%Y-%m-%d %H:%M')},{temp},{hum},{co2}")
 
 hum = hum + '%'
 temp = temp + '°C'
 co2 = co2 + 'ppm'
 
 disp = {
     'frames': [
         {
             'index' : 0,
             'text'  : temp,
             'icon'  : '12464'
         },
         {
             'index' : 1,
             'text'  : hum,
             'icon'  : '12184'
         },
         {
             'index' : 2,
             'text'  : co2,
             'icon'  : '32936'
         }
     ]
 }
 
 disp = json.dumps(disp)
 
 headers = {
     'X-Access-Token': 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy',
     'Cache-Control': 'no-cache',
     'Accept': 'application/json'
 }
 
 url = "https://developer.lametric.com/api/V1/dev/widget/update/com.lametric.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz/1"
 
 res = requests.post(url, disp, headers=headers)

 上記のスクリプトをcronで5分おきに実行し、表示する情報を更新している。

 表示は順に、時刻 -> 日付 -> 近所のアメダスの気温 -> 天気と降水量 -> 風速 -> 室温 -> 湿度 -> CO2濃度 となっている(アメダスの情報は別のスクリプトで更新している)。

 まあ、自己満足以外の何物でもないが、満足満足。
 

参考


2020-7-5

Googleカレンダーに阪神とプロ野球の日程をインポートするためのスクレイピング・2020年版

 

calendar.png

 先月(2020/06/19)とうとうプロ野球が開幕した。阪神ファンとしては、2020/07/05現在、早くもシーズン終わったんじゃねえかとも思える状況だが、それはそれとして、昨年以下のようなスクリプトを紹介していた。

 早速今年の日程をGoogleカレンダーにインポートしようとしてみたのだが、これが素直に動いてくれない。
 以下に動かすまでにやったことを示す。

阪神タイガースの日程

 まずはスクリプトの修正。

  • オリックスが「bs」だったのが「b」に変わってたのに対応。
  • 年を「2020」に、日にちを今年の変則日程に応じて変更した。
  • いちいち「JERAセ・リーグ公式戦」と表示されるのがうるさいので消した。
 #!/usr/bin/python3
 #coding: utf-8
 
 #scrapingtigers.py
 
 import re
 import datetime
 import urllib.request
 import requests
 import pprint
 from bs4 import BeautifulSoup
 
 data = {}
 
 year = '2020'
 
 team = {
     't':'阪神',
     's':'ヤクルト',
     'd':'中日',
     'h':'ソフトバンク',
     'e':'楽天',
     'f':'日本ハム',
     'l':'西武',
     'db':'DeNA',
     'm':'ロッテ',
     'b':'オリックス',
     'g':'巨人',
     'c':'広島',
 }
 
 head = "Subject, Start Date, Start Time, End Date, End Time, Description, Location"
 print(head)
 
 #month_days = {'03':'31', '04':'30', '05':'31', '06':'30', '07':'31', '08':'31', '09':'30'}
 month_days = {'06':'30', '07':'31', '08':'31', '09':'30', '10':'31', '11':'30'}
 
 for month in month_days.keys():
     data.setdefault(month, {})
     for day in range(int(month_days[month])):
         data[month].setdefault(day + 1, {})
         data[month][day + 1].setdefault('date', year + '/' + month + '/' + ('0' + str(day + 1))[-2:])
 
 for month in month_days.keys():
     html = requests.get("https://m.hanshintigers.jp/game/schedule/" + year + "/" + month + ".html")
     soup = BeautifulSoup(html.text, features="lxml")
     day = 1
     for tag in soup.select('li.box_right.gameinfo'):
         text = re.sub(' +', '', tag.text)
         info = text.split("\n")
         if len(info) > 3:
             if info[1] == '\xa0' or re.match('JERAセ・リーグ公式戦', info[1]):
                 info[1] = ''
             data[month][day].setdefault('gameinfo', info[1])
             data[month][day].setdefault('start', info[2])
             data[month][day].setdefault('stadium', info[3])
             if re.match('オールスターゲーム', info[2]):
                 data[month][day]['gameinfo'] = info[2]
                 data[month][day]['start'] = '18:00'
 
         text = str(tag.div)
         if text:
             m = re.match(r'^.*"nologo">(\w+)<.*$', text, flags=(re.MULTILINE|re.DOTALL))
             if m:
                 gameinfo = m.group(1)
                 data[month][day].setdefault('gameinfo', gameinfo)
             m = re.match(r'^.*"logo_left (\w+)">.*$', text, flags=(re.MULTILINE|re.DOTALL))
             if m:
                 team1 = m.group(1)
                 data[month][day].setdefault('team1', team[team1])
             m = re.match(r'^.*"logo_right (\w+)">.*$', text, flags=(re.MULTILINE|re.DOTALL))
             if m:
                 team2 = m.group(1)
                 data[month][day].setdefault('team2', team[team2])
 
         day += 1
 
 for month in month_days.keys():
     for day in data[month].keys():
         if data[month][day].get('start'):
             m = re.match(r'(\d+):(\d+)', data[month][day]['start'])
             if m:
                 sthr = m.group(1)
                 stmn = m.group(2)
                 start = datetime.datetime(int(year), int(month), int(day), int(sthr), int(stmn), 0)
                 delta = datetime.timedelta(hours=4)
                 end = start + delta
                 sttm = start.strftime("%H:%M:%S")
                 entm = end.strftime("%H:%M:%S")
                 summary = ''
                 if data[month][day]['gameinfo']:
                     summary = data[month][day]['gameinfo'] + " "
                 if not re.match('オールスターゲーム', data[month][day]['gameinfo']):
                     summary += data[month][day]['team1'] + "対" + data[month][day]['team2']
                 #head = "Subject, Start Date, Start Time, End Date, End Time, Description, Location"
                 print(f"{summary}, {data[month][day]['date']}, {sttm}, {data[month][day]['date']}, {entm}, {summary}, {data[month][day]['stadium']}")

 これぐらいの修正で動きそうなものだが(環境はWindow10のWSL(Ubuntu20.04))、

SSL routines:tls12_check_peer_sigalg:wrong signature type

みたいなエラーを吐いて止まってしまう。
 原因は、Ubuntu20.04にデフォルトで入ってるOpenSSLのバージョンが古いため。1.1.1fが入ってるんだが、これを1.1.1gへ上げれば、無事動くようになる。バージョンアップはaptではダメで、ソースコードからコンパイルする必要があるようなので(少なくとも私はそうした)ググってきちんと調べてやってください。
 後は出力結果をcsvファイルへ吐き出して、それをGoogleカレンダーへインポートすればOK。

プロ野球の日程

 こちらも変なエラーに悩まされたが、環境のアップデートではなく、スクリプトの修正で事足りた。

  • 年を「2020」に、日にちを今年の変則日程に応じて変更した。
  • SSL認証でエラーが出ないよう、対応を追記。
 #!/usr/bin/python3
 #coding: utf-8
 
 #scrapingnpb2.py
 
 import sys
 import re
 import datetime
 import pandas as pd
 import ssl
 
 ssl._create_default_https_context = ssl._create_unverified_context
 
 print("Subject, Start Date, Start Time, End Date, End Time, Description, Location")
 
 year = '2020'
 #months = ['03', '04', '05', '06', '07', '08', '09']
 months = ['06', '07', '08', '09', '10', '11']
 
 # 0,     1,              2,                 3,          4,   5
 #(0, '3/29(金)', 'DeNA -  中日', '横\u3000浜  18:30', nan, nan)
 
 for month in months:
     url = "http://npb.jp/games/" + year + "/schedule_" + month + "_detail.html"
     tb = pd.io.html.read_html(url)
     for row in tb[0].itertuples(name=None):
         card = ''
         md = re.sub(r'(.*)', '', row[1])
         ymd = year + '/' + md
         sttm = ''
         entm = ''
         place = ''
         if row[2] == row[2]:
             card = re.sub(' -  ', '対', row[2])
         if row[3] == row[3]:
             place_time = row[3].split('  ')
             if len(place_time) > 1:
                 (sthr, stmn) = place_time[1].split(':')
                 (mon, day) = md.split('/')
                 start = datetime.datetime(int(year), int(mon), int(day), int(sthr), int(stmn), 0)
                 delta = datetime.timedelta(minutes=200)
                 end = start + delta
                 sttm = start.strftime("%H:%M:%S")
                 entm = end.strftime("%H:%M:%S")
                 place = re.sub(r'\s+', '', place_time[0])
             else:
                 sttm = '18:00:00'
                 entm = '21:20:00'
                 place = place_time[0]
 
         if len(sys.argv) > 1:
             m = re.search(sys.argv[1], card)
             if m:
                 print(f"{card}, {ymd}, {sttm}, {ymd}, {entm}, {card}, {place}")
         elif card != '':
             print(f"{card}, {ymd}, {sttm}, {ymd}, {entm}, {card}, {place}")

 当初は以下のようなエラーに悩まされた。

urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1108)>

 去年との違いは、NPBのサイトが「https」になっていたこと。
 でも、pandasのサイトによると、以下のようにpandas.read_htmlは(というかlxmlは)httpsには対応していない。

pandas.read_html(io, match='.+', flavor=None, header=None, index_col=None, skiprows=None, attrs=None, parse_dates=False, thousands=',', encoding=None, decimal='.', converters=None, na_values=None, keep_default_na=True, displayed_only=True)
Read HTML tables into a list of DataFrame objects.
Parameters:io:str, path object or file-like object
A URL, a file-like object, or a raw string containing HTML. Note that lxml only accepts the http, ftp and file url protocols. If you have a URL that starts with 'https' you might try removing the 's'.

 なので、スクリプト上「http」にアクセスしているのはそれはそれで正しいはずなのだが、なぜかSSL認証エラーになってしまう(サイト側でhttpへのアクセスをhttpsへリダイレクトしてるためか?)。
 で、しかたがないので、SSL認証エラーを無視するようにした次第。
 後はこれまた出力結果をcsvファイルへ吐き出して、それをGoogleカレンダーへインポートすればOK。