ラズパイとcgiで、温度・湿度・気圧を測ってウェブ上で表示させる

【使用環境　ラズパイ3B+model　　使用言語 python】

ラズパイを使って温度と湿度を測定してラズパイ上で表示させるところまでは、ネットに多く乗っているのですが、cgiを使用してブラウザ上で温度・湿度を表示させる方法がなかなか見つからなかったので、投稿してみます。

ラズパイで温度と湿度を表示させるところまでは、以下のURLを参考にして進めてみてください。
「ラズペリーパイで温度・湿度・気圧をまとめて取得！AE-BME280でI2C通信」
https://deviceplus.jp/hobby/raspberrypi_entry_039/
ここのI2C通信が後で重要なので、頭の片隅において置くといいと思います。
（環境によっては上のURLでもうまく行かないところが出てくるかもしれないので自分が躓いた部分を付け加えます。
ダウンロードしたpythonのコードをそのまま実行しようとしてもうまくできませんでした。smbus2のところをsmbusに書き換えてやるか、smbus2をpipインストールしてやると解決すると思います。また、ラズパイには、python2とpython3が入っているため、どっちにsmbusが入っているかなども考える必要があると思います。私は、smbusに書き換えて行いました。）

#cgiの設定について
cgiというのは、簡単に言うとwebサーバーがプログラムを実行する仕組みのことで、webブラウザでメールフォームを作ったりするときにも使われたりします。

ラズパイでcgiを使えるようにするのは、以下のURLを参考にしてみてください。apache2のインストールからできます。
「ラズパイをwebサーバにしてcgiを実行する方法メモ」
http://www.binvar.com/entry/pi_cgi_setting
このURLの4番は、公開する必要がないならやらなくていいと思います。私はやりませんでした。
2番のところで.pyにも権限を与えてやってください。コメントアウトを外した後に、.pyと付け加えてください。
cgiで実行したいファイルは、/usr/lib/cgi-bin/に入れておくといいと思います。
付け加えると2番の

```
sudo vi /etc/apache2/mods-available/mine.conf
```
については、mineをmimeにしてください。


##cgiでpythonプログラムを実行させ、webブラウザ上で結果を見る。
いよいよ本題に入っていきたいと思います。上のcgi設定まではそこまで苦しまずに設定することができたのですが、ここからがネットにあまり乗っておらずスムーズにはできませんでした。順を追って説明して行きたいと思います。
/usr/lib/cgi-bin/に先程のpythonコードを入れてやり、webブラウザで確認したところ（webで確認するときは、URLにhttp://自分のipアドレス/cgi-bin/ファイル名で見ることが可能です。）以下の画像のようなエラーが出てきました。
![Screenshot_20191001_150323.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506388/fd8c2ddd-5dbc-b172-9d1d-e6feadc0e25f.png)

このエラーが出てきたときに考えられることが、
1.　pythonのコードを間違えている
2.　パーミッション（権限）が与えられていない
の2種類考えられます。
パーミッションに関しては、以下のリンクを参考になさってください。
「Linuxの権限確認と変更（chmod)（超初心者向け）」
https://qiita.com/shisama/items/5f4c4fa768642aad9e06
cgiを使用するときには、実行権限が必要となります。cgiのファイルの権限を変える必要があります。ファイルの権限の状態を確認するときは、確認したいファイルのディレクトリまで移動して、以下のコマンドを入力してください。

```
ls -al
```
また、今回の場合は使いたいpythonファイルに以下のように権限を与えました。

```
sudo chmod 755 /usr/lib/cgi-bin/ファイル名
```
これでも画像のようなエラーが出てくるときは、pythonのコードが間違っている可能性があります。
※cgiで使用するpythonファイルには、最初に以下のコードをつけないとエラーとなる可能性があります。加えて、cgiでは、入れ子構造がだめみたいなので、コードを少し改変してprintするところを、計算結果だけreturnしてreadDataのところでprintを用いないとだめみたいです。最後にコードを載せておきますので参考にしてみてください。

