PageRenderTime 65ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/app/controllers/components/msg_generator.php

https://github.com/c4fcm/Community-Sign-Server
PHP | 359 lines | 266 code | 35 blank | 58 comment | 37 complexity | 46ba1be3ae054407a27c6bd3c1d3ecef MD5 | raw file
  1. <?php
  2. App::import('Xml');
  3. App::import('Helper', 'Time');
  4. // TODO: make this automaticly include the right files
  5. App::import('Lib', 'NextBus');
  6. App::import('Lib', 'MbtaRailTrain');
  7. App::import('Lib', 'NoaaWeather');
  8. App::import('Lib', 'DotwellCalendar');
  9. App::import('Lib', 'FfpcCalendar');
  10. App::import('Lib', 'UnionSqCalendar');
  11. App::import('Lib', 'CivicCalendar');
  12. App::import('Lib', 'SomervilleScoutCalendar');
  13. App::import('Lib', 'GrandRapidsCalendar');
  14. App::import('Lib', 'PatchCalendar');
  15. App::import('Lib', 'BirthdayCalendar');
  16. /**
  17. * This is the engine that stitches all the various content together.
  18. */
  19. class MsgGeneratorComponent extends Object {
  20. // TODO: support international timezones
  21. var $timezoneLookup = array(
  22. 0=>'America/New_York', // EDT
  23. 1=>'America/Chicago', // CDT
  24. 2=>'America/Boise', // MDT
  25. 3=>'America/Phoenix', // MST
  26. 4=>'America/Los_Angeles', // PDT
  27. );
  28. // HACK: these need to match the ids in the db feature table
  29. const FEATURE_BOSTON_WEATHER = 1;
  30. const FEATURE_DOTWELL_CAL = 2;
  31. const FEATURE_FFPC_CAL = 3;
  32. const FEATURE_UNION_SQ_CAL = 4;
  33. const FEATURE_CIVIC_CAL = 5;
  34. const FEATURE_SCOUT_CAL = 6;
  35. const FEATURE_GRAND_RAPIDS_WEATHER = 7;
  36. const FEATURE_GRAND_RAPIDS_CAL = 8;
  37. const FEATURE_JP_PATCH_CAL = 9;
  38. const FEATURE_BIRTHDAY_CAL = 10;
  39. const MAX_EVENTS_TO_SHOW = 5; // TODO: make this a setting on each installed display
  40. const EVENT_NAME_MAX_CHARS = 80; // events with names longer than this will be trimmed
  41. var $controller = null;
  42. /**
  43. * Make double-sure we are in UTC
  44. * @param $controller
  45. */
  46. function startup(&$controller) {
  47. date_default_timezone_set('UTC');
  48. $this->controller = $controller;
  49. $this->timeHelper = new TimeHelper();
  50. }
  51. /**
  52. * Translates unix time into display-local time.
  53. * @param $eventInfo
  54. * @param $displayTimezone
  55. */
  56. private function getEventTimeStr($eventInfo, $displayTimezone) {
  57. date_default_timezone_set( $displayTimezone );
  58. $timestamp = $eventInfo['startdate'];
  59. //print date("Y-m-d H:i:s", $eventInfo['startdate']);
  60. //exit();
  61. $ongoing = $eventInfo['ongoing'];
  62. $relTime = "";
  63. if($this->timeHelper->isToday($timestamp)) {
  64. $relTime = "Today";
  65. } else if($this->timeHelper->isTomorrow($timestamp)) {
  66. $relTime = date('D',$timestamp);
  67. } else {
  68. $relTime = date('D',$timestamp);
  69. }
  70. if($eventInfo['fullday']==false){
  71. $relTime.=" ".date('g',$timestamp);
  72. $minsStr = date('i',$timestamp);
  73. if($minsStr!="00") {
  74. $relTime.=":".$minsStr;
  75. }
  76. $relTime.=date('a',$timestamp);
  77. }
  78. date_default_timezone_set('UTC');
  79. return $relTime;
  80. }
  81. /**
  82. * This tries to be clever about adding in the location name of the end of the event name
  83. * @param unknown_type $event
  84. */
  85. private function getEventNameStr($event){
  86. $eventName = "";
  87. // Make sure the event name isn't too long
  88. if(strlen($event['summary'])>MsgGeneratorComponent::EVENT_NAME_MAX_CHARS){
  89. $eventName = substr($event['summary'],0,MsgGeneratorComponent::EVENT_NAME_MAX_CHARS)."...";
  90. } else {
  91. $eventName = $event['summary'];
  92. }
  93. // pull out location name
  94. $locationParts = split(",",$event['location']);
  95. $locName = $locationParts[0];
  96. if ($locName != ' '){
  97. $eventName = $eventName." (".$locName.")";
  98. }
  99. return $eventName;
  100. }
  101. /**
  102. * Change the list of events into an array of strings for display
  103. * @param unknown_type $events
  104. * @param unknown_type $displayTimezone
  105. */
  106. private function getEventTextList($events, $displayTimezone){
  107. $eventTexts = array();
  108. // show the next N one-time events
  109. if(count($events)>0){
  110. $oneTimeEvents = array_slice($events,0,MsgGeneratorComponent::MAX_EVENTS_TO_SHOW,true);
  111. foreach($oneTimeEvents as $timestamp=>$info){
  112. $eventName = $this->getEventNameStr( $info );
  113. $eventTexts[] = $this->getEventTimeStr($info, $displayTimezone)."\n".$eventName;
  114. }
  115. }
  116. return $eventTexts;
  117. }
  118. /**
  119. * Checks if message has an odd number of lines and fixes it (needed for two line displays to work
  120. * correctly)
  121. */
  122. private function fixOddLines($message){
  123. $lines = substr_count($message, "\n");
  124. if ($lines % 2 == 0){
  125. $message = $message."\n ";
  126. }
  127. return $message;
  128. }
  129. /**
  130. * This is the heart of this class.
  131. * Assemble the text that we should show on the sign based the stops it has
  132. * @param $displayId
  133. */
  134. function updateDisplayText($displayId){
  135. $nowStr = date("Y:m:d H:i:s");
  136. // need to instantiate all the content sources
  137. $nextBus = new NextBus();
  138. $mbtaRail = new MbtaRailTrain();
  139. $weather = new NoaaWeather();
  140. $dotwellCal = new DotwellCalendar();
  141. $ffpcCal = new FfpcCalendar();
  142. $unionSqCal = new UnionSqCalendar();
  143. $civicCal = new CivicCalendar();
  144. $scoutCal = new SomervilleScoutCalendar();
  145. $grCal = new GrandRapidsCalendar();
  146. $patchCal= new PatchCalendar();
  147. $birthCal = new BirthdayCalendar();
  148. // load the display we care about
  149. $display = $this->controller->Display->FindById($displayId);
  150. $this->log(" ".$display['Display']['name'].":",LOG_DEBUG);
  151. $str = "";
  152. $displayTimezone = $this->timezoneLookup[ $display['Display']['timezone'] ];
  153. // check if the display overriden by a custom message
  154. if(strlen($display['Display']['override_text'])>0) {
  155. //$str = $display['Display']['override_text'];
  156. $str = str_replace("\r"," ",$display['Display']['override_text']);
  157. $str = $this->fixOddLines($str);
  158. } else {
  159. // normal display, so stitch the content togther
  160. $msgs = array('predictions'=>array(),'texts'=>array());
  161. // get the weather
  162. $temperature = NoaaWeather::NO_PREDICTION_FOUND;
  163. if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_BOSTON_WEATHER)){
  164. $temperature = $weather->getCurrentTempFarenheit(NoaaWeather::BOSTON_FEED_NAME);
  165. } else if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_GRAND_RAPIDS_WEATHER)){
  166. $temperature = $weather->getCurrentTempFarenheit(NoaaWeather::GRAND_RAPIDS_FEED_NAME);
  167. }
  168. // get any special text to show
  169. $appendText = null;
  170. if( strlen($display['Display']['append_text'])>0 ){
  171. //$appendText = $display['Display']['append_text'];
  172. $appendText = str_replace("\r"," ",$display['Display']['append_text']);
  173. $appendText = $this->fixOddLines($appendText);
  174. }
  175. // merge all calendars together
  176. // TODO: make this smarter so that they are all time ordered in the end
  177. $eventText = array();
  178. if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_BIRTHDAY_CAL)){
  179. $events = $birthCal->getUpcomingEvents($displayTimezone);
  180. $eventText = array_merge($eventText,$this->getEventTextList($events, $displayTimezone));
  181. }
  182. if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_DOTWELL_CAL)){
  183. $events = $dotwellCal->getUpcomingEvents($displayTimezone);
  184. $eventText = array_merge($eventText,$this->getEventTextList($events, $displayTimezone));
  185. }
  186. if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_FFPC_CAL)){
  187. $events = $ffpcCal->getUpcomingEvents($displayTimezone);
  188. $eventText = array_merge($eventText,$this->getEventTextList($events, $displayTimezone));
  189. }
  190. if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_UNION_SQ_CAL)){
  191. $events = $unionSqCal->getUpcomingEvents($displayTimezone);
  192. $eventText = array_merge($eventText,$this->getEventTextList($events, $displayTimezone));
  193. }
  194. if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_CIVIC_CAL)){
  195. $events = $civicCal->getUpcomingEvents($displayTimezone);
  196. $eventText = array_merge($eventText,$this->getEventTextList($events, $displayTimezone));
  197. }
  198. if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_SCOUT_CAL)){
  199. $events = $scoutCal->getUpcomingEvents($displayTimezone);
  200. $eventText = array_merge($eventText,$this->getEventTextList($events, $displayTimezone));
  201. }
  202. if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_GRAND_RAPIDS_CAL)){
  203. $events = $grCal->getUpcomingEvents($displayTimezone);
  204. $eventText = array_merge($eventText,$this->getEventTextList($events, $displayTimezone));
  205. }
  206. if($this->controller->Display->hasFeature($display,MsgGeneratorComponent::FEATURE_JP_PATCH_CAL)){
  207. $events = $patchCal->getUpcomingEvents($displayTimezone);
  208. $eventText = array_merge($eventText,$this->getEventTextList($events, $displayTimezone));
  209. }
  210. // get any transit information
  211. foreach($display['Stop'] as $stop){
  212. $data = null;
  213. $dataIsOk = false;
  214. // query the approrpaite data source to get the data we want
  215. switch($stop['agency']){
  216. case Stop::AGENCY_MBTA_BUS:
  217. case Stop::AGENCY_MIT_SHUTTLE:
  218. $data = $nextBus->getPrediction($stop['agency'],$stop['route'],$stop['stop_key']);
  219. if($data!=NextBus::NO_PREDICTION_FOUND) {
  220. $dataIsOk = true;
  221. }
  222. break;
  223. case Stop::AGENCY_MBTA_T:
  224. $data = $mbtaRail->getPrediction($stop['route'],$stop['stop_key']);
  225. if($data!=MbtaRailTrain::NO_PREDICTION_FOUND) {
  226. $dataIsOk = true;
  227. }
  228. break;
  229. }
  230. if($dataIsOk==false){
  231. continue;
  232. }
  233. // assemble the text to show
  234. $description = "";
  235. if($stop['DisplayStop']['name']==null){
  236. switch($stop['agency']) {
  237. case Stop::AGENCY_MBTA_BUS:
  238. $description.= "#".$stop['route_name']." (".$stop['direction_name'].") ";
  239. break;
  240. case Stop::AGENCY_MIT_SHUTTLE:
  241. $description.= $stop['route_name']." ".$stop['direction_name']." ";
  242. break;
  243. case Stop::AGENCY_MBTA_T:
  244. $description.= $stop['route']." Line ".$stop['direction_name']." ";
  245. break;
  246. }
  247. } else {
  248. $description = $stop['DisplayStop']['name'];
  249. }
  250. $meta = "";
  251. if(intval($data)>0) {
  252. $meta = $data." ";
  253. $meta.= (($data>1) ? "mins " : "min ");
  254. } else {
  255. $meta = "Arriving ";
  256. }
  257. // and add it to the list (to be sorted later)
  258. $msgs['predictions'][] = intval($data);
  259. $msgs['texts'][] = array('meta'=>$meta,'description'=>$description);
  260. }
  261. // stitch it all together (sort by arrival time, soonest first)
  262. date_default_timezone_set( $displayTimezone );
  263. $currentTime = date('g:ia');
  264. date_default_timezone_set('UTC');
  265. array_multisort($msgs['predictions'], SORT_NUMERIC, $msgs['texts'], SORT_STRING);
  266. // prefix info
  267. switch($display['Display']['hardware_type']){
  268. case Display::HW_TYPE_ONE_LINE:
  269. $str = $currentTime." ||| ";
  270. if($temperature!=NoaaWeather::NO_PREDICTION_FOUND){
  271. $str.= $temperature."F ||| ";
  272. }
  273. break;
  274. case Display::HW_TYPE_TWO_LINE:
  275. $str = $currentTime."\n".$display['Display']['city'];
  276. if($temperature!=NoaaWeather::NO_PREDICTION_FOUND){
  277. $str.= " ".$temperature."F";
  278. }
  279. $str.= "\n";
  280. break;
  281. }
  282. // extra custom text
  283. if($appendText!=null){
  284. $str.=$appendText;
  285. switch($display['Display']['hardware_type']){
  286. case Display::HW_TYPE_ONE_LINE:
  287. $str.=" ||| ";
  288. break;
  289. case Display::HW_TYPE_TWO_LINE:
  290. $str.= "\n";
  291. break;
  292. }
  293. }
  294. // transit predictions
  295. foreach($msgs['texts'] as $info) {
  296. switch($display['Display']['hardware_type']){
  297. case Display::HW_TYPE_ONE_LINE:
  298. $str.= $info['description']." ".$info['meta']." ||| ";
  299. break;
  300. case Display::HW_TYPE_TWO_LINE:
  301. $str.= $info['meta']."\n".$info['description']."\n";
  302. break;
  303. }
  304. }
  305. //events
  306. foreach($eventText as $item){
  307. switch($display['Display']['hardware_type']){
  308. case Display::HW_TYPE_ONE_LINE:
  309. $str.= str_replace("\n"," ||| ",$item);
  310. break;
  311. case Display::HW_TYPE_TWO_LINE:
  312. $str.= $item."\n";
  313. break;
  314. }
  315. }
  316. }
  317. // and save/log it
  318. $this->log(" ".$str,LOG_DEBUG);
  319. $display['Display']['text'] = $str;
  320. $display['Display']['last_text_update'] = $nowStr;
  321. $this->controller->Display->save($display['Display']);
  322. }
  323. }
  324. ?>