オムロン環境センサUSB型&PCでデータを確実に取得する

目的

オムロン環境センサUSB型(以下、環境センサUSB型)をPC(WindowsやRaspberry PiなどのLinuxもOK)と直結し、センサー値をPythonスクリプトでCSV出力する。

なぜPCとセンサを直結するのか

センサー値を欠測なく取得したいからだ。

オムロン環境センサはデータ取得機器(Raspberry Piやスマホ)とをBLE接続する方法が一般的で、それらのノウハウはWeb上に散見される。

一方、BLEで1秒おきにデータを取得すると、頻繁にデータ欠測が発生する。

欠測が起こると、のちのデータ処理が何倍も面倒になる。グラフ化するにあたって欠損処理を考慮する必要があるし、他のデータとマージが困難など問題が多い。

BLEでセンサ値を取得すると、1分間に幾多もの欠測が発生する

環境センサUSB型は、BLEに加えてUSB(シリアル)接続による端末との通信が可能だ。この点は環境センサBAG型と異なる特長の1つだ。

この章では、環境センサUSB型とPCを直接接続することで、欠測なく確実にデータを取得する方法を紹介する。

環境センサ USB型 2JCIE-BU01
OMRON
環境センサ USB型 2JCIE-BU01
Amazon_logoAmazon
Rakuten_logoRakuten

手順

環境センサUSB型とPCを接続する

PCとセンサの接続は長めのUSB延長ケーブルを用いると計測がラクになる

最初に環境センサUSB型と測定するPC(WindowsやRaspberry PiなどのLinux端末)と接続しよう。

環境を測定したい箇所にセンサを置くためにも、長めのUSB延長ケーブルを用いると良い。

Windowsのみ|ドライバのインストール

Windowsで環境センサUSB型を扱う場合はドライバのインストールが必要だ。

下記のオムロン Webサイトより、形2JCIEシリーズ(環境センサUSB型)のUSBドライバ(2jcie-bu01_usbdriver.zip)をダウンロードし、インストールする。

ドライバは実行ファイル(.exe)ではなく、デバイスマネージャーからインストールする方式だ。詳しい方法は同サイトで配布しているUSBドライバ インストールマニュアル(PDF)を参照して欲しい。

ドライバインストールのポイント

公式のマニュアルに従ってインストールを実施すれば難なくこなせると思うが、以下の点がポイントになる。

  • 環境センサUSB型をWindows機のUSBポートに挿入した状態で行う
  • ドライバのインストールは2度必要
  • センサがCOMポートで認識されればOK(下記画像の場合はCOM3で認識)
ドライバのインストール後、環境センサUSB型がCOM3で認識された

補足|センサファームウェアの更新

上記で紹介したドライバのインストール後、WindowsのCOMポートに環境センサUSB型が認識されると、センサファームウェアのアップデートが可能となる。

2019年に購入した環境センサUSB型のファームウェアはVer0.69であった。

最新(2021年5月時点でVer0.70)のファームウェアへ更新することで、iPhone 8以降の端末でBluetooth接続可能となるとのこと。自分には必要ないが、作業自体は簡単なので掲載しておく。

更新作業は、ドライバ配布先で同じく公開されているファームウェア・アップデートツールマニュアルを見ながら、手順通りに進めていけばよい。1分程度で完了する。

専用のアプリケーションでセンサのファームウェアを更新可能

なお、ファームウェアの更新作業はWindowsのみ可能だ。Linuxでは行えないので注意したい。

Linuxのみ|シリアルデバイスの認識

Raspberry PiなどのLinux機で環境センサUSB型を認識させるためのコマンドをたたく

# 環境センサをUSBシリアル接続デバイスとして認識
$ sudo modprobe ftdi_sio
$ sudo sh -c "echo 0590 00d4 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id"

# USBシリアル接続デバイス名を確認
$ dmesg | grep ttyUSB
[*******] usb 1-1.4: FTDI USB Serial Device converter now attached to ttyUSB0

