0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonのBaseHTTPRequestHandlerを使用した温度センサ(BMP280)のデータの受け取りと管理

Last updated at Posted at 2024-11-22

1. はじめに

私が研究で用いたセンサのデータをJSON形式でHTTP POSTリクエストとして送信し, BaseHTTPRequestHandlerを使用してサーバで受信し管理した方法をまとめたものです.

BaseHTTPRequestHandler とは

Python の標準ライブラリ http.server モジュールに含まれているクラスで、HTTPリクエストを処理するための基本的なフレームワークを提供します。このクラスを拡張して独自のHTTPサーバーを構築することができます。

2. 構成

サーバーを開設した後, クライアントからhttpリクエストを送信します. サーバーはクライアントからのリクエストを正常に受け取り, 処理し, HTTPステータスコード200を含むレスポンスを返します. これにより, ESP32はリクエストが成功したことを確認できます.

サーバー構成:

● BaseHTTPRequestHandlerを使用
● 言語:Python ver.3.12.0
● Ubuntu 24.04 LTS

クライアント構成:

● マイクロコントローラ:ESP32
● 言語:MicroPython
● ファームウェア:ESP32_GENERIC-20240222-v1.22.2
●センサ:BMP280(高精度気圧・温度センサーモジュール)
#クライアントプログラムはMicroPythonで記述しているためPython環境では動作しない場合があります。

3. サーバープログラム

設定箇所
・SERVER_IP:扱うWi-Fiのアドレスを記入

from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import csv

# VMのIPアドレスとポート番号
VM_IP = ''  # VMの実際のIPアドレスに置き換える
VM_PORT = 8000

class DataHandler(BaseHTTPRequestHandler):
device_data = {}

def write_csv_header(self, csv_file, data):
    """CSVファイルにヘッダーを書き込む"""
    headers = []
    if 'temperature' in data:
        headers.append('Temperature (C)')
    if 'pressure' in data:
        headers.append('Pressure (Pa)')
    with open(csv_file, mode='w', newline='') as file:
        csv.writer(file).writerow(headers)

def append_csv_data(self, csv_file, data):
    """CSVファイルにデータを書き込む"""
    row = []
    if 'temperature' in data:
        row.append(data['temperature'])
    if 'pressure' in data:
        row.append(data['pressure'])
    with open(csv_file, mode='a', newline='') as file:
        csv.writer(file).writerow(row)

def do_POST(self):
    # リクエストデータを取得
    content_length = int(self.headers['Content-Length'])
    post_data = self.rfile.read(content_length)
    data = json.loads(post_data.decode('utf-8'))
    client_ip = self.client_address[0]

    # クライアントごとのCSVファイルを準備
    if client_ip not in self.device_data:
        csv_file = f"temperature_data_{client_ip.replace('.', '_')}.csv"
        self.write_csv_header(csv_file, data)
        self.device_data[client_ip] = csv_file
    csv_file = self.device_data[client_ip]

    # データをCSVに保存
    self.append_csv_data(csv_file, data)

    # 応答を返す
    self.send_response(200)
    self.end_headers()
    self.wfile.write("Data received and saved successfully".encode('utf-8'))

#HTTPサーバーの設定と起動
def run_server():
    server_address = (VM_IP, VM_PORT)
    httpd = HTTPServer(server_address, DataHandler)
    print(f'Starting HTTP server on {VM_IP}:{VM_PORT}...')
    httpd.serve_forever()

if __name__ == '__main__':
    run_server()

4. クライアントプログラム

設定箇所
・SSID:Wi-Fiの名前
・PASSWORD:Wi-Fiのパスワード
・SERVER_IP:Wi-FiのIPアドレス(VM環境であればホストネーム)
・i2c = machine.I2C…:適応するSCL, SDAのPin番号を記載(本稿ではSCL=22, SDA=21 で設定)

~補足~
下記の部分はESP32内臓LEDをwi-fi接続時に点灯を行い処理を可視化するために使用している
#ピン設定
led_blue = machine.Pin(2, Pin.OUT)

import network
import urequests
import machine
import time
import json
from machine import Pin, SoftI2C

# WiFi接続情報の読み込み
SSID = ""

PASSWORD = ""

#モジュールの読み込み
execfile(f"BMP280_Module.py")

# VMのIPアドレスとポート番号
VM_IP = ''  # VMの実際のIPアドレスに置き換える
VM_PORT = 8000

# BMP280センサの初期化
i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
init_bmp280(i2c)
count = 0

led_blue = machine.Pin(2, Pin.OUT)

# WiFiに接続する関数
def connect_wifi(ssid, passkey, timeout=10):
    wifi = network.WLAN(network.STA_IF)
    if wifi.isconnected():
        print('WiFiに接続済み。接続をスキップします。')
        return wifi
    else:
        wifi.active(True)
        wifi.connect(ssid, passkey)
        while not wifi.isconnected() and timeout > 0:
            print('.')
            time.sleep(1)
            timeout -= 1

    if wifi.isconnected():
        print('WiFiに接続しました')
        led_blue.value(1)
        return wifi
    else:
        print('WiFiへの接続に失敗しました')
        return None

