hiroのアイコン画像

簡単百葉箱

hiro 2021年05月16日に作成  (2021年05月16日に更新)

簡単百葉箱

概要

obniz Board 1Y と BME680 を利用した簡単百葉箱です。測定結果は Ambient に送信して可視化します。
作成したコードはobnizのアプリとして公開していますのでセンサを用意すれば簡単に利用することが可能です。

obniz IoT コンテスト 応募作品です。

デモ

コンテスト応募要件なので、動画を貼ります。外観上はセンサ情報を取得するだけなので地味ですね。

ここに動画が表示されます

作者自宅で動いているシステムのグラフです。
サーバの箱の中に設置しているので気温高めです。
https://ambidata.io/bd/board.html?id=25964

部品

電源はUSBでも動作します。
モバイルバッテリを利用する場合、スリープ中にバッテリからの出力が停止するものがあるかもしれません。
秋月のBME680を利用すると、5Vを電源に使えるので、obnizから供給することが可能になり、配線も楽です。
また、電源のIOの電圧を計測することで、電源電圧も計測しています。

結線

obniz BME680
IO0 GND
IO1 SDA
IO2 SCL
IO3 VIN

おおまかなシステムの流れ

  1. obnizの電源が入る
  2. クラウド実行(デバイスがオンライン時に実行)が呼び出される
    1. センサ情報の取得
    2. Ambientへ送信
    3. スリープ移行

セットアップ手順

  1. Ambientでチャネルを作成して、チャネルIDとライトキーがわかるようにする
  2. アプリをデバイスにインストールする
  3. ambient_channel_id と ambient_write_key を設定する
  4. 電源を入れて上記のインストールしたアプリが動作し、Ambientに情報が届いたら成功。obnizコンソールのデバイスのページにもアプリ実行ログが記録されます。
    実行ログ

参考資料

おわりに

obniz公式のセンサとしてBME280はありましたが、BME680は無かったので、データシートとにらめっこしながら動かせるようにしました。今後はBME280のようにライブラリ化して簡単に使えるようにしたいと思います。
また、BME680は BOSCH が公開している BSEC というライブラリを利用することで、センサ情報から IAQ (indoor air quarity) が算出できるようです。今はガスセンサの抵抗値を算出するまでにとどまっていますが、IAQだと数値的に空気の清浄度がわかりやすいので、利用したいのですが、バイナリ公開なので、簡単には使えないようです。WebAPI化すれば利用できるかな。

ソースコード

コンテストの応募要件なのでソースコードをそのまま貼ります。

BME680_Ambient