最後のコマンドでFTDI USB Serial Device~の後に表示されるデバイス名「ttyUSB*」は次の項で必要になるので、忘れないようメモしておく。

センサー値取得スクリプトの用意

ここからはWindowsとLinuxどちらも同じ操作となる。

センサー値をUSB接続で取得するプログラムは、OMRON社がgithubで公開しているPythonスクリプトを利用すると手っ取り早い。

公開されているデータ取得プログラムsample_2jciebu.pyはRaspberry Pi(Linux)向けのPythonスクリプトだが、たった1箇所を修正するだけでWindowsでも使用することができる。

103行目に環境センサUSB型のシリアルポートを指定する箇所がある。Linuxのポート指定は/dev/ttyUSB○だが、WindowsではCOM○に置き換えれば良い。ここでのCOM○はデバイスマネージャで環境センサUSB型が認識されたCOMポートを指定する。

# Serial.
# Linuxの場合  
ser = serial.Serial("/dev/ttyUSB○", 115200, serial.EIGHTBITS, serial.PARITY_NONE)
# Windwosの場合
ser = serial.Serial("COM○", 115200, serial.EIGHTBITS, serial.PARITY_NONE)

WSL2などの仮想環境ではシリアルポートを認識できないので、実行できない。Windowsの場合は、Anaconda等のPython実行環境が必要だ。

また、Windows・Linux両方でシリアル通信のPythonパッケージpySerialが必要なので、pipでインストールする。

$ pip install pyserial

これでサンプルプログラムの動作準備は完了だ。

実行例

sample_2jciebu.pyを実行すると、現在のセンサー値が1秒ごとに表示される。

PS> python.exe .\sample_2jciebu.py

Time measured:2021/02/28 04:38:20
Temperature:20.25
Relative humidity:53.9
Ambient light:187
Barometric pressure:1032.932
Sound noise:54.52
eTVOC:110
eCO2:1128
Discomfort index:65.8
Heat stroke:17.76
Vibration information:0
SI value:0.0
PGA:0.0
Seismic intensity:0.0
Temperature flag:0
Relative humidity flag:0
Ambient light flag:0
Barometric pressure flag:0
Sound noise flag:0
eTVOC flag:0
eCO2 flag:0
Discomfort index flag:0
Heat stroke flag:0
SI value flag:0
PGA flag:0
Seismic intensity flag:0

あとは標準出力で出力しているセンサー値をCSVに書き出せばヨシ!と思うかもしれないが、サンプルプログラムとだけあって実用的なものではない。詳しくは次の項で説明する。

センサー値の出力間隔を正確にする

このプログラムでは、正確に1秒間隔の出力とならない。スクリプト内で使用されているsleep関数によれば1.1秒間隔でセンサー値が更新される。加えて、プログラムの処理時間も加算され、時間差の大きい出力となる。

# サンプルプログラムの出力時刻を計測すると、約1.15秒ずつズレている
Time measured:2021/05/08 22:46:00.732806
Time measured:2021/05/08 22:46:01.847106
Time measured:2021/05/08 22:46:02.959612
Time measured:2021/05/08 22:46:04.078193
Time measured:2021/05/08 22:46:05.201835
Time measured:2021/05/08 22:46:06.311006
Time measured:2021/05/08 22:46:07.425833
Time measured:2021/05/08 22:46:08.540134
Time measured:2021/05/08 22:46:09.655708
Time measured:2021/05/08 22:46:10.776214
Time measured:2021/05/08 22:46:11.894664
Time measured:2021/05/08 22:46:13.019093

これでは1秒おきの計測とは言えない。Pythonのタイマー処理を高精度化する手法を調べてみると、この記事が参考になった。

記事で紹介している「処理時間を考慮してthreadingとsleepを使う」をセンサー値取得スクリプトに適用する。下記のPythonスクリプトはサンプルプログラムに以下の機能を追加したものだ。

  • threadingを適用し、高精度の時刻間隔でセンサー値を取得
  • プログラムと同じディレクトリにCSVファイル(omron.csv)を出力

