width >ラズベリーパイが取得したデータを保存する d

>ラズベリーパイが取得したデータを保存する
TWELITEからシリアル通信で送られてきたデータを取得できました。次はこのデータを保存します。テキストで保存するのか違う岸城にするのか?一番簡単なのはテキスト形式です。 CSV形式で保存すれば表計算ソフトで表やグラフで見ることができます。しかしテキスト形式の弱点はファイルが大きくなってしまうことです。温度・湿度・気圧を1秒に1回送ってきます。1日分のデータは60X60X24=86400になります。1回のデータは、時刻、温度、湿度、気圧改行とします。12:56:34,24,45,1013改行 20バイトくらい必要です。1日のデータ量は1,728,000です。1日に1.7MBはラスベリーパイにはキツイ?
そこでファイルが大きくならないように独自形式での保存方法を紹介します。

ファイル拡張子:dat
世間によくある拡張子です。ただし、皆独自の形式になっているので該当するソフトがないと正しい表示や編集ができません。

ファイル形式:ヘッダー部とデータ部で構成

ヘッダー部:bme280
何のデータなのか識別するため。


データ部:温度<2バイト>湿度<2バイト>気圧<2バイト>   計6バイト
時刻はデータの位置によりきまる。1秒ごとに保存するほど変化の大きなデータではないので10秒に1回保存する。データの先頭はは00:00:00。最後は23:59:50。

ファイルのタイムスタンプは14:33で秒は不明。00秒として計算するとアドレスは0x7ACA。
赤線で囲んだところは20秒目のデータ→14:33:20のデータです。
データはリトルエンディアンで処理しているので、
最初の2バイト 10 00 →0x0010  16度
次の2バイト    00 2D →0x002D  45%
最後の2バイト E9 03 →0x03E9 1001ヘクトパスカル
測定値がファイルに保存されています。

このようなバイナリーファイルを確認するのは大変です。バイナリーエディタを使うと楽ちんです。現在使っているのはGHexです。インストールは簡単です。

sudo apt install ghex
で終わりです。

ソース

# -*- coding: utf-8 -*-
import serial
import threading
import os
from datetime import datetime
import array

g_Serial = None
g_rxData = None
g_bRxFinish = False
g_bQuit = False

#記録は10秒に1回
#ファイル名はyyyymm.dd.dat
#ファイルにヘッダーをつける 'bme280'

g_DataSize = 6 * 60 * 24
g_Header = b'bme280'

def isHeader(fd):
    lenght = len(g_Header)
    header =  os.read(fd, lenght)
    if(header == g_Header):
        return True
    return False


def saveToFile():
    global g_DataSize, g_Header

    now = datetime.now()
    if((now.second % 10) != 0):
        return
    
    date = "%04d%02d%02d" %(now.year, now.month, now.day)
    filename = '/media/pi/USBMEMO/' + date + '.dat'
    print(filename)
    ary = array.array('b',[-1, -1, -1, -1, -1, -1] * g_DataSize)
    # print(ary)
    fd = None
    if(os.path.exists(filename) == True):
        fd = os.open(filename, os.O_RDWR)
        if(isHeader(fd) == False):
            os.close(fd)
            return
        #ary = os.read(fd, 6 * g_DataSize)# 6 byte * size
    else:
        fd = os.open(filename, os.O_RDWR|os.O_CREAT )
        os.write(fd, g_Header)
        os.write(fd, ary)

    writePos = int(len(g_Header) + (now.second / 10) * 6 + now.minute * 6 * 6 + now.hour * 6 * 6 * 60)
    os.lseek(fd, writePos, os.SEEK_SET)
    print(writePos)
    uRx = g_rxData.decode()
    #余計なスペースを削除
    uRx.replace(' ', '')
    #温度データを整数型にする
    posStart = uRx.find('T=')
    posEnd = uRx.find('H=')
    temprature = int(uRx[posStart + 2 : posEnd])
    #リトルエンディアンに変換して書き込み
    little = int.to_bytes(temprature, 2, 'little')
    os.write(fd, little)

    #湿度データを整数型にする
    posStart = uRx.find('H=')
    posEnd = uRx.find('P=')
    humidity = int(uRx[posStart + 2 : posEnd])
    #リトルエンディアンに変換して書き込み
    little = int.to_bytes(humidity, 2, 'little')
    os.write(fd, little)

    #気圧データを整数型にする
    posStart = uRx.find('P=')
    posEnd = uRx.find('\n')
    pressure = int(uRx[posStart + 2 : posEnd])
    #リトルエンディアンに変換して書き込み
    little = int.to_bytes(pressure, 2, 'little')
    os.write(fd, little)

    os.close(fd)



#RX232C受信スレッド関数
def rx232c():
    global g_bRxFinish, g_rxData, g_bQuit
    while(True):
        if(g_bQuit == True):
            break
        rxData = g_Serial.read()
        if(rxData == b'\n'):
            g_bRxFinish = True
        else:
            if(g_rxData == None):
                g_rxData = rxData
            else:
                g_rxData += rxData
            
def isRxData():
    global g_rxData
    if(g_rxData == None):
        return False
    if(g_rxData.find(b'T=') == -1):
        return False
    if(g_rxData.find(b'H=') == -1):
        return False
    if(g_rxData.find(b'P=') == -1):
        return False
    return True
    



def main():
    global g_Serial,g_bRxFinish, g_rxData, g_bQuit
    g_Serial = serial.Serial('/dev/ttyUSB0', 115200)
    threadRx = threading.Thread(target=rx232c)
    threadRx.start()
    num = 1
    try:
        while(g_bQuit == False):
            if(g_bRxFinish == True):
                g_bRxFinish = False
                if(isRxData() == True):
                    print('{:4d}  {}'.format(num, g_rxData))
                else:
                    print('RX data is invalid.')
                saveToFile()
                g_rxData = None
                g_rxData = b''
                l = len(g_rxData)
                num += 1
    except KeyboardInterrupt:
        #print('control C')
        g_bQuit = True
        threadRx.join()
        g_Serial.close()

if __name__ == "__main__":
    main()

	

シリアル通信データを受信するとsaveToFile()が呼び出されます。この関数でやっていることは10秒に一回ファイルにデータを保存する。
保存するファイル名はyyyymmdd.datです。
ファイルがないときはヘッダー部と0xFFのデータ部をファイルに書き込みます。
時;分秒からファイルの書込み位置を算出して、そこに温度・湿度・気圧をリトルエンディアンに
変換して書き込みます。
今回はコメントを多くしたので参考にしてください。