自動観察日記

自動で観察したい

はてなブログAPIを使った自動投稿プログラムの改良

これを改良しました。

saibaimen.hatenadiary.jp

温湿度・写真の自動取得

ベースは以前述べたexample/simple_test.py ですが、それにカメラモジュールでの撮影を含めたプログラムを作りました。

saibaimen.hatenadiary.jp

#!/home/pi/.pyenv/shims/python
# log_condition_v3.py                                                                                                      
# -*- coding: utf-8 -*-                                                                                                              

import os
import Adafruit_DHT
import datetime

now = datetime.datetime.now() # 現在の日時を取得                                                                                     

sensor = Adafruit_DHT.DHT22
pin = 4
degC = '℃'

#def condition(humidity, temperature, discomfort):                                                                                   
def condition():
    humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
    discomfort = 0.81*temperature + 0.01*humidity*(0.99*temperature - 14.3) + 46.3

    return humidity, temperature, discomfort

def takepic():
    #    cmd = 'raspistill -n -o /home/pi/log_condition/log_pic/{0:%Y%m%d%H%M}.png -t 500 -q 5 -e'.format(now)                       
    cmd = 'raspistill -n -o /home/pi/log_condition/log_pic/{0:%Y%m%d%H}.png -t 500 -q 5 -e'.format(now)
    cmdact = os.popen(cmd)

def main():
    # log current condition                                                                                                          
    file = open('/home/pi/log_condition/log_TH/{0:%Y%m%d}.txt'.format(now), 'a+')  #書き込みモードでオープン                         
    humidity, temperature, discomfort = condition()
    # Markdown記法(半角スペース2個で改行)                                                                                          
    file.write('時刻:{0:%Y/%m/%d %H:%M:%S}  \n'.format(now))
    file.write('湿度:{0:3.1f}%  \n'.format(humidity))
    file.write('温度:{0:3.1f} {1}  \n'.format(temperature,degC))
    file.write('不快指数:{0:3.1f} \n\n'.format(discomfort))

    #    take a picture                                                                                                              
    takepic()

if __name__ == '__main__':
    main()

cronに書き込んで、1日3回情報を取得するようにしておきます。

$ crontab -e

0 7,12,22 * * * sudo python /home/pi/log_condition/log_condition_v3.py

7:00、12:00、22:00に温湿度のログと写真撮影を行うようにしました。

はてなブログへの自動投稿

自動投稿は次のような手順で行うようにしました。
1. はてなフォトライフAPIを使って、写真をはてなフォトライフへアップロードする
2. はてなフォトライフAPIを使って、アップロードした写真の情報を取得する
3. 取得した情報(アップロードされたときの名称)と一緒に、はてなブログAPIを使ってはてなブログへとアップロードする

#!/home/pi/.pyenv/shims/python
# hatenablog_post_v7.py                                                                                                                                                                           
#coding=utf-8                                                                                                                                                                                               
import os
import sys
import random
import requests
from base64 import b64encode
from datetime import datetime
from hashlib import sha1
from pathlib import Path
from chardet.universaldetector import UniversalDetector
import re
from time import sleep
import shutil
import glob

now = datetime.now()
dtime = str(now.year)+"""-"""+str(now.month)+"""-"""+str(now.day)+"""T"""+str(now.hour)+""":"""+str(now.minute)+""":"""+str(now.second)
print(dtime)

# setting -----------------------------------------------------------                                                                                                                                       
username = 'はてなブログに登録している名前(ここではshut9)'
api_key  = '********APIキーをここに書く*******'
blogname = 'ブログの名前を書く(ここではsaibaimen.hatenadiary.jp)'
draft = 'yes' # yes or no    下書きとして投稿する場合はyes。本投稿はno。                                                                                                                                                                               

# WSSE                                                                                                                                                                                                      
def wsse(username, api_key):
    created = datetime.now().isoformat() + "Z"
    b_nonce = sha1(str(random.random()).encode()).digest()
    b_digest = sha1(b_nonce + created.encode() + api_key.encode()).digest()
    c = 'UsernameToken Username="{0}", PasswordDigest="{1}", Nonce="{2}", Created="{3}"'
    return c.format(username, b64encode(b_digest).decode(), b64encode(b_nonce).decode(), created)

# hatena blog                                                                                                                                                                                               
def create_data_blog(title, body, fotoname):
    if fotoname == None:
        text = body
    else:
        text = body + """                                                                                                                                                                                   
                                                                                                                                                                                                            
[f:id:shut9:{0}p:plain]                                                                                                                                                                                     
        """.format(fotoname)

    template = """<?xml version="1.0" encoding="utf-8"?>                                                                                                                                                    
    <entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app">                                                                                                                      
    <title>{0}</title>                                                                                                                                                                                      
    <author><name>{1}</name></author>                                                                                                                                                                       
    <content type="text/x-markdown">                                                                                                                                                                        
{2}                                                                                                                                                                                                         
    </content>                                                                                                                                                                                              
    <updated>{3}</updated>                                                                                                                                                                                  
    <category term="" />                                                                                                                                                                                    
    <app:control>
    <app:draft>{4}</app:draft>                                                                                                                                                                              
    </app:control>                                                                                                                                                                                          
    </entry>
    """

    data = template.format(title, username, text, dtime, draft).encode()
    return data