完成したPythonスクリプト

スクリプトの109行目:環境センサUSB型のシリアルポート,111行目:CSV出力ファイル名は適宜変更して欲しい。

import serial
import time
from datetime import datetime
import sys
import threading
import csv

def s16(value):
    return -(value & 0x8000) | (value & 0x7fff)

def calc_crc(buf, length):
    """
    CRC-16 calculation.
    """
    crc = 0xFFFF
    for i in range(length):
        crc = crc ^ buf[i]
        for i in range(8):
            carrayFlag = crc & 1
            crc = crc >> 1
            if (carrayFlag == 1):
                crc = crc ^ 0xA001
    crcH = crc >> 8
    crcL = crc & 0x00FF
    return (bytearray([crcL, crcH]))


def print_latest_data(data,csv_file):
    """
    print measured latest value.
    """
    time_measured = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
    temperature = str( s16(int(hex(data[9]) + '{:02x}'.format(data[8], 'x'), 16)) / 100)
    relative_humidity = str(int(hex(data[11]) + '{:02x}'.format(data[10], 'x'), 16) / 100)
    ambient_light = str(int(hex(data[13]) + '{:02x}'.format(data[12], 'x'), 16))
    barometric_pressure = str(int(hex(data[17]) + '{:02x}'.format(data[16], 'x')
            + '{:02x}'.format(data[15], 'x') + '{:02x}'.format(data[14], 'x'), 16) / 1000)
    sound_noise = str(int(hex(data[19]) + '{:02x}'.format(data[18], 'x'), 16) / 100)
    eTVOC = str(int(hex(data[21]) + '{:02x}'.format(data[20], 'x'), 16))
    eCO2 = str(int(hex(data[23]) + '{:02x}'.format(data[22], 'x'), 16))
    discomfort_index = str(int(hex(data[25]) + '{:02x}'.format(data[24], 'x'), 16) / 100)
    heat_stroke = str(s16(int(hex(data[27]) + '{:02x}'.format(data[26], 'x'), 16)) / 100)
    vibration_information = str(int(hex(data[28]), 16))
    si_value = str(int(hex(data[30]) + '{:02x}'.format(data[29], 'x'), 16) / 10)
    pga = str(int(hex(data[32]) + '{:02x}'.format(data[31], 'x'), 16) / 10)
    seismic_intensity = str(int(hex(data[34]) + '{:02x}'.format(data[33], 'x'), 16) / 1000)

    print("")
    print("Time measured:" + time_measured)
    print("Temperature:" + temperature)
    print("Relative humidity:" + relative_humidity)
    print("Ambient light:" + ambient_light)
    print("Barometric pressure:" + barometric_pressure)
    print("Sound noise:" + sound_noise)
    print("eTVOC:" + eTVOC)
    print("eCO2:" + eCO2)
    print("Discomfort index:" + discomfort_index)
    print("Heat stroke:" + heat_stroke)
    print("Vibration information:" + vibration_information)
    print("SI value:" + si_value)
    print("PGA:" + pga)
    print("Seismic intensity:" + seismic_intensity)

    # Output CSV
    with open(csv_file, 'a', newline="") as f:
        writer = csv.writer(f)
        writer.writerow([time_measured, temperature, relative_humidity, ambient_light,
                         barometric_pressure, sound_noise, eTVOC, eCO2, discomfort_index,
                         heat_stroke, vibration_information, si_value, pga, seismic_intensity])

def worker(ser,csv_file):
    # Get Latest data Long.
    command = bytearray([0x52, 0x42, 0x05, 0x00, 0x01, 0x21, 0x50])
    command = command + calc_crc(command, len(command))
    tmp = ser.write(command)
    time.sleep(0.1)
    data = ser.read(ser.inWaiting())
    print_latest_data(data,csv_file)

