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 を使ってビルド。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
#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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
#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)用の、データベースおよびテーブル作成用スクリプト。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
-- 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で動作を確認した。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
<?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リクエストを送る。
1 2 3 4 5 6 7 8 9 10 11 |
<?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は以下のようになる。
1 2 3 4 5 6 7 8 9 10 11 |
<?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については、より省電力化を図ったスケッチと共に掲載する予定。