I2Cで繋がる BME280 環境センサを使った、 Raspberry Pi による環境計測とデータ送信の仕組みを Python2 で組んでから数年。当時、Pythonほぼ初めての自分が作ったスクリプトを試行錯誤しながら Python3 対応してみました。
現在の環境を確認
環境センサBME280や照度センサBH1750とRaspberry Piを組み合わせたシステムをこれまで何度か記事にしてきましたが、その中で実際に動くプログラムはPythonで記述されています。
そのプログラムをPython3へ対応させるに当たり、検証に使うのは初代Raspberry PiにBME280とBH1750を2つずつ繋いだ、こちらのシステムです。
OSは次の通りで、Debian BusterベースのRaspberry Pi OSが入っています。
1 2 3 4 5 6 7 8 |
$ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 10 (buster) Release: 10 Codename: buster $ uname -a Linux pi1 5.10.63+ #1488 Thu Nov 18 16:14:04 GMT 2021 armv6l GNU/Linux |
このシステムには既に2つの異なるバージョンのPythonが同居していますが、デフォルトはPython2.7です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$ python --version Python 2.7.16 $ python3 --version Python 3.7.3 $ ls -l /usr/bin/python* lrwxrwxrwx 1 root root 7 Mar 4 2019 /usr/bin/python -> python2 lrwxrwxrwx 1 root root 9 Mar 4 2019 /usr/bin/python2 -> python2.7 -rwxr-xr-x 1 root root 2984816 Oct 11 2019 /usr/bin/python2.7 lrwxrwxrwx 1 root root 36 Oct 11 2019 /usr/bin/python2.7-config -> arm-linux-gnueabihf-python2.7-config lrwxrwxrwx 1 root root 16 Mar 4 2019 /usr/bin/python2-config -> python2.7-config lrwxrwxrwx 1 root root 9 Mar 26 2019 /usr/bin/python3 -> python3.7 -rwxr-xr-x 2 root root 4275580 Jan 23 2021 /usr/bin/python3.7 lrwxrwxrwx 1 root root 36 Jan 23 2021 /usr/bin/python3.7-config -> arm-linux-gnueabihf-python3.7-config -rwxr-xr-x 2 root root 4275580 Jan 23 2021 /usr/bin/python3.7m lrwxrwxrwx 1 root root 37 Jan 23 2021 /usr/bin/python3.7m-config -> arm-linux-gnueabihf-python3.7m-config lrwxrwxrwx 1 root root 16 Mar 26 2019 /usr/bin/python3-config -> python3.7-config lrwxrwxrwx 1 root root 10 Mar 26 2019 /usr/bin/python3m -> python3.7m lrwxrwxrwx 1 root root 17 Mar 26 2019 /usr/bin/python3m-config -> python3.7m-config -rwxr-xr-x 1 root root 1797 Jul 12 2017 /usr/bin/python3-unidiff lrwxrwxrwx 1 root root 14 Mar 4 2019 /usr/bin/python-config -> python2-config |
デフォルトをPython3へ
これら2つのPythonを維持したまま、そのデフォルトをPython3系に変えるのに、 update-alternatives コマンドを使います。まずこのコマンド初回起動では、以下のように未だ設定が無いことを指摘されますが、これは正常動作。
1 2 |
$ sudo update-alternatives --config python update-alternatives: error: no alternatives for python |
次に2つのPythonを次のように登録します。
1 2 3 4 5 |
$ sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1 update-alternatives: using /usr/bin/python2 to provide /usr/bin/python (python) in auto mode $ sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 2 update-alternatives: using /usr/bin/python3 to provide /usr/bin/python (python) in auto mode |
そして始めのコマンドをもう一度実行すると、2つのPythonに設定された優先順位が表示されます。
1 2 3 4 5 6 7 8 |
$ sudo update-alternatives --config python There are 2 choices for the alternative python (providing /usr/bin/python). Selection Path Priority Status ------------------------------------------------------------ * 0 /usr/bin/python3 2 auto mode 1 /usr/bin/python2 1 manual mode 2 /usr/bin/python3 2 manual mode Press <enter> to keep the current choice[*], or type selection number: |
上記の自動設定で問題が無ければ(Python3がデフォルト)Enterキーのみ、Python2をデフォルトにしたい場合はここでその選択番号1を入力します。
以上で、Python3がPythonのデフォルトになりました。
1 2 3 4 |
$ python --version Python 3.7.3 $ python2 --version Python 2.7.16 |
pipはどうなっている?
今後に備えて、pip, pip2, pip3がこのシステムではどうなっているのか、確認してみました。
1 2 3 4 5 6 7 8 9 10 11 |
$ pip3 --version pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7) $ pip --version pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7) $ pip2 --version pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7) $ ls -l /usr/bin/pip* -rwxr-xr-x 1 root root 302 Apr 11 2019 /usr/bin/pip -rwxr-xr-x 1 root root 302 Apr 11 2019 /usr/bin/pip2 -rwxr-xr-x 1 root root 303 Apr 11 2019 /usr/bin/pip3 |
これまでpipとpip3をなんとなく使い分けていましたが、この環境ではどれでも同じでした。
Python2版環境センサ読取りスクリプト
Raspberry PiのI2Cインターフェイスにぶら下がった、BME280(温湿度気圧)とBH1750(照度)から計測値を読取り、ThingSpeakへ送信する環境ステーションで稼働しているのが、次のPython2ベースのスクリプトです(2種のセンサはそれぞれデバイスアドレスを違えた2個ずつの計4つの冗長構成)。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
import sys, os, json, requests, time, smbus sys.path.append(os.getcwd() + "/Adafruit_Python_BME280") from Adafruit_BME280 import * ## DEVICE KEY ts_key = "################" ## ALTITUDE altitude = 75 data1 = {'Temp':'0', 'Humid':'0', 'PressRaw':'0', 'PressZero':'0', 'Lux':'0'} data2 = {'Temp':'0', 'Humid':'0', 'PressRaw':'0', 'PressZero':'0', 'Lux':'0'} def setDatum(sen, dat): dat['Temp'] = sen.read_temperature() dat['Humid'] = sen.read_humidity() dat['PressRaw'] = sen.read_pressure() / 100 if dat['Temp'] != 0 and dat['PressRaw'] != 0: dat['PressZero'] = dat['PressRaw'] + (dat['PressRaw'] * 9.81 * altitude) / (287 * (273.15 + dat['Temp'])) else: dat['PressZero'] = 0 def get_CPUtemp(): f = open("/sys/class/thermal/thermal_zone0/temp","r") tmp = 0 for t in f: tmp = t[:2]+"."+t[2:5] f.close() return float(tmp) def get_Lux(addr): bus = smbus.SMBus(1) lux = bus.read_i2c_block_data(addr,0x10) return (lux[0]*256+lux[1])/1.2 class ThingSpeak: def __init__(self,apiKey): self.apiKey = apiKey def regist(self): request = "field1=" + str(round(data1['Temp'],2)) + \ "&field2=" + str(round(data2['Temp'],2)) + \ "&field3=" + str(round(data1['Humid'],2)) + \ "&field4=" + str(round(data2['Humid'],2)) + \ "&field5=" + str(round(data1['PressZero'],2)) + \ "&field6=" + str(round(data2['PressZero'],2)) + \ "&field7=" + str(round(data1['Lux'],2)) + \ "&field8=" + str(round(data2['Lux'],2)) # "&field8=" + str(round(cputemp,2)) url = "https://api.thingspeak.com/update?key=" + self.apiKey headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} res = requests.post(url, headers=headers, data=request) print res return res ## MAIN ## #BME280 try: sensor1 = BME280(t_mode=BME280_OSAMPLE_8, p_mode=BME280_OSAMPLE_8, h_mode=BME280_OSAMPLE_8, address=0x76) setDatum(sensor1, data1) except: print "BME280#1 Failure" data1['Temp'] = data1['Humid'] = data1['PressRaw'] = data1['PressZero'] = 0 time.sleep(1) try: sensor2 = BME280(t_mode=BME280_OSAMPLE_8, p_mode=BME280_OSAMPLE_8, h_mode=BME280_OSAMPLE_8, address=0x77) setDatum(sensor2, data2) except: print "BME280#2 Failure" data2['Temp'] = data2['Humid'] = data2['PressRaw'] = data2['PressZero'] = 0 #BH1750 time.sleep(1) try: data1['Lux'] = get_Lux(addr=0x23) except: print "BH1750#1 Failure" data1['Lux'] = 0 time.sleep(1) try: data2['Lux'] = get_Lux(addr=0x5c) except: print "BH1750#2 Failure" data2['Lux'] = 0 #CPU_TEMP cputemp = get_CPUtemp() print '-- Data --' print 'CPU Temp = {0:0.2f} deg C'.format(cputemp) print 'Illumi1 = {0:0.2f} lux'.format(data1['Lux']) print 'Illumi2 = {0:0.2f} lux'.format(data2['Lux']) print 'Temp1 = {0:0.2f} deg C'.format(data1['Temp']) print 'Temp2 = {0:0.2f} deg C'.format(data2['Temp']) print 'Humidity1 = {0:0.2f} %'.format(data1['Humid']) print 'Humidity2 = {0:0.2f} %'.format(data2['Humid']) print 'Pressure1 = {0:0.2f} hPa'.format(data1['PressRaw']) print 'Pressure2 = {0:0.2f} hPa'.format(data2['PressRaw']) print 'PressureZ1 = {0:0.2f} hPa'.format(data1['PressZero']) print 'PressureZ2 = {0:0.2f} hPa'.format(data2['PressZero']) print '-- ThingSpeak --' thingDevice = ThingSpeak(ts_key) thingDevice.regist() |
このスクリプトがPython3で正常動作するよう、デバッグを進めます。
デバッグ (1) print構文
まず怒られるのはprint構文ですが、エラーメッセージの中で正しい記述法まで提示してもらえるのですぐ気づきます。
1 2 3 4 5 |
$ python ./sensors_upload.py File "sensors_upload.py", line 69 print res ^ SyntaxError: Missing parentheses in call to 'print'. Did you mean print(res)? |
デバッグ (2) smbus
基本的な構文ミスに続いては、インポートモジュール関連のエラーが挙がります。このsmbusはBH1750照度センサとの通信に必要なライブラリです。
1 2 3 4 5 |
$ python ~/sensors_upload.py Traceback (most recent call last): File "/home/pi/sensors_upload.py", line 11, in <module> import sys, os, json, requests, time, smbus ModuleNotFoundError: No module named 'smbus' |
これはPython3用のsmbusライブラリを apt パッケージマネージャから以下の要領でインストールすると解決します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ sudo apt-get install python3-smbus Reading package lists... Done Building dependency tree Reading state information... Done The following NEW packages will be installed: python3-smbus 0 upgraded, 1 newly installed, 0 to remove and 15 not upgraded. Need to get 11.7 kB of archives. After this operation, 51.2 kB of additional disk space will be used. Get:1 http://mirror.rise.ph/raspbian/raspbian buster/main armhf python3-smbus armhf 4.1-1 [11.7 kB] Fetched 11.7 kB in 2s (6,351 B/s) Selecting previously unselected package python3-smbus:armhf. (Reading database ... 75890 files and directories currently installed.) Preparing to unpack .../python3-smbus_4.1-1_armhf.deb ... Unpacking python3-smbus:armhf (4.1-1) ... Setting up python3-smbus:armhf (4.1-1) ... |
使い方はPython2の頃と互換なので、インポートさえ出来てしまえば、旧来のスクリプトをそのまま使えました。
デバッグ (3) Adafruit_Python_BME280
数年ぶりにAdafruit Python BME280のGitHubページを開いてみると、既にライブラリは非推奨とされてメンテナンスも終了していて、
開発はAdafruit CircuitPython BME280ライブラリへ移行されていました。
Adafruit_Python_BME280ライブラリの導入では、依存するAdafruit_Python_GPIOライブラリも共に git clone する必要があったりと手間が多いものでしたが、新しいAdafruit_CircuitPython_BME280ライブラリは、 pip3 から簡単にインストール可能です。
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 32 33 34 35 |
$ pip3 install adafruit-circuitpython-bme280 Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple Collecting adafruit-circuitpython-bme280 Downloading https://www.piwheels.org/simple/adafruit-circuitpython-bme280/adafruit_circuitpython_bme280-2.6.8-py3-none-any.whl Collecting adafruit-circuitpython-busdevice (from adafruit-circuitpython-bme280) Downloading https://www.piwheels.org/simple/adafruit-circuitpython-busdevice/adafruit_circuitpython_busdevice-5.1.1-py3-none-any.whl Collecting Adafruit-Blinka (from adafruit-circuitpython-bme280) Downloading https://www.piwheels.org/simple/adafruit-blinka/Adafruit_Blinka-6.20.1-py3-none-any.whl (194kB) 100% |████████████████████████████████| 204kB 137kB/s Collecting Adafruit-PureIO>=1.1.7 (from Adafruit-Blinka->adafruit-circuitpython-bme280) Downloading https://www.piwheels.org/simple/adafruit-pureio/Adafruit_PureIO-1.1.9-py3-none-any.whl Collecting rpi-ws281x>=4.0.0 (from Adafruit-Blinka->adafruit-circuitpython-bme280) Downloading https://www.piwheels.org/simple/rpi-ws281x/rpi_ws281x-4.3.1-cp37-cp37m-linux_armv6l.whl (116kB) 100% |████████████████████████████████| 122kB 103kB/s Collecting RPi.GPIO (from Adafruit-Blinka->adafruit-circuitpython-bme280) Downloading https://www.piwheels.org/simple/rpi-gpio/RPi.GPIO-0.7.0-cp37-cp37m-linux_armv6l.whl (69kB) 100% |████████████████████████████████| 71kB 110kB/s Collecting Adafruit-PlatformDetect>=3.13.0 (from Adafruit-Blinka->adafruit-circuitpython-bme280) Downloading https://www.piwheels.org/simple/adafruit-platformdetect/Adafruit_PlatformDetect-3.19.3-py3-none-any.whl Collecting sysv-ipc>=1.1.0 (from Adafruit-Blinka->adafruit-circuitpython-bme280) Downloading https://www.piwheels.org/simple/sysv-ipc/sysv_ipc-1.1.0-cp37-cp37m-linux_armv6l.whl (68kB) 100% |████████████████████████████████| 71kB 115kB/s Collecting pyftdi>=0.40.0 (from Adafruit-Blinka->adafruit-circuitpython-bme280) Downloading https://files.pythonhosted.org/packages/e8/42/74fe01fe7533a58808b9f2a9a112b22ee39849c3ac3526f4f6e16ef56fb4/pyftdi-0.53.3-py3-none-any.whl (141kB) 100% |████████████████████████████████| 143kB 180kB/s Collecting pyserial>=3.0 (from pyftdi>=0.40.0->Adafruit-Blinka->adafruit-circuitpython-bme280) Downloading https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl (90kB) 100% |████████████████████████████████| 92kB 213kB/s Collecting pyusb!=1.2.0,>=1.0.0 (from pyftdi>=0.40.0->Adafruit-Blinka->adafruit-circuitpython-bme280) Downloading https://files.pythonhosted.org/packages/15/a8/4982498b2ab44d1fcd5c49f07ea3795eab01601dc143b009d333fcace3b9/pyusb-1.2.1-py3-none-any.whl (58kB) 100% |████████████████████████████████| 61kB 151kB/s Installing collected packages: Adafruit-PureIO, rpi-ws281x, RPi.GPIO, Adafruit-PlatformDetect, sysv-ipc, pyserial, pyusb, pyftdi, Adafruit-Blinka, adafruit-circuitpython-busdevice, adafruit-circuitpython-bme280 The scripts pyserial-miniterm and pyserial-ports are installed in '~/.local/bin' which is not on PATH. Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location. Successfully installed Adafruit-Blinka-6.20.1 Adafruit-PlatformDetect-3.19.3 Adafruit-PureIO-1.1.9 RPi.GPIO-0.7.0 adafruit-circuitpython-bme280-2.6.8 adafruit-circuitpython-busdevice-5.1.1 pyftdi-0.53.3 pyserial-3.5 pyusb-1.2.1 rpi-ws281x-4.3.1 sysv-ipc-1.1.0 |
上記インストールの終了メッセージに於いて、パスのことをアドバイスされますが、結果的にはパスを通さなくとも動作しました。もしうまく呼び出せないようであれば、 ~/.profile の下方に次のスクリプトを追記すると良いでしょう(編集後、次回新セッションより反映)。
1 2 3 |
if [ -d "$HOME/.local/bin" ] ; then PATH="$HOME/.local/bin:$PATH" fi |
ライブラリの呼び出し方に使い方に関しては、公式ドキュメントが用意されています。
今回はスクリプトでは、おおよそ次のように変更すれば良さそうです。新ライブラリでもデバイスアドレスを指定して呼び出すことも出来るので一安心。また、公式ドキュメントでは海抜ゼロでの気圧を定数として与えて海抜を算出していますが、これを逆にしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# インポート from adafruit_bme280 import basic as adafruit_bme280 # オブジェクト生成 i2c = board.I2C() sensor1 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76) sensor2 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77) # 読み出し dat['Temp'] = sen.temperature dat['Humid'] = sen.relative_humidity dat['PressRaw'] = sen.pressure dat['PressZero'] = dat['PressRaw'] + (dat['PressRaw'] * 9.81 * altitude) / (287 * (273.15 + dat['Temp'])) |
Python3版環境センサ読取りスクリプト完成
以上のデバッグを経て、Python3で動作するスクリプトは出来上がりました。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
import json, requests, time, smbus, board from adafruit_bme280 import basic as adafruit_bme280 ## DEVICE KEY ts_key = "################" ## ALTITUDE altitude = 75 data1 = {'Temp':'0', 'Humid':'0', 'PressRaw':'0', 'PressZero':'0', 'Lux':'0'} data2 = {'Temp':'0', 'Humid':'0', 'PressRaw':'0', 'PressZero':'0', 'Lux':'0'} def setDatum(sen, dat): dat['Temp'] = sen.temperature dat['Humid'] = sen.relative_humidity dat['PressRaw'] = sen.pressure if dat['Temp'] != 0 and dat['PressRaw'] != 0: dat['PressZero'] = dat['PressRaw'] + (dat['PressRaw'] * 9.81 * altitude) / (287 * (273.15 + dat['Temp'])) else: dat['PressZero'] = 0 def get_CPUtemp(): f = open("/sys/class/thermal/thermal_zone0/temp","r") tmp = 0 for t in f: tmp = t[:2]+"."+t[2:5] f.close() return float(tmp) def get_Lux(addr): bus = smbus.SMBus(1) lux = bus.read_i2c_block_data(addr,0x10) return (lux[0]*256+lux[1])/1.2 class ThingSpeak: def __init__(self,apiKey): self.apiKey = apiKey def regist(self): request = "field1=" + str(round(data1['Temp'],2)) + \ "&field2=" + str(round(data2['Temp'],2)) + \ "&field3=" + str(round(data1['Humid'],2)) + \ "&field4=" + str(round(data2['Humid'],2)) + \ "&field5=" + str(round(data1['PressZero'],2)) + \ "&field6=" + str(round(data2['PressZero'],2)) + \ "&field7=" + str(round(data1['Lux'],2)) + \ "&field8=" + str(round(data2['Lux'],2)) # "&field8=" + str(round(cputemp,2)) url = "https://api.thingspeak.com/update?key=" + self.apiKey headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} res = requests.post(url, headers=headers, data=request) print (res) return res ## MAIN ## #BME280 i2c = board.I2C() try: sensor1 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76) setDatum(sensor1, data1) except: print ("BME280#1 Failure") data1['Temp'] = data1['Humid'] = data1['PressRaw'] = data1['PressZero'] = 0 time.sleep(1) try: sensor2 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77) setDatum(sensor2, data2) except: print ("BME280#2 Failure") data2['Temp'] = data2['Humid'] = data2['PressRaw'] = data2['PressZero'] = 0 #BH1750 time.sleep(1) try: data1['Lux'] = get_Lux(addr=0x23) except: print ("BH1750#1 Failure") data1['Lux'] = 0 time.sleep(1) try: data2['Lux'] = get_Lux(addr=0x5c) except: print ("BH1750#2 Failure") data2['Lux'] = 0 #CPU_TEMP cputemp = get_CPUtemp() print ('-- Data --') print ('CPU Temp = {0:0.2f} deg C'.format(cputemp)) print ('Illumi1 = {0:0.2f} lux'.format(data1['Lux'])) print ('Illumi2 = {0:0.2f} lux'.format(data2['Lux'])) print ('Temp1 = {0:0.2f} deg C'.format(data1['Temp'])) print ('Temp2 = {0:0.2f} deg C'.format(data2['Temp'])) print ('Humidity1 = {0:0.2f} %'.format(data1['Humid'])) print ('Humidity2 = {0:0.2f} %'.format(data2['Humid'])) print ('Pressure1 = {0:0.2f} hPa'.format(data1['PressRaw'])) print ('Pressure2 = {0:0.2f} hPa'.format(data2['PressRaw'])) print ('PressureZ1 = {0:0.2f} hPa'.format(data1['PressZero'])) print ('PressureZ2 = {0:0.2f} hPa'.format(data2['PressZero'])) print ('-- ThingSpeak --') thingDevice = ThingSpeak(ts_key) thingDevice.regist() |
実行結果は次のようになり、ThingSpeakへもこれまで通りデータを送信してくれました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ python ~/sensors_upload.py -- Data -- CPU Temp = 51.38 deg C Illumi1 = 190.83 lux Illumi2 = 211.67 lux Temp1 = 23.96 deg C Temp2 = 24.22 deg C Humidity1 = 53.74 % Humidity2 = 53.07 % Pressure1 = 1003.27 hPa Pressure2 = 1004.01 hPa PressureZ1 = 1011.93 hPa PressureZ2 = 1012.66 hPa -- ThingSpeak -- <Response [200]> |
vnstatレポートメール送信スクリプトのPython3対応
このRaspberry Piには、 vnstat から毎朝ネットワークインターフェイスの集計レポートをメール送信するしくみもあって、このスクリプトも次のようにPythonで組んでいます。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import sys, os, smtplib, shutil from email.MIMEMultipart import MIMEMultipart from email.MIMEBase import MIMEBase from email.MIMEText import MIMEText from email.Utils import formatdate from email import Encoders def send_mail(send_from, send_to, subject, body, path_att): cset = 'utf-8' msg = MIMEMultipart() msg['From'] = send_from msg['To'] = send_to msg['Date'] = formatdate(localtime=True) msg['Subject'] = subject if body != '': msg.attach( MIMEText(body, 'plain', cset) ) files = os.listdir(path_att) i = 1 for f in files: part = MIMEBase('application', "octet-stream") part.set_payload( open(path_att + f,"rb").read() ) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(path_att + f)) part.add_header('Content-ID', '<image' + str(i) + '>') msg.attach(part) body = body + '<img src="cid:image' + str(i) + '"><br />' i += 1 msg.attach( MIMEText(body, 'html', cset) ) ## SMTP TO ISP smtp = smtplib.SMTP('smtp.######.###.###') ## SMTP TO GMAIL #smtp = smtplib.SMTP('smtp.gmail.com', 587) #smtp.ehlo() #smtp.starttls() #smtp.ehlo() #smtp.login('#######@#####.###', '############') smtp.sendmail(send_from, send_to, msg.as_string()) smtp.close() if __name__ == '__main__': path = '$HOME/vnstat/' shutil.rmtree(path, ignore_errors=True) if not os.path.exists(path): os.makedirs(path) os.system('vnstati -d -o ' + path + 'daily.png') os.system('vnstati -vs -o ' + path + 'hsum.png') subj = os.uname()[1] + ': Daily Traffic Report' txt = '' send_mail('###@######.###', '###@######.###.###', subj, txt, path) |
仕組みはシンプルなのですが、利用しているemailライブラリの構成がPython3では少し異なるようです。逆に言うとライブラリのインポート以外の修正は必要ないので、スクリプトの冒頭を以下のようにPythonのバージョンを見てインポート方法を切り替えることで、汎用的に使えるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import sys, os, smtplib if sys.version_info.major == 2: # For Python2 from email.MIMEMultipart import MIMEMultipart from email.MIMEBase import MIMEBase from email.MIMEText import MIMEText from email.Utils import formatdate from email import Encoders elif sys.version_info.major >= 3: # For Python3 from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email.mime.text import MIMEText from email.utils import formatdate from email import encoders as Encoders |
実行すると次のようなメールが発出されます。