def scheduler(interval, fanc, port, csv_file, wait=True):
    # Timer Initialize
    time_measured = datetime.now().strftime("0.%f")
    initial_time = 1.37 - float(time_measured)
    time.sleep(initial_time)

    base_time = time.time()
    next_time = 0
    
    # Serial.
    ser = serial.Serial(port, 115200, serial.EIGHTBITS, serial.PARITY_NONE)
    
    try:
        while ser.isOpen():
            t = threading.Thread(target = fanc(ser,csv_file))
            t.start()
            if wait:
                t.join()
            next_time = ((base_time - time.time()) % interval) or interval
            time.sleep(next_time)

    except KeyboardInterrupt:
        # script finish.
        sys.exit


if __name__ == '__main__':

    # 環境センサUSB型のシリアルポートを指定
    PORT = "COM3"
    # 出力するCSVファイル名
    CSV_FILE = "./omron.csv"

    with open(CSV_FILE, 'w', newline="") as f:
        writer = csv.writer(f)
        writer.writerow(['Time measured', 'Temperature', 'Relative humidity', 'Ambient light',
                         'Barometric pressure', 'Sound noise', 'eTVOC', 'eCO2', 'Discomfort index',
                         'Heat stroke', 'Vibration information', 'SI value', 'PGA', 'Seismic intensity'])

    scheduler(1, worker, PORT, CSV_FILE, False)

MIT License

Copyright (c) 2018 – present OMRON Corporation

All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

実行例

出力されるCSVファイルの中身はこんな感じ。

Time measured,Temperature,Relative humidity,Ambient light,Barometric pressure,Sound noise,eTVOC,eCO2,Discomfort index,Heat stroke,Vibration information,SI value,PGA,Seismic intensity
2021/05/08 22:41:45,25.32,62.14,0,1002.56,50.49,3940,3510,73.49,23.11,0,0.0,0.0,0.0
2021/05/08 22:41:46,25.33,62.12,0,1002.537,49.57,3940,3510,73.51,23.12,0,0.0,0.0,0.0
2021/05/08 22:41:47,25.32,62.14,0,1002.51,51.21,3940,3510,73.49,23.11,0,0.0,0.0,0.0
2021/05/08 22:41:48,25.33,62.1,0,1002.498,51.52,3940,3510,73.5,23.12,0,0.0,0.0,0.0
2021/05/08 22:41:49,25.33,62.1,0,1002.494,49.22,3940,3510,73.5,23.12,0,0.0,0.0,0.0
2021/05/08 22:41:50,25.32,62.11,0,1002.496,49.8,3940,3510,73.49,23.11,0,0.0,0.0,0.0
2021/05/08 22:41:51,25.32,62.14,0,1002.506,50.08,3940,3510,73.49,23.11,0,0.0,0.0,0.0
2021/05/08 22:41:52,25.31,62.17,0,1002.522,49.68,3940,3510,73.48,23.1,0,0.0,0.0,0.0

センサー値出力時刻の間隔

最も肝心なセンサー値の出力は、正確に1秒間隔と言っていいだろう。0.01~0.02秒の誤差に収まっている。

2021/05/08 23:31:11.513589
2021/05/08 23:31:12.495803
2021/05/08 23:31:13.498054
2021/05/08 23:31:14.495348
2021/05/08 23:31:15.491560
2021/05/08 23:31:16.485768
2021/05/08 23:31:17.485038
2021/05/08 23:31:18.484865
2021/05/08 23:31:19.485943
2021/05/08 23:31:20.492670
2021/05/08 23:31:21.487735
2021/05/08 23:31:22.502090
2021/05/08 23:31:23.487152
2021/05/08 23:31:24.491681
欠測なく取得できたデータは扱いやすく、グラフもキレイ!

まとめ

