{start}, "end"=>{end}], ...) * getSnowPeriods(...): returns array(["start"=>{start}, "end"=>{end}], ...) * getTemperatureData(...): returns array(["time"=>{time}, "reading"=>{reading}, ...) * getHumidityData(...): returns array(["time"=>{time}, "reading"=>{reading}, ...) * getRainData(...): returns array(["time"=>{time}, "reading"=>{reading}, ...) * getSnowData(...): returns array(["time"=>{time}, "reading"=>{reading}, ...) * * [?] these functions all take an optional start and end time parameters eg: * $lastWeekTemperatures = getTemperatureData(time() - strtotime("7 days", 0), time()); * * to extend this, three functions are modified and optionally a frontend oriented helper function could be * defined here to retrieve stored data. The functions to be modified are regexParse, which handles * searching the user description, and it's sister function typeLookup which encodes the type of data, like * "celcius", "rain", or "snow" as a number. One may want to modify the third function, saveEvents if * additional processing needs to occur on data as regexParse should only be finding the data of interest */ /* order 2021 Nov 14 6:30pm * aroydee 1-515-528-8009 * 14. vermicelli combo * 29. pad thai chicken * H4. green curry beef * vietnamese eggnog (2) * order will be ready in 25 minutes */ function scanDescription($text = false, $time = false) { # connect to mysql database radondb $db = mysql_connect("localhost", "root", "secom8703"); mysql_select_db("radondb", $db); # make sure events table exists if(false === mysql_query("SELECT 1 FROM `events` LIMIT 1", $db)) { mysql_query("CREATE TABLE `events` (start INTEGER, end INTEGER, type INTEGER, value INTEGER, CONSTRAINT event UNIQUE (start, end, type))", $db); } # either perform extraction on entire table, or just user text if(false /* entire table */ === $text) { fwrite(STDERR, "scanning radondb for temperature and humidity data" . PHP_EOL); $result = mysql_query("SELECT time, description FROM locationLog WHERE id = 'note';", $db); while($row = mysql_fetch_array($result, MYSQL_ASSOC)) { //printRow($row); saveEvents($db, regexParse($row["description"]), $row["time"]); } mysql_free_result($result); } else saveEvents($db, regexParse($text), (false === $time) ?time() :$time); # mysql needs clean-up mysql_close($db); } // returns stats from user descriptions function regexParse($s) { $stats = $t = array(); $s = strtolower($s); // do simple mathing based using NNN{symbol} pattern $terms = array("farenheit" => "f", "celcius" => "c", "humidity" => "%"); foreach($terms as $k=>$v) if(1 === preg_match("/\d{1,3}(?={$v})/", $s, $t)) $stats[$k] = $t[0]; // check for rain, and other keywords (do snow separately) $terms = array("rain"); foreach($terms as $v) if(false !== strpos($s, $v)) $stats[$v] = true; // when checking for snow, ignore if you also find melt if(false !== strpos($s, "snow")) { if(false === strpos($s, "melt")) $stats["snow"] = true; } return $stats; } // the database has a table events schema that includes types, defined in regexParse, the keys are immutable function typeLookup($s, $getArray = false) { $types = array( "farenheit" => 0, "celcius" => 1, "humidity" => 2, "rain" => 3, "snow" => 4 /* ... no changes to previous types and keys, add new types below */ ); if(!isset($types[$s])) return false; return $types[$s]; } // save the stats from a scanned description into the database as events function saveEvents($db, $stats, $time) { foreach($stats as $plaintextType=>$value) { // convert canonical integers to SQL INTEGER if(ctype_digit($value)) $value = intval($value); // convert boolean to SQL INTEGER if(true === $value) $value = 1; if(false === $value) $value = 0; // other values should be integers if(!is_int($value)) { fwrite(STDERR, "[!] stat {$plaintextType} has value {$value}, not an integer" . PHP_EOL); continue; } // stat type must be valid, also prevents unwanted SQL if(false === ($type = typeLookup($plaintextType))) { fwrite(STDERR, "[!] stat type {$plaintextType} is not defined in events schema" . PHP_EOL); continue; } // and also make sure nothing unsafe is coming through time $start = intval($time); $end = $start; // make rain or snow occur over time, 8 hours minimum if("rain" === $plaintextType || "snow" === $plaintextType) $end += (8/*hours*/ * 60 /*minutes*/ * 60 /*seconds*/); // save record if(false === mysql_query("INSERT INTO events (start, end, type, value) VALUES ({$start},{$end},{$type},{$value})", $db)) { // event previously recorded } else /* event sucessfully stored */; } } /* sometimes users input time intervals without an end time: * I might record that it is raining, then go take a six hour nap, upon waking I * might notice that it is still raining and wait until it stops and then record * that it stops. This will probably never happen. More likely I will record that * it is raining and then never say anything again. There is a possibility that * when I wake up I might record again that it is raining. We just assume that rain * always lasts at least 8-hours and that whenever there are overlapping mentions * of rain the intervals are merged, eg: not summed: * * |----------- rain 8-hours ------------| * |----------- rain 8-hours ------------| * * |-------------------------- merged --------------------------| */ function eightHourIntervalMerge($type, $start = false, $end = false) { if(false === ($rows = helperFunctionTemplate($type, $start, $end))) return false; $intervals = array(); $start = false; // rain/snow are added in 8-hour intervals, merge overlapping intervals foreach($rows as $k=>$row) { // record the begining of the interval if(false === $start) $start = $row["start"]; // goto next interval if current interval overlaps if(isset($rows[$k + 1]) and ($rows[$k + 1]["start"] <= $row["end"])) continue; // store the interval when there are no more overlaps $intervals[] = array("start"=>$start, "end"=>$row["end"]); $start = false; } return $intervals; } // returns array of ["time"=>{time}, "reading"=>{reading}] from events by type function getReadings($type, $start = false, $end = false) { if(false === ($rows = helperFunctionTemplate($type, $start, $end))) return false; $arr = array( /* [time => ..., reading => ...] */ ); foreach($rows as $row) $arr[] = array("time"=>$row["start"], "reading"=>$row["value"]); return $arr; } // returns merged time intervals of rain, with optional time range (unix epoch) function getRainPeriods($start = false, $end = false) { return eightHourIntervalMerge("rain", $start = false, $end = false); } // returns merged time intervals of snow, with optional time range (unix epoch) function getSnowPeriods($start = false, $end = false) { return eightHourIntervalMerge("snow", $start = false, $end = false); } // returns farenheit readings, with optional time range (unix epoch) function getTemperatureData($start = false, $end = false) { return getReadings("farenheit", $start, $end); } // returns humidity percentage readings, with optional time range (unix epoch) function getHumidityData($start = false, $end = false) { return getReadings("humidity", $start, $end); } // merged time intervals of snow, sampled and filtered function getSnowData($start = false, $end = false) { return intervalsToSamples($start, $end, "snow"); } // merged time intervals of rain, sampled and filtered function getRainData($start = false, $end = false) { return intervalsToSamples($start, $end, "rain"); } // merged time intervals of rain/snow, sampled and filtered function intervalsToSamples($start = false, $end = false, $type = "rain") { // make a set of numbers, between zero and one to use as start and stop data $points = /* arbitrary number, consider HighCharts - more points, more load */ 8; $startStopLookup = generateSoftStart($points); // make up the amount of time for a "soft start" $softTime = /* fraction of "length of rain" in time */ strtotime("8 hours", 0) / (2 * $points); // make up the first data point where there is no rain (selected from database manually) $arr = array(array("time"=> /* SELECT MIN(time) FROM radonLog */ 1631631600, "reading" => 0)); // remaining data points come from the "events" table $periods = eightHourIntervalMerge($type, $start = false, $end = false); // generate the graph, use soft start/stop data at beginging/end of intervals foreach($periods as $a) { // [?] $a["reading"] value is always one, soft starts are between zero and one // generate soft start values, separated by a made up amount of time $start = $a["start"]; for($i = 0;$i<$points; $i++) { $arr[] = array("time"=>$start, "reading" => $startStopLookup[$i]); $start += $softTime; } // generate soft stop values, separated by a made up amount of time $stop = $a["end"] - ($points * $softTime); for($i = $points; $i--;) { $arr[] = array("time"=>$stop, "reading" => $startStopLookup[$i]); $stop += $softTime; } } return $arr; } /* generate a soft curve to use instead of a harsh edge when displaying data * that would normally have a jarring on/off transition, user optionally * provides the number of points used to represent the wave * * [?] user should use the returned values in reverse for a "soft-stop" * * | .-'`` * | .` * | ,` * | * 180 degrees of a sinewave, offset by one * | ` and a phase shift of PI/2 gives a "soft- * | .` start" -- this opposes a "sharp" edge * | * of digital on/off, or a square wave * | ' (to make values between zero and one, scaled by half) * | .` * | ,' * |,.-'_____________________ */ function generateSoftStart($n = 6) { $arr = array(); for($i = 0; $i < $n; $i++) $arr[] = (1+sin(($i*2*M_PI_2)/($n-1) - M_PI_2))/2; return $arr; } function helperFunctionTemplate($type, $start = false, $end = false) { # 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) ?"start >= {$start} AND end <= {$end} AND" :""; // add optional time interval to query $sql = "SELECT start, end, value FROM `events` WHERE {$and}"; // lookup type, make sure query sorts results oldest event to newest $sql .= "type = " . typeLookup($type) . " ORDER BY start ASC"; 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; } //scanDescription(); //var_dump(getRainPeriods()); //var_dump(getSnowPeriods()); //var_dump(getHumidityData()); //var_dump(getTemperatureData()); //var_dump(getRainData()); // calling this without parameters will try to extract all data from all records in locationLog //scanDescription(); /* ------ stuff for debugging ------ */ // row contains ["time" => "...", "description" => "..."] function printRow($mySQL_assocRow) { $temp = ""; foreach($mySQL_assocRow as $k=>$v) { echo "{$v} "; if("description" !== $k) continue; $temp = implode(" ", regexParse($v)); } if("" !== $temp) echo "---- @{$mySQL_assocRow["time"]} {$temp}"; echo PHP_EOL; }