<?phperror_reporting(E_ALL);libxml_use_internal_errors(true);// ********************* //// ***** FUNCTIONS ***** //// ********************* //class BDWeather { // Extract a time-layout structure from the overall document ($X) for a // given $Element. Basically, $Element has a time-layout attribute. The // value of that attribute specifies a time-layout element to use for // mapping the values in the element to a series of timestamps. public static function getLayout($X, $Element) { $layout = $Element->attributes()->{"time-layout"}; $times = self::getPath($X, "data/time-layout[layout-key='{$layout}']"); if (count($times) == 0) throw new Exception("Could not find time layout [{$layout}]"); return current($times); } // Do the basic rest call to get the weather data we want. public static function restCall($zip) { $params = array( "whichClient" => "NDFDgenMultiZipCode", "zipCodeList" => $zip, "product" => "time-series", "temp" => "temp", "wspd" => "wspd", "sky" => "sky", "precipa_r" => "precipa_r", ); $uri_base = "http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdXMLclient.php"; $uri = "{$uri_base}?" . http_build_query($params); $result = file_get_contents($uri); try { $X = new SimpleXMLElement($result); } catch (Exception $E) { // Add the result into the exception and rethrow it, so we can see what the bad XML was later. $E->result = $result; throw $E; } return $X; } // Given a SimpleXMLElement object, and an XPath path, do the search, and deal with possible error conditions. public static function getPath($X, $path) { if (!($X instanceof SimpleXMLElement)) throw new Exception("X is not a SimpleXMLElement. It's a [" . gettype($X) . "]."); $elements = $X->xpath($path); if (!is_array($elements)) throw new Exception("xpath call [{$path}] did not return an array. It returned a [" . gettype($elements) . "]."); return $elements; } // Standardized short time format. public static function shortTime($time) { return date("g", $time) . substr(date("a", $time), 0, 1); } // Given a zip code, return an array of formatted weather data. public static function getWeather($zip) { $X = self::restCall($zip); $fp = fopen("~/logs/xml_weather", "a"); fwrite($fp, date("c") . "\n\n"); fwrite($fp, $X->asXML() . "\n\n"); fclose($fp); // Get the 'temperature' element $temps = self::getPath($X, "data/parameters/temperature[@type='hourly']"); if (count($temps) == 0) throw new Exception("Did not find an hourly 'temperature' element."); // There will only be one, so fetch it out of the array. $X_Temp = current($temps); // Now get the temperature values. $temp_values = self::getPath($X_Temp, 'value'); if (count($temp_values) == 0) throw new Exception("Did not find any hourly temperature values."); // Get the 'wind-speed' element. $winds = self::getPath($X, "data/parameters/wind-speed"); if (count($winds) == 0) throw new Exception("Did not find a 'wind-speed' element."); $X_Wind = current($winds); $wind_values = self::getPath($X_Wind, 'value'); if (count($wind_values) == 0) throw new Exception("Did not find any wind speed values."); // Get the 'cloud-amount' element. $clouds = self::getPath($X, "data/parameters/cloud-amount"); if (count($clouds) == 0) throw new Exception("Did not find a 'cloud-amount' element."); $X_Cloud = current($clouds); $cloud_values = self::getPath($X_Cloud, 'value'); if (!is_array($temp_values)) throw new Exception("Did not find any cloud cover values."); $temps = array(); $winds = array(); $clouds = array(); $hours = array(); $start = null; $max_wind = 0; // What I want is to go through the array of dates, and for each one, get the next max/min temp. They correspond. $Times = self::getLayout($X, $X_Temp); foreach ($Times->{"start-valid-time"} as $Time) { if ($start === null) $start = date("H:i:s", strtotime((string)$Time)); $hours[] = self::shortTime(strtotime((string)$Time)); $temps[] = current($temp_values); $max_wind = max($max_wind, current($wind_values)); $winds[] = current($wind_values); $clouds[] = current($cloud_values); next($temp_values); next($wind_values); next($cloud_values); if (count($temps) == 4) break; } $out = array(); $out["hours"] = "{$zip}: " . implode(" ", $hours) . "\n"; $out["temps"] = "F* " . implode(",", $temps) . "\n"; $out["clouds"] = "Cl: " . implode(",", $clouds) . "\n"; $out["winds"] = "Wn: " . implode(",", $winds) . "\n"; if ($max_wind >= 20) $out["windsev"] = "Very windy\n"; else if ($max_wind >= 10) $out["windsev"] = "Windy\n"; else $out["windsev"] = ""; return $out; } // Format getWeather parts in the way Alpha likes. public static function formatAlpha($parts) { $msg = $parts["hours"]; $msg .= $parts["temps"]; $msg .= $parts["clouds"]; $msg .= $parts["winds"]; if (array_key_exists('weathers', $parts)) $msg .= $parts["weathers"]; return $msg; } // Format getWeather parts in the way Bravo likes. public static function formatBravo($parts) { $msg = $parts["hours"]; $msg .= $parts["temps"]; $msg .= $parts["clouds"]; $msg .= $parts["windsev"]; if (array_key_exists('weathers', $parts)) $msg .= $parts["weathers"]; return $msg; } // Send a text message to Alpha. public static function textAlpha($msg) { echo("Texting Alpha...\n"); $gateway_address = "foo@bar.net"; self::sendSMS($gateway_address, $msg); } // Send a text message to Bravo. public static function textBravo($msg) { echo("Texting Bravo...\n"); $gateway_address = "foo@bar.net"; self::sendSMS($gateway_address, $msg); } public static function sendSMS($addr, $msg) { $msg = date("c") . "\n" . substr($msg, 0, 140 - 37); mail($addr, "", $msg, "From: a@b.c"); }}// ***************** //// ***** LOGIC ***** //// ***************** //date_default_timezone_set('America/New_York');$dump = false;$text = false;if (php_sapi_name() == "cli") { if ($argc == 2 && $argv[1] == "dump") $dump = true; else if ($argc == 2 && array_search('text', $argv)) $text = true;} else if (php_sapi_name() == "cgi") { if (array_key_exists('dump', $_GET) && $_GET['dump'] == '1') $dump = true; else if (array_key_exists('text', $_GET) && $_GET['text'] == '1') $text = true;} else { echo("Only CLI and CGI are allowed. Exiting.\n"); exit;}if ($dump) { try { $X = BDWeather::restCall(10101); } catch (Exception $E) { $X = new SimpleXMLElement("<error>There was an error: [" . $E->getMessage() . "], [" . htmlspecialchars($E->result) . "]</error>"); } header("Content-Type: text/xml"); echo($X->asXML()); exit;} else { $sent = false; echo("<pre>\n"); $count = 0; $max = 20; $sec = 15; while ($count < $max) { $count++; echo(date("c") . "\n\n"); try { $msgs = array(); if (date("N") >= 6) { $parts = BDWeather::getWeather(10101); $msg = BDWeather::formatAlpha($parts); $msgs["alpha"] = $msg; echo($msg); var_dump(strlen($msg)); echo("\n"); $msg = BDWeather::formatBravo($parts); $msgs["bravo"] = $msg; echo($msg); var_dump(strlen($msg)); echo("\n"); } else { $parts = BDWeather::getWeather(10102); $msg = BDWeather::formatAlpha($parts); $msgs["alpha"] = $msg; echo($msg . "\n\n"); var_dump(strlen($msg)); echo("\n"); $parts = BDWeather::getWeather(10101); $msg = BDWeather::formatBravo($parts); $msgs["bravo"] = $msg; echo($msg . "\n\n"); var_dump(strlen($msg)); echo("\n"); } if ($text) { foreach ($msgs as $target => $msg) { if ($target == 'alpha') BDWeather::textAlpha($msg); if ($target == 'bravo') BDWeather::textBravo($msg); } } $count = $max; $sent = true; } catch (Exception $E) { echo("Something went wrong: [" . $E->getMessage() . "].\nSleeping for {$sec} seconds and trying again (this was attempt {$count} of {$max}).\n"); echo($E->getTraceAsString()); flush(); sleep($sec); } } if (!$sent) { echo("Could not get weather report.<br>"); if ($text) { echo("Sending failure text messages.<br>"); BDWeather::textAlpha("Could not get weather report. Check the log."); BDWeather::textBravo("Sorry, I couldn't generate the weather report today. Tell Alpha!"); } }}