オムロン環境センサでセンサデータを確実に取得するポイントをまとめると、以下のとおりだ。

  • 環境センサUSB型とPCを接続することで、欠測なくデータを取得可能
  • オムロン公式のサンプルプログラムを活用しよう
  • Pythonのthreadingなど、高精度な間隔でプログラムを実行可能な仕組みが必要

環境センサUSB型は無線(BLE)と有線(USB)どちらを選んでもデータ取得ができる、応用が効くセンサだ。場面に応じて使い分けることでセンサ活用の幅が広がること間違いなしだろう。

環境センサ USB型 2JCIE-BU01
OMRON
環境センサ USB型 2JCIE-BU01
Amazon_logoAmazon
Rakuten_logoRakuten

5 COMMENTS

アバター たかひろ

mimoさん、threadingのお知恵を頂き大変助かりました。ありがとうございました!

返信する
mimo mimo

たかひろさま
コメントありがとうございます!私も先人のお知恵に感謝しております。

返信する
アバター takeshi

コメント失礼いたします。とても参考になりました!
完成後のpythonスクリプト82~84行目のTimer Initialize処理はどのような目的で行っているのでしょうか。1.37という数字は何を示しているのでしょうか。
よろしければご教示いただけますと幸いです。

返信する
mimo mimo

プログラムの中身にはほとんど触れておらず、説明不足でした。
私なりの考えですが、参考になれば幸いです。

Timer Initializeは測定開始時刻を調整するために設けています。
例えば、センサー値が出力される時刻が以下のようになると、少し面倒だと感じませんか?

【コンマ0秒のタイミングで実行した例】
hh:mm:ss.ms
10:00:00.000
10:00:01.101
10:00:01.998 *
10:00:03.012
10:00:04:032

*印のところは、02秒になって欲しいところですが、わずかな実行時間のブレが生じることは度々あります。
1秒毎のデータにしたければ、上記のms部分を丸め処理する手もあります。

ここで、もう1つの手として開始時刻をUNIX時刻のちょうどコンマ5秒に調整できれば、このブレは吸収できます。

【コンマ5のタイミングで実行した例】
10:00:00.500
10:00:01.488
10:00:02.565
10:00:03.480
10:00:04.604

これなら、出力時刻からmsを切ってしまえば、簡単に1秒毎のデータになります。

それでTimer Initializeに話を戻すと、initial_time = 1.37 – float(time_measured)としているのは
ちょうど、UNIX時刻が.37秒になるまで、あとどれくらい待てばよいかを計算しています。

なぜ.37という中途半端な値なのかというと、その後のセンサー値を取得するworker関数の実行時間が約0.13秒だからです。
もちろん、実行するシステムによってworker関数の実行にかかる時間はバラバラだと思いますので、適宜調整する必要はあります。

ちなみに1.37の値に0.5を足せば、コンマ0秒のタイミングでセンサー値が出力されるようになります。

返信する
アバター inf_nha

下記を追記してinfluxdbへの入力をしてみました
printのネーミングをそのまま転用です。

#influxdb input
from influxdb import InfluxDBClient
json_body = [
{
“measurement”: “omron_2jciebu”,
“tags”: {
“host”: “XXXXXXXXXX”,
“region”: “jpn”
},
“fields”: {
“Temperature”: float(temperature),
“Relative humidity”: float(relative_humidity),
“Ambient light”: float(ambient_light),
“Barometric pressure”: float(barometric_pressure),
“Sound noise”: float(sound_noise),
“eTVOC”: float(eTVOC),
“eCO2”: float(eCO2),
“Discomfort index”: float(discomfort_index),
“Heat stroke”: float(heat_stroke),
“Vibration information”: float(vibration_information),
“SI value”: float(si_value),
“PGA”: float(pga),
“Seismic intensity”: float(seismic_intensity)
}
}
]

client = InfluxDBClient(‘xxx.xxx.xxx.xxx’, 8086, ‘root’, ”, ‘omron’)
client.write_points(json_body)

返信する

takeshi へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 が付いている欄は必須項目です