<html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" /> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://unpkg.com/obniz@3.x/obniz.js" crossorigin="anonymous"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.10.1/qs.min.js" integrity="sha512-aTKlYRb1QfU1jlF3k+aS4AqTpnTXci4R79mkdie/bp6Xm51O5O3ESAYhvg6zoicj/PD6VYY0XrYwsWLcvGiKZQ==" crossorigin="anonymous"></script> <script src="https://unpkg.com/ambient-lib@1.0.3/lib/ambient-lib.js"></script> </head> <body> <div id="obniz-debug"></div> <script> var obniz = new Obniz("OBNIZ_ID_HERE"); var bme_addr = 0x77; var par_t1, par_t2, par_t3; var par_p1, par_p2, par_p3, par_p4, par_p5, par_p6, par_p7, par_p8, par_p9, par_p10; var par_h1, par_h2, par_h3, par_h4, par_h5, par_h6, par_h7; var par_g1, par_g2, par_g3, res_heat_range, res_heat_val, range_switching_error; function write(data) { obniz.i2c0.write(bme_addr, data); } async function read(reg_addr, num = 1) { write([reg_addr]); try { var ret = await obniz.i2c0.readWait(bme_addr, num); // console.log(`read(0x${reg_addr.toString(16)}[${num}]): ${ret}`); if (num == 1) return ret[0]; return ret; } catch (e) { console.error(e); } } async function loadCalibration() { all = await read(0x00, 0x100); par_t1 = (all[0xea] << 8) | all[0xe9]; par_t2 = _readSigned16((all[0x8b] << 8) | all[0x8a]); par_t3 = _readSigned8(all[0x8c]); par_p1 = (all[0x8f] << 8) | all[0x8e]; par_p2 = _readSigned16((all[0x91] << 8) | all[0x90]); par_p3 = _readSigned8(all[0x92]); par_p4 = _readSigned16((all[0x95] << 8) | all[0x94]); par_p5 = _readSigned16((all[0x97] << 8) | all[0x96]); par_p6 = _readSigned8(all[0x99]); par_p7 = _readSigned8(all[0x98]); par_p8 = _readSigned16((all[0x9d] << 8) | all[0x9c]); par_p9 = _readSigned16((all[0x9f] << 8) | all[0x9e]); par_p10 = all[0xa0]; par_h1 = (all[0xe3] << 4) | (all[0xe2] >> 4); par_h2 = (all[0xe1] << 4) | (all[0xe2] & 0xf); par_h3 = _readSigned8(all[0xe4]); par_h4 = _readSigned8(all[0xe5]); par_h5 = _readSigned8(all[0xe6]); par_h6 = all[0xe7]; par_h7 = _readSigned8(all[0xe8]); par_g1 = _readSigned8(all[0xed]); par_g2 = _readSigned16((all[0xec] << 8) | all[0xeb]); par_g3 = _readSigned8(all[0xee]); res_heat_range = (all[0x02] & 0x30) >> 4; res_heat_val = _readSigned8(all[0x00]); range_switching_error = (all[0x04] & 0xf0) >> 4; } function _readSigned16(value) { if (value >= 0x8000) { value = value - 0x10000; } return value; } function _readSigned8(value) { if (value >= 0x80) { value = value - 0x100; } return value; } async function getAllSensorValues(enableGas) { data = await read(0x1f, 8); d_gas = await read(0x2a, 2); var press_adc = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4); var temp_adc = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4); var hum_adc = (data[6] << 8) | data[7]; var gas_adc = (d_gas[0] << 2) | (d_gas[1] & 0xc0); var gas_range = d_gas[1] & 0x0f; var temp = calcTemperature(temp_adc); var temperature = temp[0]; var t_fine = temp[1]; var pressure = calcPressure(press_adc, t_fine); var humidity = calcHumidity(hum_adc, temperature); var gas = 0; if (enableGas) gas = calcGas(gas_adc, gas_range); return { temperature, humidity, pressure, gas }; } function calcTemperature(temp_adc) { var temp_comp, t_fine, var1, var2; var1 = (temp_adc / 16384.0 - par_t1 / 1024.0) * par_t2; var2 = (temp_adc / 131072.0 - par_t1 / 8192.0) * (temp_adc / 131072.0 - par_t1 / 8192.0) * (par_t3 * 16.0); t_fine = var1 + var2; temp_comp = t_fine / 5120.0; return [temp_comp, t_fine]; } function calcPressure(press_adc, t_fine) { var press_comp, var1, var2, var3; var1 = t_fine / 2.0 - 64000.0; var2 = var1 * var1 * (par_p6 / 131072.0); var2 = var2 + var1 * par_p5 * 2.0; var2 = var2 / 4.0 + par_p4 * 65536.0; var1 = ((par_p3 * var1 * var1) / 16384.0 + par_p2 * var1) / 524288.0; var1 = (1.0 + var1 / 32768.0) * par_p1; press_comp = 1048576.0 - press_adc; if (var1 != 0) { press_comp = ((press_comp - var2 / 4096.0) * 6250.0) / var1; var1 = (par_p9 * press_comp * press_comp) / 2147483648.0; var2 = press_comp * (par_p8 / 32768.0); var3 = (press_comp / 256.0) * (press_comp / 256.0) * (press_comp / 256.0) * (par_p10 / 131072.0); press_comp = press_comp + (var1 + var2 + var3 + par_p7 * 128.0) / 16.0; } else { press_comp = 0; } return press_comp / 100; // to hPa } function calcHumidity(hum_adc, temp_comp) { var hum_comp, var1, var2, var3, var4; var1 = hum_adc - (par_h1 * 16.0 + (par_h3 / 2.0) * temp_comp); var2 = var1 * ((par_h2 / 262144.0) * (1.0 + (par_h4 / 16384.0) * temp_comp + (par_h5 / 1048576.0) * temp_comp * temp_comp)); var3 = par_h6 / 16384.0; var4 = par_h7 / 2097152.0; hum_comp = var2 + (var3 + var4 * temp_comp) * var2 * var2; return hum_comp; } function calcGas(gas_adc, gas_range) { const const_array1 = [ 1, 1, 1, 11000000, 1, 0.99, 1125000, 0.992, 1, 115625, 0.998, 0.995, 11953.125, 0.99, 1, 1, ]; const const_array2 = [ 8000000, 4000000, 2000000, 499500.4995, 248262.1648, 63004.03226, 31281.28128, 7812.5, 3906.25, 976.5625, 488.28125, 244.140625, ]; var1 = (1340.0 + 5.0 * range_switching_error) * const_array1[gas_range]; gas_res = (var1 * const_array2[gas_range]) / (gas_adc - 512.0 + var1); return gas_res / 1000; // to kOhm } async function waitUntilMeasured(enableGas) { while (read(0x1d) & 0x20); if (enableGas) while (read(0x1d) & 0x40); } function calcWaitVal(dur) { var multi = 0; while (dur > 63) { dur /= 4; multi++; } if (multi > 3) return 0; return (multi << 6) | dur; } function calcResHeat(target_temp, amb_temp) { var1 = par_g1 / 16.0 + 49.0; var2 = (par_g2 / 32768.0) * 0.0005 + 0.00235; var3 = par_g3 / 1024.0; var4 = var1 * (1.0 + var2 * target_temp); var5 = var4 + var3 * amb_temp; res_heat = 3.4 * (var5 * (4.0 / (4.0 + res_heat_range)) * (1.0 / (1.0 + res_heat_val * 0.002)) - 25); return parseInt(res_heat); } async function setHeater(target_temp = 0, duration = 0) { obj = await getAllWait(false); amb_temp = obj.temperature; // 4. Set gas_wait_0<7:0> to 0x59 to select 100 ms heat up duration wait_val = calcWaitVal(duration); write([0x64, wait_val]); // 5. Set the corresponding heater set-point by writing the target heater resistance to res_heat_0<7:0> res_heat = calcResHeat(target_temp, amb_temp); write([0x5a, res_heat]); // 6. Set nb_conv<3:0> to 0x0 to select the previously defined heater settings // 7. Set run_gas_l to 1 to enable gas measurements write([0x71, 0x10]); } async function getAllWait(enableGas = true) { // force mode // 1. Set humidity oversampling to 1x by writing 0b001 to osrs_h<2:0> write([0x72, 0x01]); // 2. Set temperature oversampling to 2x by writing 0b010 to osrs_t<2:0> // 3. Set pressure oversampling to 16x by writing 0b101 to osrs_p<2:0> write([0x74, 0x54]); if (enableGas) { await setHeater(320, 150); } // 8. Set mode<1:0> to 0b01 to trigger a single measurement. write([0x74, 0x55]); await waitUntilMeasured(enableGas); val = await getAllSensorValues(enableGas); val.voltage = await obniz.ad3.getWait(); return val; } // called on online obniz.onconnect = async function () { // 電源設定 obniz.io3.drive("5v"); obniz.io3.output(true); // i2c var i2c = obniz.getFreeI2C(); await i2c.start({ mode: "master", gnd: 0, sda: 1, scl: 2, clock: 400000, pull: "5v", }); obniz.i2c0.onerror = function (err) { console.error(err); }; await obniz.wait(100); var chip_id = await read(0xd0); console.log(`chip_id: ${chip_id}`); await loadCalibration(); // センサが安定するまで何回か読み込む for (let step = 0; step < 5; step++) { await getAllWait(); } var val = await getAllWait(); obniz.i2c0.end(); console.log(val); console.log(`temperature: ${val.temperature} [degC]`); console.log(`pressure: ${val.pressure} [hPa]`); console.log(`humidity: ${val.humidity} [%]`); console.log(`gas: ${val.gas} [kOhm]`); console.log(`power voltage: ${val.voltage} [V]`); // Ambient const userconfig = Obniz.App.configs(); console.log(userconfig); const am = new Ambient( userconfig.ambient_channel_id, userconfig.ambient_write_key ); am.send( { d1: val.temperature, d2: val.pressure, d3: val.humidity, d4: val.gas, d5: val.voltage, }, function (response) { console.log(`Upload to Ambient: ${response.status}`); } ); obniz.display.print(`Temp: ${val.temperature.toPrecision(4)}`); obniz.display.print(`Press: ${val.pressure.toPrecision(6)}`); obniz.display.print(`Hum: ${val.humidity.toPrecision(4)}`); obniz.display.print(`Gas: ${val.gas.toPrecision(6)}`); obniz.display.print(`Volt: ${val.voltage.toPrecision(3)}`); await obniz.wait(2000); obniz.display.clear(); obniz.display.print("Good night."); await obniz.wait(1000); if (Obniz.App.isCloudRunning()) { Obniz.App.done({ status: "success", text: `TPHG: ${val.temperature.toPrecision( 4 )}, ${val.pressure.toPrecision(6)}, ${val.humidity.toPrecision( 4 )}, ${val.gas.toPrecision(4)}, Voltage: ${val.voltage.toPrecision(3)}`, }); } // sleep for 10 minutes // 6 times x 24 hour = 144 times / day obniz.sleepMinute(10); }; </script> </body> </html>
ログインしてコメントを投稿する