```Python:Code.py
#!/usr/bin/python3
print('Content-type: text/html; charset=UTF-8\r\n')
```


これらでエラーが解消されたと思ったら次は、温度・湿度・気圧がブラウザ上で表示されないというエラーが出ました。これは、エラーメッセージすら出てこないので何が悪いのかすらわかりません。そこで、エラーメッセージを表示させるように、モジュールをimportして見たいと思います。
pythonのコードに

```Python:Code.py
import cgitb
cgitb.enable()
```
と付け加えてみると、下の画像のようになりました。
![DSC_0050.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506388/13d62251-3c1a-69fe-d55c-a95036cf8858.jpeg)
smbusの権限が与えられていなことがわかりました。
ここで気にしなければならないのが、cgiを使うとどのユーザーでプログラムを実行しているのかということです。上記のcgiの仕組みを考えると、webサーバーのユーザーであると考えられます。なら今のwebサーバー(apache2)のユーザーは一体誰なのかを確認したいと思います。以下のコマンドで確認できます。

```
sudo nano /etc/apache2/envvars
```
このコマンドでapache2のユーザーを見ることができます。
中にこのような記載があったと思います。

```
export APACHE_RUN_USER=www-data
export APACHE_RUN_GROUP=www-data
```
これによりapache2のユーザーはwww-dataであることがわかりました。
ここで安直に、権限が面倒ならすべてroot権限で実行させるようにできてしまえば、楽なのでは無いかと考え、root権限でやる方法を探しましたが、なかなかに難しく、どうしたらよいかと考えましたが、本質はそこではありませんでした。本質は、I2C通信の方にあったのです。smbusを使用するには、I2Cを使用する権限が必要でした。ラズパイでただ表示させるときは、ユーザーがI2Cの権限を持っていて、www-dataは権限持っていなかったと考えられます。確認してみます。

```
groups ユーザー名
```
これで確認することができます。www-dataを確認してみると、I2Cのグループに入っていませんでした。そこでwww-dataもI2Cのグループに入れて見たいと思います。

```
sudo gpasswd -a www-data i2c
```
これでwww-dataがi2cのグループに入りました。これで権限の問題が解消されました。webブラウザで確認してみると、しっかりと表示されました。
![Screenshot_20191001_150652.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506388/e41002cc-b8a7-d6b0-7479-29f95e4cf3a9.png)

###最後に
最後に、しっかりと表示させることができたpythonのコードを載せておきます。単純なコピペだと何故か表示されないみたいなこともありましたが、基本的にはできると思います。

