123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- <?php
- /* provides a table to store environmental data submitted by bosch bme sensors
- *
- * the table radondb.bosch_bme680 stores a timestamp time as seconds since unix epoch,
- * the sensor sending the data is stored, eg: 'H' is stored as 72,
- * and the sensors readings as integers, [celsius, humidity, pressure as pascals,
- * gas resistance, indoor air quality index, and iaq accuracy]
- *
- * to use as viewer:
- * provide name for the Bosch BME680 sensor and optionally a unix epoch range (...)
- *
- * function name(parameters) returns array:
- * get_BME860_humidityData('H', ...): ["time"=>{time}, "reading"=>{reading}, ...]
- * get_BME860_celsiusData('H', ...): ["time"=>{time}, "reading"=>{reading}, ...]
- * get_BME860_pressureData('H', ...): ["time"=>{time}, "reading"=>{reading}, ...]
- * get_BME860_gasData('H', ...): ["time"=>{time}, "reading"=>{reading}, ...]
- * get_BME860_iaqData('H', ...): ["time"=>{time}, "reading"=>{reading}, ...]
- * get_BME860_iaqAccuracyData('H', ...): ["time"=>{time}, "reading"=>{reading}, ...]
- * get_BME860_krumaylData('H', ...): ["time"=>{time}, "reading"=>{reading}, ...]
- * get_BME860_ukrumaylData('H', ...): ["time"=>{time}, "reading"=>{reading}, ...]
- * get_BME860_wesDust('H', ...): ["time"=>{time}, "reading"=>{reading}, ...]
- *
- * to use as submitter:
- * use HTTP POST, data will be saved to radondb.bosch_bme680
- */
- define("BME680_COLUMNS", "time sensor celsius humidity pascal gas_resistance indoor_air_quality iaq_accuracy");
- function save() {
- // connect to mysql database radondb
- $db = mysql_connect("localhost", "root", "secom8703");
- mysql_select_db("radondb", $db);
- $columns = explode(" ", BME680_COLUMNS);
- // make sure events table exists
- if(false === mysql_query("SELECT 1 FROM bosch_bme680 LIMIT 1", $db)) {
- $create_cols = implode(" INTEGER, ", $columns) . " INTEGER";
- mysql_query("CREATE TABLE bosch_bme680 ({$create_cols}, CONSTRAINT reading UNIQUE (time, sensor))", $db);
- }
- // prepare data - first store column names, then match column names to bme680 submitted data
- $table_cols = implode(",", $columns);
- if(false !== ($key = array_search("celsius", $columns))) $columns[$key] = "temperature";
- if(false !== ($key = array_search("pascal", $columns))) $columns[$key] = "pressure";
- if(false !== ($key = array_search("indoor_air_quality", $columns))) $columns[$key] = "iaq_(accuracy)";
- $values = post2toast($_POST, $columns);
- // data should just be a bunch of integers
- $values = implode(",", $values);
- if(false === mysql_query("INSERT INTO bosch_bme680 ({$table_cols}) VALUES ({$values})", $db)) {
- // event previously recorded
- } else /* event sucessfully stored */;
- // mysql needs clean-up
- mysql_close($db);
- exit("thanks");
- }
- // convert alphabetic sensor name to integer [A..Z] to [65..90]
- function eMa_sensorName($str) {
- return (int) ord(strtoupper($str[0]));
- }
- function post2toast($post, $columns) {
- // either manually provide time or overwrite it
- $post["time"] = time();
- // convert sensor ASCII character (A..Z) into integer value represented using a string
- if(isset($post["sensor"])) $post["sensor"] = strval(eMa_sensorName($post["sensor"][0])) . ' ';
- $arr = array();
- foreach($columns as $k => $v) {
- if(isset($post[$v])) $arr[$k] = trim($post[$v]);
- }
- // handle splitting iaq into index and accuracy
- if(false !== ($key = array_search("iaq_accuracy", $columns))) {
- if(false !== ($key2 = array_search("iaq_(accuracy)", $columns))) {
- $chomp = strtok($arr[$key2], ' ');
- $arr[$key] = trim(strtok(''), " \n()") . /* needs whitespace for later */ ' ';
- }
- }
- // convert all values into integers, round floats
- foreach($arr as $k => $v) {
- $arr[$k] = (int) round(getFirstWord($v));
- }
- return $arr;
- }
- // confusing, word is meant to be first number
- function getFirstWord($v) {
- $v = str_split(strtok(trim($v), ' '), 1);
- $s = "";
- foreach($v as $t) {
- if('.' === $t) { $s .= '.'; continue; }
- if((0x29 < ord($t)) && (0x40 > ord($t))) $s .= $t;
- else break;
- }
- return $s;
- }
- // save data sent to us (HTTP POST terminate here)
- if(!empty($_POST)) save();
- // otherwise we are providing some additional functionality ...
- define("GET_LAST_READING", PHP_INT_MAX);
- function getColumnHelper($type, $sensor = 'H', $queryOptions = NULL) {
- /* Prior to using query options start and end could have been used to
- * specify sensor samples between two dates, this feature was not really
- * used. Query Options overlap this feature. Under normal conditions
- * using Query Options also retrieves data from a cache rather than from
- * the MySQL database.
- *
- * using a default QueryOptions object should not use the cache, if you
- * want to still make use of start and end, setting them in the QueryOptions
- * is supported
- */
- $start = false;
- $end = false;
- // expand query options if they are provided
- if((NULL !== $queryOptions) and ("QueryOptions" === get_class($queryOptions))) {
- if($queryOptions->useCache()) {
- $rows = CACHE_DB::getCachedData($sensor, $type,
- $queryOptions->zoom,
- $queryOptions->fir,
- $queryOptions->start,
- $queryOptions->end
- );
- //return $rows;
- // add the most recent non-cached bosch bme680 sensor readings [mySQL]
- $lastReading = bosch_bme_680_query($type, $sensor, GET_LAST_READING);
- if($lastReading) {
- if(defined("ARRAY_OF_TIME_READING_PAIRS")) {
- $rows[] = array_pop($lastReading);
- } else {
- $rows[0][] = $lastReading[0]["time"];
- $rows[1][] = $lastReading[0]["reading"];
- }
- }
- return $rows;
- } // else - possibly get the start/end, use MySQL instead
- /* continuing down this path will just use the MySQL database, we
- * can try to get the start and end seconds since UNIX epoch from
- * the Query Options though...
- */
- $start = $queryOptions->start;
- $end = $queryOptions->end;
- } // else
- return bosch_bme_680_query($type, $sensor, $start, $end);
- }
- function bosch_bme_680_query($type, $sensor = 'H', $start = false, $end = false) {
- // check if we are only supposed to get last reading, do this before start is modified
- $lastReading = GET_LAST_READING === $start;
- // create a list of columns that are okay to query
- $okay_columns = explode(" ", BME680_COLUMNS);
- if(false !== ($k = array_search("time", $okay_columns))) unset($okay_columns[$k]);
- if(false !== ($k = array_search("sensor", $okay_columns))) unset($okay_columns[$k]);
- // cancel this query if type is invalid
- if(false === array_search($type, $okay_columns)) {
- error_log("the requested type, {$type} must be one of: " . implode(',', $okay_columns));
- return array(/* empty array for you */);
- }
- // the sensor that you want has a different name, I will look it up
- $sensor = strval(eMa_sensorName($sensor));
-
- # connect to mysql database radondb
- $db = mysql_connect("localhost", "root", "secom8703");
- mysql_select_db("radondb", $db);
- // prevent injection in time interval input
- $start = intval($start);
- $end = intval($end);
- $and = (/* php zero is false */ $start and $end) ?"time >= {$start} AND time <= {$end} AND " :"";
- // add optional time interval to query
- $sql = "SELECT time, {$type} AS reading FROM `bosch_bme680` WHERE {$and}";
- // lookup by sensor, make sure query sorts results oldest event to newest
- $sql .= "sensor = {$sensor} ORDER BY time ASC";
- if($lastReading) {
- $sql = "SELECT time, {$type} AS reading FROM `bosch_bme680` WHERE ";
- $sql .= "sensor = {$sensor} ORDER BY time DESC LIMIT 1";
- }
- if(false === ($result = mysql_query($sql, $db))) {
- fwrite(STDERR, "{$type} query failed:" . PHP_EOL . mysql_error($db));
- return false;
- }
- $rows = array();
- while($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
- $rows[] = $row;
- //printRow($row);
- }
- mysql_free_result($result);
- # mysql needs clean-up
- mysql_close($db);
-
- return $rows;
- }
- // returns humidity percentage readings, with optional time range (unix epoch)
- function get_BME860_humidityData($sensor = 'H', $qo = NULL) {
- return getColumnHelper("humidity", $sensor, $qo);
- }
- // returns temperature Celsius readings, with optional time range (unix epoch)
- function get_BME860_celsiusData($sensor = 'H', $qo = NULL) {
- return getColumnHelper("celsius", $sensor, $qo);
- }
- // returns pressure pascal readings, with optional time range (unix epoch)
- function get_BME860_pressureData($sensor = 'H', $qo = NULL) {
- return getColumnHelper("pascal", $sensor, $qo);
- }
- // returns gas resistance readings, with optional time range (unix epoch)
- function get_BME860_gasData($sensor = 'H', $qo = NULL) {
- return getColumnHelper("gas_resistance", $sensor, $qo);
- }
- // returns indoor air quality index readings, with optional time range (unix epoch)
- function get_BME860_iaqData($sensor = 'H', $qo = NULL) {
- return getColumnHelper("indoor_air_quality", $sensor, $qo);
- }
- // returns IAQ accuracy readings, with optional time range (unix epoch)
- function get_BME860_iaqAccuracyData($sensor = 'H', $qo = NULL) {
- return getColumnHelper("iaq_accuracy", $sensor, $qo);
- }
- // returns temperature Fahrenheit readings, with optional time range (Unix epoch)
- function get_BME860_fahrenheitData($sensor = 'H', $qo = NULL) {
- $arr = getColumnHelper("celsius", $sensor, $qo);
- array_walk($arr, function(&$v) { $v["reading"] = 32 + round($v["reading"] * 9 / 5); });
- return $arr;
- }
- // crop integer values to 0..100
- function kug($x) { return $x > 0 ?$x < 100 ?$x :100 :0; }
- /* returns realative pressure readings, with optional time range (unix epoch)
- *
- * - a common elevation for people living in Iowa might be 270 meters above sea level.
- * - the highest pressure in Iowa occurred in 1989 and was recorded as 105452Pa
- * - the lowest pressure in Iowa occurred in 1940 and was recorded as 98408Pa
- * - in the eye of a hurricane or tornado it is possible to have a pressure of 87000Pa
- * - at the Dead Sea, at the lowest place on earth, 430 meters below sea level, and this
- * one weird time in 1961, you could get lucky and experience a pressure of 108100Pa,
- * normally it is 106500Pa.
- * - sea level pressure is 101325Pa, or one atmosphere (atm)
- * - the pressure at the peak of mt everest might be as low as 33500Pa
- *
- * "altitude (km) versus pressure (kPa)"
- * |
- * |
- * 25k-| *
- * |
- * |
- * 20k-| '*' represents average atmospheric pressure
- * | * '-' line points to peak of Mt Everest, highest
- * | above sea montain with an altiture of 8.8km
- * 15k-| * and an average pressure of 33700Pa
- * |
- * | *
- * 10k-| *
- * |----------.*
- * | ;'` '.*
- * 5km-| .`;.' `. *
- * |.`Mount`. `, *
- * | Everest , *
- * 0km |_____________________________*____
- * 0 10kPa 35kPa 70kPa 101.3kPa
- *
- * the krumayl is an experimental unit that is more representative of
- * pressure that one may experience in everyday use:
- * to increase sensitivity, a pressure of 108100Pa is going to be one hundred,
- * a pressure of 87000 will be zero. To scale existing readings:
- * (x - 87000) * (100 - 0) / (108100 - 87000) + 0 or simply (x - 87000)/211
- * a more sensitive unit, ukrumayl only considers pressures between 96kPa and 106kPa
- * (x - 96000) * (100 - 0) / (106000 - 96000) + 0 or simply (x - 96000)/100
- */
- function get_BME860_krumaylData($sensor = 'H', $qo = NULL) {
- $arr = getColumnHelper("pascal", $sensor, $qo);
- array_walk($arr, function(&$v) { $v["reading"] = kug(round(($v["reading"] - 87000)/211)); });
- return $arr;
- }
- function get_BME860_ukrumaylData($sensor = 'H', $qo = NULL) {
- $arr = getColumnHelper("pascal", $sensor, $qo);
- array_walk($arr, function(&$v) { $v["reading"] = kug(round(($v["reading"] - 96000)/100)); });
- return $arr;
- }
- // experimental and not well thought out unit representing gas resistance
- function get_BME860_wesDust($sensor = 'H', $qo = NULL) {
- $arr = getColumnHelper("gas_resistance", $sensor, $qo);
- array_walk($arr, function(&$v) { $v["reading"] = 100-(kug(round(($v["reading"] - 100000)/2000))); });
- return $arr;
- }
- //var_dump(get_BME860_iaqAccuracyData());
- //var_dump(get_BME860_iaqData());
- //var_dump(get_BME860_celsiusData());
- //var_dump(get_BME860_humidityData());
- //var_dump(get_BME860_gasData());
- //var_dump(get_BME860_pressureData());
|