ESP-WROOM-02とBME280を使った温度測定に関係する基本的なソース類をまとめた。概要や回路については、こちらを参照のこと。
目次
- 改良前メインスケッチ(ESP_BME280-1.ino)
- センサーインタフェースクラス(hdc1000_bmc280.h)
- テーブル作成スクリプト(create_table.sql)
- サーバー側データベースインタフェースクラス(envdata_db.php)
- サーバー側データ格納スクリプト(store_data.php)
ESP_BME280-1.ino
消費電流を考慮していない当初のスケッチ。ESP8266 Arduino Core 2.4.0 を使ってビルド。
#include <ESP8266WiFi.h> #include "hdc1000_bme280.h" extern "C" { #include "user_interface.h" } #define START_MSG "\n" + String(__FILE__) + " start." #if 1 #define SERIAL_INIT(a) Serial.begin(a) #define SERIAL_OUT(a) Serial.println(a) #else #define SERIAL_INIT(a) #define SERIAL_OUT(a) #endif #define LED1 15 #define MEASURE_INTERVAL_SECONDS 180 bme280 bmp280(0x76); // wifi const char* ssid = "ssid"; const char* password = "password"; const char* remote_host = "host_ipaddress"; void die(int msec, const char* cp) { if (cp) SERIAL_OUT(cp); for(;;) { digitalWrite(LED1, 1); delay(msec); digitalWrite(LED1, 0); delay(msec); } } void deep_sleep(int seconds) { SERIAL_OUT("deep_sleep()"); ESP.deepSleep(seconds * 1000 * 1000, WAKE_RF_DEFAULT); delay(10); } bool ap_connect() { SERIAL_OUT("\nstart ap_connect()"); WiFi.mode(WIFI_STA); wifi_station_set_auto_connect(false); WiFi.begin(ssid, password); delay(200); int n; int count = 0; int retry = 0; while ((n = WiFi.status()) != WL_CONNECTED) { SERIAL_OUT(String(n) + " "); delay(500); if (n == WL_NO_SSID_AVAIL || n == WL_CONNECT_FAILED) { WiFi.reconnect(); count = 0; retry++; } if (count++ > 30 || retry > 10) { SERIAL_OUT(""); return false; } } SERIAL_OUT("\nap_connect() done."); return true; } void ap_disconnect() { if (WiFi.status() != WL_CONNECTED) return; WiFi.disconnect(); int n; while ((n = WiFi.status()) != WL_IDLE_STATUS) { SERIAL_OUT(String(n) + " "); delay(50); } SERIAL_OUT("ap_disconnect() done."); } bool wifi_send_data(float temp, float humi, float bpress, float vbat) { SERIAL_OUT("start wifi_send_data()"); WiFiClient client; if (!client.connect(remote_host, 80)) return false; String request = "/store_data.php?point_id=" + WiFi.macAddress() + "&T=" + String(temp) + "&H=" + String(humi) + "&P=" + String(bpress) + "&V=" + String(vbat); String req_line = "GET " + request + " HTTP/1.1\r\nHost: " + String(remote_host) + "\r\nConnection: close\r\n\r\n"; client.print(req_line); int count = 0; while(!client.available()) { // waif for response. delay(50); count++; if (count >= 200) { // limit = 10secs. return false; } } while(client.available()){ String line = client.readStringUntil('\n'); SERIAL_OUT(line); delay(1); } return true; } void setup() { delay(10); SERIAL_INIT(115200); SERIAL_OUT(START_MSG); pinMode(LED1, OUTPUT); digitalWrite(LED1, 1); if (!bmp280.init()) die(500, "FATAL: BMP280 init failed."); if (!ap_connect()) { deep_sleep(30); // reset myself after a while. return; } float vbat = 570.0 * system_adc_read() / 1024.0; // 100倍した電圧。470K + 100Kでの分圧時 570 / 100 = 5.7 bmp280.measure(); SERIAL_OUT("\nPa = " + String(bmp280.press) + ", Temp = " + String(bmp280.temp) + ", Humi = " + String(bmp280.humi) + ", Vbat = " + String(vbat)); wifi_send_data(bmp280.temp, bmp280.humi, bmp280.press, vbat); ap_disconnect(); deep_sleep(MEASURE_INTERVAL_SECONDS); } void loop() { delay(100); }
解説
このスケッチは、下に載せたセンサーインタフェース用のhdc1000_bme280.h と同じフォルダに入れておく。そうすることで、Arduino IDEを開いたときに各ファイルをタブで選択できるようになる。
定義、定数関係
スケッチを作るときはシリアルモニタにいろいろなメッセージを表示させるが、実稼働時には不要になのでSerial を使う部分は#if ~ #endifブロックに囲んだ。
#define MEASURE_INTERVAL_SECONDS 180 は、測定間隔の秒数を定義しており、1000000倍して ESP.deepSleep(); に渡す。
bme280 bmp280(0x76); によってbme280クラスのインスタンスを作成しており、引数の0x76でi2cアドレスを指定している。このアドレスに接続されているセンサーがBME280なのかBMP280なのかの識別はbme280クラス内で行っている。このスケッチは、以前BMP280用に作ったものを流用したので変数名がbmp280になっている。
ssid, password, remote_hostは、接続先のWiFiアクセスポイントに接続するための情報と、データ送信先のリモートホストのアドレスを指定している。
void deep_sleep(int seconds)
ディープスリープする秒数を指定する。この関数内では、 ESP.deepSleep(seconds * 1000 * 1000, WAKE_RF_DEFAULT); を呼ぶことで、スリープ時間をμ秒単位にして渡している。この関数が呼び出す EspClass::deepSleep()メソッドは、スリープ時間を 32ビットの符号なし整数で受けるから、最大で UINT_MAX μ秒まで指定可能となる。具体的には約4295秒 ≒ 71.6分まで指定可能ということになる。
なお、EspClass::deepSleep() 内では、system_deep_sleep(); というSDKのAPIに受け取った引数をそのまま渡している。SDK 側は 64ビットの符号なし整数を受け付けるので、はるかに長い時間を指定することができる。現段階では Arduino Core 2.4を利用しているのでuint32_tでディープスリープ時間を指定する必要があるが、将来的には uint64_tで指定できるようになるのかもしれない。
※ ESP8266 Arduino Core 2.4.1 において、ESP.deepSleep() の引数は uint64_t に変更された。https://github.com/esp8266/Arduino/releases を参照。
bool ap_connect()
WiFiアクセスポイントに接続するための関数。以前から疑いもなく使っているが、接続が完了するまで7~10秒ほどかかってしまう。動作の開始からdeep sleepに入るまでの時間のほとんどはこの関数が費やしてしまうので、電池のモチを考えると改善すべき部分。接続時間を3秒以下に短縮する方法については本文側に掲載した。
void ap_disconnect()
WiFiアクセスポイントから切断するための関数。やはり歴史的に使い続けている。
bool wifi_send_data(float temp, float humi, float press, float vbat)
remote_host で指定のホストのポート80にtcp接続し、パラメータの温度、相対湿度、気圧および電池電圧をHTTP 1.1のGETリクエストで送信する。リクエスト内容は、/store_data.php?point_id=mac_addr&T=temp&H=humi&P=press&V=vbat 。
送信後、サーバーからのレスポンスを受け取りシリアルモニタに表示する。例えば、レスポンスヘッダやボディ内の文字列を解釈してボード側の動作を変更するようなこともあるので、丁寧に(?)受信している。
void setup()
まずはGPIO15に接続したLEDを点灯させ、BME280を初期化する。初期化に失敗した場合LEDを点滅させて停止する。続いてap_connect() でWiFiアクセスポイント(うちの場合は自宅のルーター)に接続するが、稀に接続が失敗することもあるので、その場合は30秒後に最初からやり直すことにしている。
続いて、system_adc_read() によってWROOM-02のTOUT端子に接続している分圧した電池電圧を測定し、分圧比を掛けて実電圧の100倍の値を得ている。電池電圧は、NiMH直列4本なので最大5.7Vを想定しており、内蔵ADCの分解能は1024ビットなので1ビットにつき0.0056Vを表していることになる。
そして、bmp280.measure() によって温度、相対湿度、気圧を得る。BME280のオーバーサンプリングは3対象ともx1としているので、測定時間は約10msecとしている。
得られた各値をシリアルモニタに表示し、wifi_send_data() を使ってサーバーに送る。最後に ap_disconnect() で切断し、deep_sleep(MEASURE_INTERVAL_SECONDS); によって次の測定タイミングまでdeep sleepする。
void loop() では何もしない。
hdc1000_bme280.h
#include <stdint.h> #include "Wire.h" class i2c_support { private: byte i2c_address; public: i2c_support(byte address) { i2c_address = address; Wire.begin(); } // read 8bit data from pointer. uint8_t i2c_read8(byte pointer) { Wire.beginTransmission(i2c_address); Wire.write(pointer); Wire.endTransmission(); Wire.requestFrom(i2c_address, (byte)1); return (uint8_t)Wire.read(); } // read 16bit data from addr/pointer. 1st MSB, 2nd LSB. uint16_t i2c_read16(byte pointer) { Wire.beginTransmission(i2c_address); Wire.write(pointer); Wire.endTransmission(); Wire.requestFrom(i2c_address, (byte)2); unsigned short data = Wire.read(); data <<= 8; data |= Wire.read(); return data; } // read 16bit data from addr/pointer. 1st LSB, 2ns MSB. uint16_t i2c_read16_swab(byte pointer) { Wire.beginTransmission(i2c_address); Wire.write(pointer); Wire.endTransmission(); Wire.requestFrom(i2c_address, (byte)2); uint16_t lsb = (uint16_t)Wire.read(); uint16_t msb = (uint16_t)(Wire.read() << 8); return msb | lsb; } // for HDC1000 uint32_t i2c_read32(byte pointer, int delay_ms = 0) { Wire.beginTransmission(i2c_address); Wire.write(pointer); // このタイミングでA/D変換が開始する。 Wire.endTransmission(); delay(delay_ms); Wire.requestFrom(i2c_address, (byte)4); unsigned int data = Wire.read(); data <<= 8; data |= Wire.read(); data <<= 8; data |= Wire.read(); data <<= 8; data |= Wire.read(); return data; } void i2c_read_burst(byte pointer, int byte_count, byte* p) { Wire.beginTransmission(i2c_address); Wire.write(pointer); Wire.endTransmission(); Wire.requestFrom(i2c_address, (byte)byte_count); while(Wire.available() && byte_count > 0) { *p++ = (byte)Wire.read(); byte_count--; } } void i2c_write_reg(byte reg, byte data) { Wire.beginTransmission(i2c_address); Wire.write(reg); Wire.write(data); Wire.endTransmission(); } }; class hdc1000 : public i2c_support { public: hdc1000() : i2c_support(0x40) { temp = 0.0; humi = 0.0; } enum { reg_measure = 0, reg_config = 2, reg_id = 0xff }; bool init() { i2c_write_reg(reg_config, 0x90); // 10000001B = RESET, 32bit MODE(temp << 16 + humi) return (i2c_read16(reg_id) == 0x1000); // check device id. } void measure() { uint32_t data = i2c_read32(reg_measure, 15); temp = (float)((data >> 16) / 65536.0) * 165.0 - 40.0; humi = (float)((data & 0xffff) / 65536.0) * 100.0; } float temp; float humi; }; #ifndef BME280_S32_t #define BME280_S32_t int32_t #endif #ifndef BME280_U32_t #define BME280_U32_t uint32_t #endif #ifndef BME280_S64_t #define BME280_S64_t int64_t #endif class bme280 : public i2c_support { bool bme280_flag; // true : bme280, false: bmp280 bool unknown_flag = false; public: bme280(int address ) : i2c_support(address) { temp = 0.0; humi = 0.0; press = 0.0; } // results. float temp; float humi; float press; enum { Tmeasure = 10 }; // oversamplinkg, x1 x1 x1 /// registers. enum { reg_id = 0xd0, reg_reset = 0xe0, reg_ctrl_hum = 0xf2, reg_ctrl_meas = 0xf4, reg_config = 0xf5 }; enum { reg_p_result = 0xf7, reg_t_result = 0xfa, reg_h_result = 0xfd }; // IIR Filter disable. // osrs_p = 3, osrs_t = 3 ---> 18bit either. bool init() { uint8_t id = i2c_read8(reg_id); // check device-id. if (id == 0x60) bme280_flag = true; else if (id == 0x58) bme280_flag = false; else return false; load_bme280_compensation_params(); i2c_write_reg(reg_config, 0); // no stand-by, no IIR filter, no SPI. return true; } // TEMP resolution = 16 + (osrs_t ? 1) bit, HUM resolution = 16 + (osrs_h ? 1) bit, // select forced mode. void measure() { if (bme280_flag) i2c_write_reg(reg_ctrl_hum, 1); // humi measurement control register.00000011 (osrs_h = 1); i2c_write_reg(reg_ctrl_meas, 0x25); // measurement control register. 00100101 (osrs_p = 1, osrs_t = 1, Forced mode); delay(Tmeasure); byte buffer[10]; memset(buffer, 0, sizeof(buffer)); i2c_read_burst(reg_p_result, 8, buffer); uint32_t raw_press = ((uint32_t)buffer[0] << 12) | ((uint32_t)buffer[1] << 4) | (((uint32_t)buffer[2] >> 4) & 0xf); uint32_t raw_temp = ((uint32_t)buffer[3] << 12) | ((uint32_t)buffer[4] << 4) | (((uint32_t)buffer[5] >> 4) & 0xf); uint16_t raw_humi = ((uint16_t)buffer[6] << 8) | (uint16_t)buffer[7]; // uint16_t raw_humi = i2c_read16(reg_h_result); #if 0 Serial.print("raw_press = "); Serial.println(raw_press, HEX); Serial.print("raw_temp = "); Serial.println(raw_temp, HEX); Serial.print("raw_humi = "); Serial.println(raw_humi, HEX); #endif temp = BME280_compensate_T_int32(raw_temp) / 100.0; press = BME280_compensate_P_int64(raw_press) / 25600.0; if (bme280_flag) humi = bme280_compensate_H_int32(raw_humi) / 1024.0; else humi = 0.0; // reset. // i2c_write_reg(reg_reset, 0xb6); } // from BST-BME280_DS001-10.pdf Chapter4.2.3 (rev.1.1) private: BME280_S32_t t_fine; // Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC. // t_fine carries fine temperature as global value BME280_S32_t BME280_compensate_T_int32(BME280_S32_t adc_T) { BME280_S32_t var1, var2, T; var1 = ((((adc_T>>3) - ((BME280_S32_t)dig_T1<<1))) * ((BME280_S32_t)dig_T2)) >> 11; var2 = (((((adc_T>>4) - ((BME280_S32_t)dig_T1)) * ((adc_T>>4) - ((BME280_S32_t)dig_T1))) >> 12) * ((BME280_S32_t)dig_T3)) >> 14; t_fine = var1 + var2; T = (t_fine * 5 + 128) >> 8; return T; } // Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits). // Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa BME280_U32_t BME280_compensate_P_int64(BME280_S32_t adc_P) { BME280_S64_t var1, var2, p; var1 = ((BME280_S64_t)t_fine) - 128000; var2 = var1 * var1 * (BME280_S64_t)dig_P6; var2 = var2 + ((var1*(BME280_S64_t)dig_P5)<<17); var2 = var2 + (((BME280_S64_t)dig_P4)<<35); var1 = ((var1 * var1 * (BME280_S64_t)dig_P3)>>8) + ((var1 * (BME280_S64_t)dig_P2)<<12); var1 = (((((BME280_S64_t)1)<<47)+var1))*((BME280_S64_t)dig_P1)>>33; if (var1 == 0) { return 0; // avoid exception caused by division by zero } p = 1048576 - adc_P; p = (((p<<31) - var2)*3125) / var1; var1 = (((BME280_S64_t)dig_P9) * (p>>13) * (p>>13)) >> 25; var2 = (((BME280_S64_t)dig_P8) * p) >> 19; p = ((p + var1 + var2) >> 8) + (((BME280_S64_t)dig_P7)<<4); return (BME280_U32_t)p; } // Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits). // Output value of “47445” represents 47445/1024 = 46.333 %RH BME280_U32_t bme280_compensate_H_int32(BME280_S32_t adc_H) { BME280_S32_t v_x1_u32r; v_x1_u32r = (t_fine - ((BME280_S32_t)76800)); v_x1_u32r = (((((adc_H << 14) - (((BME280_S32_t)dig_H4) << 20) - (((BME280_S32_t)dig_H5) * v_x1_u32r)) + ((BME280_S32_t)16384)) >> 15) * (((((((v_x1_u32r * ((BME280_S32_t)dig_H6)) >> 10) * (((v_x1_u32r * ((BME280_S32_t)dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) + ((BME280_S32_t)2097152)) * ((BME280_S32_t)dig_H2) + 8192) >> 14)); v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)dig_H1)) >> 4)); v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); return (BME280_U32_t)(v_x1_u32r>>12); } // Chapter4.2.2. Trimming parameters. uint16_t dig_T1; int16_t dig_T2; int16_t dig_T3; uint16_t dig_P1; int16_t dig_P2; int16_t dig_P3; int16_t dig_P4; int16_t dig_P5; int16_t dig_P6; int16_t dig_P7; int16_t dig_P8; int16_t dig_P9; uint8_t dig_H1; int16_t dig_H2; uint8_t dig_H3; int16_t dig_H4; int16_t dig_H5; uint8_t dig_H6; void load_bme280_compensation_params() { dig_T1 = i2c_read16_swab(0x88); dig_T2 = (int16_t)i2c_read16_swab( 0x8a); dig_T3 = (int16_t)i2c_read16_swab(0x8c); dig_P1 = i2c_read16_swab(0x8e); dig_P2 = (int16_t)i2c_read16_swab(0x90); dig_P3 = (int16_t)i2c_read16_swab(0x92); dig_P4 = (int16_t)i2c_read16_swab(0x94); dig_P5 = (int16_t)i2c_read16_swab(0x96); dig_P6 = (int16_t)i2c_read16_swab(0x98); dig_P7 = (int16_t)i2c_read16_swab(0x9a); dig_P8 = (int16_t)i2c_read16_swab(0x9c); dig_P9 = (int16_t)i2c_read16_swab(0x9e); if (bme280_flag) { dig_H1 = i2c_read8(0xa1); dig_H2 = (int16_t)i2c_read16_swab(0xe1); dig_H3 = i2c_read8(0xe3); uint16_t _e4 = (uint16_t)i2c_read8(0xe4); uint16_t _e5 = (uint16_t)i2c_read8(0xe5); uint16_t _e6 = (uint16_t)i2c_read8(0xe6); dig_H4 = (int16_t)((_e4 << 4) + (_e5 & 0xf)); dig_H5 = (int16_t)((_e6 << 4) + ((_e5 >> 4) & 0xf)); dig_H6 = (int16_t)i2c_read8(0xe7); } } };
内容については、以前の投稿を参照。
テーブル作成スクリプト
mysql (5.1)用の、データベースおよびテーブル作成用スクリプト。
-- create database envdata default character set utf8 collate utf8_general_ci; -- grant all privileges on envdata.* to envuser@localhost identified by 'xxxxxx'; use envdata; create table envdata( serial_no int not null AUTO_INCREMENT ,point_id varchar (20) not null ,T float (7,2) null ,H float (7,2) null ,P float (7,2) null ,V float (7,2) null ,X1 float (7,2) null ,X2 float (7,2) null ,post_datetime timestamp default CURRENT_TIMESTAMP not null ,PRIMARY KEY (serial_no) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- create table envdata2( serial_no int not null AUTO_INCREMENT ,point_id varchar (20) not null ,BMP_T float (7,2) null ,BMP_P float (7,2) null ,HDC_T float (7,2) null ,HDC_H float (7,2) null ,V float (7,2) null ,X1 float (7,2) null ,X2 float (7,2) null ,post_datetime timestamp default CURRENT_TIMESTAMP not null ,PRIMARY KEY (serial_no) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; --
テーブルenvdataにはBME280単独のボードからのデータを、envdata2には、BMP280とHDC1000を載せたボードからのデータを格納する。Tが温度、Hが相対湿度、Pが気圧、Vが電圧で、X1およびX2は拡張用(接続時間の格納など)。
サーバー側データベースインタフェースクラス(envdata_db.php)
データベース envdbに接続し測定データの追加や、テーブルに格納されているデータのクエリを行うためのクラス。Linux上のPHP 5.33で動作を確認した。
<?PHP class envdata_db { private $mysql; // コネクション private $last_error; private $sql; public function __construct(){ $this->mysql= mysqli_connect("localhost", "envuser", "xxxxxxx", "envdata"); $this->last_error = mysqli_connect_error(); } public function __destruct() { if ($this->mysql) mysqli_close($this->mysql); $this->mysql = FALSE ; } public function isConnect() { return ($this->mysql != FALSE); } public function getLast_error() { return $this->last_error; } public function getLast_sql() { return $this->sql; } public function insert_array($table, $ar) { header('cotent-type: text/html'); if (!$this->isConnect()) return FALSE; foreach($ar as $k => $v) { $s = isset($_GET[$k]) ? $_GET[$k] : ""; if (strlen($s) > 0) $ar[$k] = $s; } $cols = ""; $vals = ""; foreach($ar as $k => $v) { if (strlen($cols) > 0) $cols .= ","; $cols .= $k; if (strlen($vals) > 0) $vals .= ","; $vals .= is_string($v) ? "'" . mysqli_real_escape_string($this->mysql, $v) . "'" : $v; } $this->sql = "insert into " . $table . "(" . $cols . ") values (" . $vals . ")"; if (!mysqli_query($this->mysql, $this->sql)) { $this->last_error = mysqli_error($this->mysql); return FALSE ; } return TRUE; } public function query_json($ar, $sql, $condition, $sel_date = "", $hours = "") { if (!$this->isConnect()) return FALSE; $where = ""; $group = ""; $order = "post_datetime"; if ($sel_date != '' && $hours != '' && ctype_digit($hours)) { $num_mins = $hours * 60 + 30; if ($sel_date == 'current') $where = "post_datetime >= date_sub(current_timestamp, interval " . $num_mins . " minute) "; else $where = "post_datetime >= '" . $sel_date . "' and post_datetime <= date_add('" . $sel_date . "', interval " . $num_mins . " minute) "; } if ($condition == "interval") { if ($sel_date != '' && $hours != '') { $num_mins = $hours * 60 + 10; if ($sel_date == 'current') $where = "post_datetime >= date_sub(current_timestamp, interval " . $num_mins . " minute) "; else $where = "post_datetime >= '" . $sel_date . "' and post_datetime <= date_add('" . $sel_date . "', interval " . $num_mins . " minute) "; } $group = "DATE(post_datetime)"; } else if ($condition == "day_list" || $condition == "day_rlist") { $where = ""; $group = "DATE(post_datetime)"; $order .= $condition == "day_rlist" ? " desc" : " asc"; } else { if ($hours == "today") $where = "date(post_datetime) > date(current_timestamp - interval 1 day)"; else if ($sel_date != "" && $hours == "") // 以降全て $where = "date(post_datetime) >= '" . $sel_date . "'"; } $this->sql = $sql; if (strlen($where) > 0) $this->sql .= " where " . $where; if (strlen($group) > 0) $this->sql .= " group by " . $group; if (strlen($order) > 0) $this->sql .= " order by " . $order; if (($query_result = mysqli_query($this->mysql, $this->sql)) == FALSE) { $this->last_error = mysqli_error($this->mysql); return FALSE ; } $result_count = mysqli_num_rows($query_result); $result = array(); $idx = 0; while ($r = mysqli_fetch_assoc($query_result)) { $one = array('datetime' => str_replace("-", "/", $r['datetime'])); if (count($ar) > 0) { reset($ar); foreach($ar as $val) $one += array($val => (float)$r[$val]); } $result[$idx++] = $one; } mysqli_free_result($query_result); $r = json_encode(array('count' => $result_count, 'data' => $result)); $json_result = str_replace('\\/', '/', $r); // before php 5.4 return $json_result; } }
このクラスは、インスタンスが作成された時点でデータベースenvdataに接続し、デストラクト時に切断する。
public function insert_array($table, $ar)
文字列$tableで指定されるテーブルに、配列$arの内容にしたがってリクエストパラメータの読み取りや初期値の設定を行った後INSERTを行う。$arのキー名は$_GET[]からリクエストパラメータを得るときのパラメータ名とINSERT時のカラム名を兼ねている。所定のパラメータが存在しない場合には、呼び出し時に$arが保持しているキー名に対応する値を設定する。
public function query_json($ar, $sql, $condition, $sel_date, $hours)
テーブルから測定データのクエリを行い、json形式にして返す。このメソッドは、GoogleChartでグラフを描画するためのデータを得るために作成したもの。
$arにはカラム名(または別名)に一致したキー名の配列を与え、$sqlにはテーブル名を含むクエリ用のSQL文を指定する。$conditionはクエリの種別(単純なクエリ、インターバル表示用のクエリ、測定日を得るためのクエリ), $sel_date, $hoursにはクエリ条件を指定する。
以前作ったときはPHP5.5で動かしていたが、今回はPHP5.33を使っているため、json_encode(); のoptions に JSON_UNESCAPED_SLASHES を指定できない。そのため、データ内(この場合は日付)の”/”が”\/” とエスケープされてしまうため、str_replace(‘\\/’, ‘/’, $r); によって除去している。
測定データ格納用スクリプト(store_data.php)
envdata テーブルに測定データを格納するためのPHPスクリプト。測定ボード内のスケッチは、このスクリプトにGETリクエストを送る。
<?PHP require_once('envdata_db.php'); $ar = array('T' => 0.0, 'P' => 0.0, 'H' => 0.0, 'V' => 0.0, 'X1' => 0.0, 'X2' => 0.0, 'point_id' => 'unknown'); $db = new envdata_db(); if (!$db->insert_array("envdata", $ar)) { echo "NG SQL ERROR: " . $envdata->getLast_error() . '<br/>'; echo $envdata->getLast_sql() . '<br/>'; } else echo "OK"; ?>
リクエストパラメータ(T, P, H, V, X1, X2)と同じ名前のカラムにデータを格納する。配列$arは、リクエストパラメータのキー名とカラム名、それらに対応した省略値を保持する。
※ リクエストパラメータのpoint_id (現状はMACアドレス)には “:” が含まれているが、特に問題ないのでエスケープしていない。
わざわざarray() を作ったりテーブル名を引数にしているのは、カラム構成や名前の異なるテーブルを複数個作り、測定ボードごとに専用のテーブルを用いるようにしたため。例えばenvdata2テーブルに格納するための store_data2.phpは以下のようになる。
<?PHP require_once('envdata_db.php'); $ar = array('BMP_T' => 0.0, 'BMP_P' => 0.0, 'HDC_T' => 0.0, 'HDC_H' => 0.0, 'V' => 0.0, 'X1' => 0.0, 'X2' => 0.0, 'point_id' => 'unknown'); $db = new envdata_db(); if (!$db->insert_array("envdata2", $ar)) { echo "NG SQL ERROR: " . $envdata->getLast_error() . '<br/>'; echo $envdata->getLast_sql() . '<br/>'; } else echo "OK"; ?>
envdata_dbクラスのクエリ関係の使い方やGoogleChartを使った描画用JavaScriptについては、より省電力化を図ったスケッチと共に掲載する予定。