```Python:bme280_cgi.py
#!/usr/bin/python3
print('Content-type: text/html; charset=UTF-8\r\n')
import smbus
import math
from time import sleep
import cgitb
cgitb.enable()

bus_number  = 1
i2c_address = 0x76

bus = smbus.SMBus(bus_number)

digT = []
digP = []
digH = []

t_fine = 0.0

def writeReg(reg_address, data):
        bus.write_byte_data(i2c_address,reg_address,data)

def get_calib_param():
        calib = []

        for i in range (0x88,0x88+24):
                calib.append(bus.read_byte_data(i2c_address,i))
        calib.append(bus.read_byte_data(i2c_address,0xA1))
        for i in range (0xE1,0xE1+7):
               calib.append(bus.read_byte_data(i2c_address,i))

        digT.append((calib[1] << 8) | calib[0])
        digT.append((calib[3] << 8) | calib[2])
        digT.append((calib[5] << 8) | calib[4])
        digP.append((calib[7] << 8) | calib[6])
        digP.append((calib[9] << 8) | calib[8])
        digP.append((calib[11]<< 8) | calib[10])
        digP.append((calib[13]<< 8) | calib[12])
        digP.append((calib[15]<< 8) | calib[14])
        digP.append((calib[17]<< 8) | calib[16])
        digP.append((calib[19]<< 8) | calib[18])
        digP.append((calib[21]<< 8) | calib[20])
        digP.append((calib[23]<< 8) | calib[22])
        digH.append( calib[24] )
        digH.append((calib[26]<< 8) | calib[25])
        digH.append( calib[27] )
        digH.append((calib[28]<< 4) | (0x0F & calib[29]))
        digH.append((calib[30]<< 4) | ((calib[29] >> 4) & 0x0F))
        digH.append( calib[31] )

        for i in range(1,2):
                if digT[i] & 0x8000:
                        digT[i] = (-digT[i] ^ 0xFFFF) + 1

        for i in range(1,8):
                if digP[i] & 0x8000:
                        digP[i] = (-digP[i] ^ 0xFFFF) + 1

        for i in range(0,6):
                if digH[i] & 0x8000:
                        digH[i] = (-digH[i] ^ 0xFFFF) + 1  

def readData():
        data = []
        for i in range (0xF7, 0xF7+8):
                data.append(bus.read_byte_data(i2c_address,i))
        pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
        temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
        hum_raw  = (data[6] << 8)  |  data[7]


        temperature = compensate_T(temp_raw)
        pressure = compensate_P(pres_raw)
        humidity = compensate_H(hum_raw)

        print("temp : %-6.2f `C" % (temperature))
        print("<br>")
        print("pressure : %7.2f hPa" % (pressure))
        print("<br>")
        print("hum : %6.2f %%" % (humidity))

def compensate_P(adc_P):
        global  t_fine
        pressure = 0.0

        v1 = (t_fine / 2.0) - 64000.0
        v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5]
        v2 = v2 + ((v1 * digP[4]) * 2.0)
        v2 = (v2 / 4.0) + (digP[3] * 65536.0)
        v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8)  + ((digP[1]$
        v1 = ((32768 + v1) * digP[0]) / 32768

        if v1 == 0:
                return 0
        pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125
        if pressure < 0x80000000:
                pressure = (pressure * 2.0) / v1
        else:
                pressure = (pressure / v1) * 2
        v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096
        v2 = ((pressure / 4.0) * digP[7]) / 8192.0
        pressure = pressure + ((v1 + v2 + digP[6]) / 16.0)
        return pressure/100  
        #print("pressure : %7.2f hPa" % (pressure/100))

def compensate_T(adc_T):
        global t_fine
        v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1]
        v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0$
        t_fine = v1 + v2
        temperature = t_fine / 5120.0
        return temperature
        #print("temp : %-6.2f " % (temperature)) 
def compensate_H(adc_H):
        global t_fine
        var_h = t_fine - 76800.0
        if var_h != 0:
                var_h = (adc_H - (digH[3] * 64.0 + digH[4]/16384.0 * var_h)) * $
        else:
                return 0
        var_h = var_h * (1.0 - digH[0] * var_h / 524288.0)
        if var_h > 100.0:
                var_h = 100.0
        elif var_h < 0.0:
                var_h = 0.0
        return var_h
        #print("hum : %6.2f ％" % (var_h))

def setup():
        osrs_t = 1                      #Temperature oversampling x 1
        osrs_p = 1                      #Pressure oversampling x 1
        osrs_h = 1                      #Humidity oversampling x 1
        mode   = 3                      #Normal mode
        t_sb   = 5                      #Tstandby 1000ms
        filter = 0                      #Filter off
        spi3w_en = 0                    #3-wire SPI Disable

        ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode
        config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en
        ctrl_hum_reg  = osrs_h

        writeReg(0xF2,ctrl_hum_reg)
        writeReg(0xF4,ctrl_meas_reg)
        writeReg(0xF5,config_reg)


setup()
get_calib_param()


if __name__ == '__main__':
        try:
                readData()
        except KeyboardInterrupt:
                pi.stop()
```