def parse_text(file, charset):
    with open(file, encoding=charset) as f:
        obj = f.readlines()
        body  = ""
        for i, line in enumerate(obj):
            body = body + line
    return body

def check_encoding(file):
    detector = UniversalDetector()
    with open(file, mode='rb') as f:
        for binary in f:
            detector.feed(binary)
            if detector.done:
                break
    detector.close()
    charset = detector.result['encoding']
    return charset

def post_hatena(data, headers):
    url = 'http://blog.hatena.ne.jp/{0}/{1}/atom/entry'.format(username, blogname)
    r = requests.post(url, data=data, headers=headers)

    if r.status_code != 201:
        sys.stderr.write('Error!\n' + 'status_code: ' + str(r.status_code) + '\n' + 'message: ' + r.text)


# hatena foto life                                                                                                                                                                                          
def create_data_foto(filename):
    files = Path(filename).read_bytes()
    root,ext=os.path.splitext(filename)
    ext = ext[1:]

    uploadData = b64encode(files)

    template="""                                                                                                                                                                                            
    <entry xmlns="http://purl.org/atom/ns#">                                                                                                                                                                
    <title>{0}</title>                                                                                                                                                                                      
    <content mode="base64" type="image/{1}">{2}</content>                                                                                                                                                   
    </entry>                                                                                                                                                                                                
    """

    return template.format(filename,ext,uploadData.decode())

def upload_foto(data, headers):
    url = 'http://f.hatena.ne.jp/atom/post/'
    r = requests.post(url, data=data, headers=headers)

    if r.status_code != 201:
        sys.stderr.write('Error!\n' + 'status_code: ' + str(r.status_code) + '\n' + 'message: ' + r.text)

def foto_info(headers):
    url = 'http://f.hatena.ne.jp/atom/feed/'
    r = requests.post(url, headers=headers)

    return re.search('[0-9]{14}', r.text).group()

# main -----------------------------------------------------------                                                                                                                                          
def main():

    # define WSSE header                                                                                                                                                                                    
    headers = {'X-WSSE': wsse(username, api_key)}

    # upload photo to HatenaFotoLife                                                                                                                                                                        
    filelist = glob.glob('/home/pi/log_condition/log_pic/*')
    filename = '/home/pi/log_condition/log_pic/{0:%Y%m%d}12.png'.format(now)
    print(filename)
    fotoflag = 0
    if filename in filelist:
        print('Photo uploading...')
        data_foto = create_data_foto(filename)
        upload_foto(data_foto, headers)
        fotoflag = 1
        sleep(10)
    else:
        print('Photo data is nothing')
        fotoflag = 0

    # confirm infomation of uploaded pyhoto                                                                                                                                                                 
    if fotoflag == 1:
        print('---- Uploaded foto info ----')
        fotoname = foto_info(headers)
        print(fotoname)
    else:
        fotoname = None

    # post blog to HatenaBlog                                                                                                                                                                               
    filelist_log = glob.glob('/home/pi/log_condition/log_TH/*')
    filename_log = '/home/pi/log_condition/log_TH/{0:%Y%m%d}.txt'.format(now)
    print(filename_log)
    if filename_log in filelist_log:
        print('blog uploading...')
        charset = check_encoding(filename_log)
        title = '今日の観察'
        body = parse_text(filename_log, charset)
        data_blog = create_data_blog(title, body, fotoname)
    else:
        print("log file is nothing")
        return 0

    post_hatena(data_blog, headers)
    print('Done')

if __name__ == '__main__':
    main()

main関数を読むと、先程上で記述した順番で動かそうとしていることがわかるかと思います。 APIキーは、はてなブログの[設定]→[詳細設定]から見ることができます。 f:id:shut9:20180823012340p:plain

このプログラムを使えば、こんな感じでブログにアップロードされます。 f:id:shut9:20180823005920p:plain

あとはcronに書き込んで、定期投稿させるようにして完成です。
1日のおわり(23:50にしました)に記事を投稿させるようにしています。

$ crontab -e

50 23 * * * /home/pi/.pyenv/shims/python /home/pi/posthatena_python/hatenablog_post_v7.py

python絶対パスで/home/pi/.pyenv/shims/pythonと書いておかないと動きません。
実際には/home/pi/.pyenv/shims/pythonの部分には

$ which python

としたときに書かれている文字列を書く必要がありますが、pyenv環境下で普段実行していれば多分これでよいと思います。

その他

自動投稿の準備まではすでに出来ているのですが、部屋が狭いので置く場所がいまいち決まらなかったり、Raspberry Piケースを削ったりなど、細かい部分の調整があってまだ観察日記が始まっていません。
今のところ、一番致命的な問題点は、部屋が暗いと写真を撮影しても見えづらいことです。(写真撮影のフラッシュ用にPower LEDを付けたいと思っています。)
早く完成させないとアボカドが少しずつ成長しています………