# データをVMに送信する関数
def send_data_to_vm(temperature, pressure):
    url = f'http://{VM_IP}:{VM_PORT}'
    headers = {'Content-Type': 'application/json'}
    data = {
        "temperature": temperature,
        "pressure": pressure
    }
    response = urequests.post(url, headers=headers, data=json.dumps(data))
    if response.status_code == 200:
        #print('データの送信に成功しました')
        return
    else:
        print(f'データの送信に失敗しました: {response.status_code}')
    response.close()

# メインプログラムの実行部分
print('WiFiに接続します...')
wifi = connect_wifi(SSID, PASSWORD)

if wifi:
    while True:
        try:
            temp = str(read_temp(i2c))
        except:
            temp = "None"
        try:
            pres = str(read_pressure(i2c))
        except:
            pres = "None"
        
        print(f'count: {count+1}\n   Temperature: {temp} °C,\n   Pressure: {pres} Pa')
        try:
            send_data_to_vm(temp, pres)
        except Exception as e:
            print("送信失敗")
        time.sleep(5)  # 5秒間隔でデータを送信(適宜調整)

センサの配線図qiita.png

センサのモジュールコード

このコードはBMP280のホームページにあるデータシートのPDFに記載してあるC言語のコードをMicroPythonで動作するようにしたコードです
また、実験で扱うために『def read_temp』と『def read_pressure』で別々の関数で温度と気圧データを取得できるように変更を加えました
※使用する場合ファイル名をBMP280_Module.pyに設定してください。

import machine

#execfile("bmp280_module.py")

# BMP280のアドレス
BMP280_I2C_ADDRESS = 0x76

# BMP280のレジスタ
BMP280_REG_TEMP_XLSB = 0xFC
BMP280_REG_TEMP_LSB = 0xFB
BMP280_REG_TEMP_MSB = 0xFA
BMP280_REG_PRESS_XLSB = 0xF9
BMP280_REG_PRESS_LSB = 0xF8
BMP280_REG_PRESS_MSB = 0xF7
BMP280_REG_CONFIG = 0xF5
BMP280_REG_CTRL_MEAS = 0xF4
BMP280_REG_STATUS = 0xF3
BMP280_REG_RESET = 0xE0
BMP280_REG_ID = 0xD0

# BMP280の設定
BMP280_OSRS_T = 1  # Temperature oversampling x 1
BMP280_OSRS_P = 1  # Pressure oversampling x 1
BMP280_MODE = 3    # Normal mode

# 設定の計算
ctrl_meas = (BMP280_OSRS_T << 5) | (BMP280_OSRS_P << 2) | BMP280_MODE

# グローバル変数
calibration_params = {}

def init_bmp280(i2c):
    global calibration_params
    
    # BMP280の初期化
    i2c.writeto_mem(BMP280_I2C_ADDRESS, BMP280_REG_CTRL_MEAS, bytes([ctrl_meas]))

    # 補正パラメータの読み取り
    
    #read_calibration_paramsを別ファイルに置く場合
    
    #data = i2c.readfrom_mem(BMP280_I2C_ADDRESS, 0x88, 24)
    #calibration_params = read_calibration_params(data)
    
    def read_calibration_params():
        data = i2c.readfrom_mem(BMP280_I2C_ADDRESS, 0x88, 24)

        dig_T1 = data[1] << 8 | data[0]
        dig_T2 = data[3] << 8 | data[2]
        dig_T3 = data[5] << 8 | data[4]
        dig_P1 = data[7] << 8 | data[6]
        dig_P2 = data[9] << 8 | data[8]
        dig_P3 = data[11] << 8 | data[10]
        dig_P4 = data[13] << 8 | data[12]
        dig_P5 = data[15] << 8 | data[14]
        dig_P6 = data[17] << 8 | data[16]
        dig_P7 = data[19] << 8 | data[18]
        dig_P8 = data[21] << 8 | data[20]
        dig_P9 = data[23] << 8 | data[22]

        return {
            'dig_T1': dig_T1,
            'dig_T2': dig_T2,
            'dig_T3': dig_T3,
            'dig_P1': dig_P1,
            'dig_P2': dig_P2,
            'dig_P3': dig_P3,
            'dig_P4': dig_P4,
            'dig_P5': dig_P5,
            'dig_P6': dig_P6,
            'dig_P7': dig_P7,
            'dig_P8': dig_P8,
            'dig_P9': dig_P9,
        }
        
    
    calibration_params = read_calibration_params()

def read_temp(i2c):
    data = i2c.readfrom_mem(BMP280_I2C_ADDRESS, BMP280_REG_PRESS_MSB, 6)
    
    # 温度
    adc_T = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)

    var1 = ((((adc_T >> 3) - (calibration_params['dig_T1'] << 1))) * (calibration_params['dig_T2'])) >> 11
    var2 = (((((adc_T >> 4) - (calibration_params['dig_T1'])) * ((adc_T >> 4) - (calibration_params['dig_T1']))) >> 12) * (calibration_params['dig_T3'])) >> 14
    t_fine = var1 + var2
    temperature = (t_fine * 5 + 128) >> 8
    
    if var1 == 0:
        return None
    
    return temperature / 100.0
    
def read_pressure(i2c):
    data = i2c.readfrom_mem(BMP280_I2C_ADDRESS, BMP280_REG_PRESS_MSB, 6)
    
    adc_T = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)

    var1 = ((((adc_T >> 3) - (calibration_params['dig_T1'] << 1))) * (calibration_params['dig_T2'])) >> 11
    var2 = (((((adc_T >> 4) - (calibration_params['dig_T1'])) * ((adc_T >> 4) - (calibration_params['dig_T1']))) >> 12) * (calibration_params['dig_T3'])) >> 14
    t_fine = var1 + var2
    
    # 気圧
    adc_P = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)

    var1 = t_fine - 128000
    var2 = var1 * var1 * calibration_params['dig_P6']
    var2 = var2 + ((var1 * calibration_params['dig_P5']) << 17)
    var2 = var2 + (calibration_params['dig_P4'] << 35)
    var1 = ((var1 * var1 * calibration_params['dig_P3']) >> 8) + ((var1 * calibration_params['dig_P2']) << 12)
    var1 = (((1 << 47) + var1) * calibration_params['dig_P1']) >> 33

    if var1 == 0:
        return None

    p = 1048576 - adc_P
    p = (((p << 31) - var2) * 3125) // var1
    var1 = (calibration_params['dig_P9'] * (p >> 13) * (p >> 13)) >> 25
    var2 = (calibration_params['dig_P8'] * p) >> 19
    pressure = ((p + var1 + var2) >> 8) + (calibration_params['dig_P7'] << 4)

    return pressure / 25600.0

5. 実行結果

一部アドレス等の部分を除き結果を表示します
クライアントとサーバのプログラムを実行します。仮に接続ができていない場合、count1 のように送信失敗という結果が表示されます。接続ができていれば何も表示されません。(count2 からは正常)
これは5秒おきにデータを送信しました。

クライアント

MPY: soft reboot
WiFiに接続します...
.
.
WiFiに接続しました
count: 1
   Temperature: 26.83 °C,
   Pressure: 1017.922 Pa
送信失敗
count: 2
   Temperature: 26.84 °C,
   Pressure: 1017.431 Pa
count: 3
   Temperature: 26.86 °C,
   Pressure: 1016.998 Pa
count: 4
   Temperature: 26.86 °C,
   Pressure: 1016.45 Pa
count: 5
   Temperature: 26.87 °C,
   Pressure: 1016.148 Pa
count: 6
.
.
.

サーバー

[サーバーIPアドレス]:サーバーのIPアドレスが表示されます
[ポート番号]:設定しているポート番号が表示されます
[クライアントIPアドレス]:クライアント機器のIPアドレスが表示されます。機器の識別に用いられます

このようにサーバー側では正常に受信できており、5秒間隔にデータの取得が出来ていることが分かります

Starting HTTP server on [サーバーIPアドレス]:[ポート番号]...
[クライアントIPアドレス] - - [22/Nov/2024 06:20:44] "POST / HTTP/1.0" 200 -
[クライアントIPアドレス] - - [22/Nov/2024 06:20:49] "POST / HTTP/1.0" 200 -
[クライアントIPアドレス] - - [22/Nov/2024 06:20:54] "POST / HTTP/1.0" 200 -
[クライアントIPアドレス] - - [22/Nov/2024 06:20:59] "POST / HTTP/1.0" 200 -
[クライアントIPアドレス] - - [22/Nov/2024 06:21:04] "POST / HTTP/1.0" 200 -
[クライアントIPアドレス] - - [22/Nov/2024 06:21:09] "POST / HTTP/1.0" 200 -

サーバーで受け取ったセンサーデータ, 温度データ(temp)と気圧データ(press)をクライアントのIPアドレスごとにcsvファイルとして保存しています.
仮に、別のクライアント機器で同じように実行した場合別のCSVファイルが生成され個別に保管されます。(複数の機器を同時に実行することもできます)

Temperature (C),Pressure (Pa)
26.83,1017.922
26.84,1017.431
26.86,1016.998
26.86,1016.45
26.87,1016.148
26.87,1015.931

6. おわりに

今回 BaseHTTPRequestHandlerを使用してセンサーデータの取得とcsvとアドレス別による管理を行いました.
不出来なプログラムですが, 実験や実装の助けになればと思います.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?