Various changes including adding new stevens renderer.
authorScott Gasch <[email protected]>
Sun, 20 Mar 2022 05:37:32 +0000 (22:37 -0700)
committerScott Gasch <[email protected]>
Sun, 20 Mar 2022 05:37:32 +0000 (22:37 -0700)
chooser.py
file_writer.py
gcal_renderer.py
gkeep_renderer.py
kiosk.py
kiosk_constants.py [new file with mode: 0644]
myq_renderer.py
myq_trigger.py
renderer_catalog.py
stevens_renderer.py

index 3514c976cb7c7149f539cefd25952d91fa97bf30..813eaffa95a404a5958183d8796ced85cf2314f0 100644 (file)
@@ -10,7 +10,7 @@ from typing import Any, Callable, List, Optional, Set, Tuple
 
 import datetime_utils
 
-import constants
+import kiosk_constants
 import trigger
 
 
@@ -29,8 +29,8 @@ class chooser(ABC):
         filenames = []
         pages = [
             f
-            for f in os.listdir(constants.pages_dir)
-            if os.path.isfile(os.path.join(constants.pages_dir, f))
+            for f in os.listdir(kiosk_constants.pages_dir)
+            if os.path.isfile(os.path.join(kiosk_constants.pages_dir, f))
         ]
         for page in pages:
             result = re.match(valid_filename, page)
@@ -38,7 +38,7 @@ class chooser(ABC):
                 if result.group(3) != "none":
                     freshness_requirement = int(result.group(3))
                     last_modified = int(
-                        os.path.getmtime(os.path.join(constants.pages_dir, page))
+                        os.path.getmtime(os.path.join(kiosk_constants.pages_dir, page))
                     )
                     age = now - last_modified
                     if age > freshness_requirement:
index fced44939cddc73681d6008f94ee589caf97a8e2..beac7bbcd284861e4b3d8bf7bb419c9bdd9a51b5 100644 (file)
@@ -1,17 +1,17 @@
 #!/usr/bin/env python3
 
-import constants
 import os
 from uuid import uuid4
 
+import kiosk_constants
 
 class file_writer:
     """Helper context to write a pages file."""
 
     def __init__(self, filename: str, *, transformations=[]):
         temp = "temp-" + str(uuid4())
-        self.temp_filename = os.path.join(constants.pages_dir, temp)
-        self.full_filename = os.path.join(constants.pages_dir, filename)
+        self.temp_filename = os.path.join(kiosk_constants.pages_dir, temp)
+        self.full_filename = os.path.join(kiosk_constants.pages_dir, filename)
         self.xforms = [file_writer.remove_tricky_unicode]
         self.xforms.extend(transformations)
         self.f = None
index edb9546e6a35980a2a225c56d1b6c947ff81ca3a..1e026cd2e11797ae496dabcba5ec3c3e07f7a874 100644 (file)
@@ -13,7 +13,7 @@ from dateutil.parser import parse
 import gdata_oauth
 import pytz
 
-import constants
+import kiosk_constants
 import file_writer
 import globals
 import renderer
@@ -282,9 +282,9 @@ f"""
                     x = int(delta.total_seconds())
                     if x > 0:
                         identifier = "id%d" % count
-                        days = divmod(x, constants.seconds_per_day)
-                        hours = divmod(days[1], constants.seconds_per_hour)
-                        minutes = divmod(hours[1], constants.seconds_per_minute)
+                        days = divmod(x, kiosk_constants.seconds_per_day)
+                        hours = divmod(days[1], kiosk_constants.seconds_per_hour)
+                        minutes = divmod(hours[1], kiosk_constants.seconds_per_minute)
                         g.write(
                             f'<li><SPAN id="%s">%d days, %02d:%02d</SPAN> until %s</li>\n'
                             % (
@@ -349,7 +349,7 @@ var fn = setInterval(function() {
             return False
 
     def look_for_triggered_events(self) -> bool:
-        with file_writer.file_writer(constants.gcal_imminent_pagename) as f:
+        with file_writer.file_writer(kiosk_constants.gcal_imminent_pagename) as f:
             f.write("<h1>Imminent Upcoming Calendar Events:</h1>\n<hr>\n")
             f.write("<center><table width=99%>\n")
             now = datetime.datetime.now(pytz.timezone("US/Pacific"))
@@ -360,10 +360,10 @@ var fn = setInterval(function() {
                     continue
                 delta = eventstamp - now
                 x = int(delta.total_seconds())
-                if x > -120 and x <  4 * constants.seconds_per_minute:
-                    days = divmod(x, constants.seconds_per_day)
-                    hours = divmod(days[1], constants.seconds_per_hour)
-                    minutes = divmod(hours[1], constants.seconds_per_minute)
+                if x > -120 and x <  4 * kiosk_constants.seconds_per_minute:
+                    days = divmod(x, kiosk_constants.seconds_per_day)
+                    hours = divmod(days[1], kiosk_constants.seconds_per_hour)
+                    minutes = divmod(hours[1], kiosk_constants.seconds_per_minute)
                     eventstamp = event.start_time
                     name = event.friendly_name()
                     calendar = event.calendar
index d7765c8a32950a3d21bfb7d71536f9731e3b418e..f910e309a4ef62771d4d67d3998b0f961bc04ba2 100644 (file)
@@ -7,7 +7,7 @@ from typing import Dict
 
 import gkeepapi  # type: ignore
 
-import constants
+import kiosk_constants
 import file_writer
 import renderer
 import kiosk_secrets as secrets
@@ -132,7 +132,7 @@ class gkeep_renderer(renderer.abstaining_renderer):
                     f.write("</DIV>")
             else:
                 logger.debug(f"Note is empty, deleting {filename}.")
-                _ = os.path.join(constants.pages_dir, filename)
+                _ = os.path.join(kiosk_constants.pages_dir, filename)
                 try:
                     os.remove(_)
                 except:
index b1bdb1c7c7e9b6f8319b46288c5995fac95c95f2..01b7949bdec216273527451b66faa44908f8fdbb 100755 (executable)
--- a/kiosk.py
+++ b/kiosk.py
@@ -24,7 +24,7 @@ import config
 import datetime_utils
 import file_utils
 
-import constants
+import kiosk_constants
 import file_writer
 import renderer_catalog
 import chooser
@@ -142,9 +142,9 @@ def process_command(command: str, page_history: List[str], page_chooser) -> str:
                 break
     elif 'internal' in command:
         if 'render' in command:
-            page = constants.render_stats_pagename
+            page = kiosk_constants.render_stats_pagename
         else:
-            page = constants.render_stats_pagename
+            page = kiosk_constants.render_stats_pagename
     elif 'weather' in command:
         if 'telma' in command or 'cabin' in command:
             page = 'weather-telma_3_10800.html'
@@ -222,8 +222,8 @@ def thread_change_current(command_queue: Queue) -> None:
     page_chooser = chooser.weighted_random_chooser_with_triggers(
         trigger_catalog.get_triggers(), [filter_news_during_dinnertime]
     )
-    current_file = os.path.join(constants.pages_dir, "current.shtml")
-    emergency_file = os.path.join(constants.pages_dir, "reload_immediately.html")
+    current_file = os.path.join(kiosk_constants.pages_dir, "current.shtml")
+    emergency_file = os.path.join(kiosk_constants.pages_dir, "reload_immediately.html")
 
     # Main chooser loop
     while True:
@@ -255,7 +255,7 @@ def thread_change_current(command_queue: Queue) -> None:
         if triggered:
             if page != page_history[0] or (swap_page_target - now) < 10.0:
                 logger.info(f'chooser: An emergency page reload to {page} is needed at this time.')
-                swap_page_target = now + constants.emergency_refresh_period_sec
+                swap_page_target = now + kiosk_constants.emergency_refresh_period_sec
 
                 # Set current.shtml to the right page.
                 try:
@@ -263,7 +263,7 @@ def thread_change_current(command_queue: Queue) -> None:
                         emit(
                             f,
                             page,
-                            override_refresh_sec = constants.emergency_refresh_period_sec,
+                            override_refresh_sec = kiosk_constants.emergency_refresh_period_sec,
                             command = command
                         )
                     logger.debug(f'chooser: Wrote {current_file}.')
@@ -288,7 +288,7 @@ def thread_change_current(command_queue: Queue) -> None:
             logger.info(
                 f'chooser: Nominal choice of {page} as the next to show.'
             )
-            swap_page_target = now + constants.refresh_period_sec
+            swap_page_target = now + kiosk_constants.refresh_period_sec
             try:
                 with open(current_file, "w") as f:
                     emit(f, page)
@@ -346,9 +346,9 @@ def emit_wrapped(f,
             return float(override_refresh_sec * 1000.0)
         now = datetime.now(tz=pytz.timezone("US/Pacific"))
         if now.hour < 6:
-            return float(constants.refresh_period_night_sec * 1000.0)
+            return float(kiosk_constants.refresh_period_night_sec * 1000.0)
         else:
-            return float(constants.refresh_period_sec * 1000.0)
+            return float(kiosk_constants.refresh_period_sec * 1000.0)
 
     age = file_utils.describe_file_ctime(f"pages/{filename}")
     bgcolor = pick_background_color()
@@ -513,7 +513,7 @@ def emit_wrapped(f,
   setInterval(check_reload, 500);
   </SCRIPT>
 </HEAD>
-""" % constants.root_url)
+""" % kiosk_constants.root_url)
     f.write(f'<BODY BGCOLOR="#{bgcolor}">')
     f.write(
 """
@@ -573,7 +573,7 @@ def renderer_update_internal_stats_page(
     logger.info(
         'renderer: Updating internal render statistics page.'
     )
-    with file_writer.file_writer(constants.render_stats_pagename) as f:
+    with file_writer.file_writer(kiosk_constants.render_stats_pagename) as f:
         f.write(
 '''
 <CENTER>
@@ -669,7 +669,7 @@ f'''
         # Update a page about internal stats of renderers.
         renderer_update_internal_stats_page(last_render, render_counts, render_times)
         logger.info('renderer: having a little nap...')
-        time.sleep(constants.render_period_sec)
+        time.sleep(kiosk_constants.render_period_sec)
 
 
 @bootstrap.initialize
@@ -729,7 +729,7 @@ def main() -> None:
 
         # Have a little break and then check to make sure all threads are still alive.
         logger.debug('watchdog: having a little nap.')
-        time.sleep(constants.check_threads_period_sec)
+        time.sleep(kiosk_constants.check_threads_period_sec)
 
 
 if __name__ == "__main__":
diff --git a/kiosk_constants.py b/kiosk_constants.py
new file mode 100644 (file)
index 0000000..ce04f7e
--- /dev/null
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+
+hostname = 'kiosk.house'
+pages_dir = '/var/www/html/kiosk'
+root_url = f'http://{hostname}/kiosk'
+
+refresh_period_sec = 22.0
+emergency_refresh_period_sec = 45.0
+refresh_period_night_sec = 600.0
+render_period_sec = 30.0
+check_threads_period_sec = 60.0
+
+seconds_per_minute = 60
+seconds_per_hour = seconds_per_minute * 60
+seconds_per_day = seconds_per_hour * 24
+
+myq_pagename = "myq_4_300.html"
+render_stats_pagename = 'internal/render-stats_1_1000.html'
+gcal_imminent_pagename = "hidden/gcal-imminent_0_none.html"
index 037796375f577f4c488ac060f8e2787c2929aebc..2379a70dddba3995a3b691ff90dd9fb3b0a63d76 100644 (file)
@@ -9,7 +9,7 @@ from typing import Dict, Optional
 
 import datetime_utils
 
-import constants
+import kiosk_constants
 import file_writer
 import renderer
 import kiosk_secrets as secrets
@@ -43,7 +43,7 @@ class garage_door_renderer(renderer.abstaining_renderer):
             return len(self.doors) > 0
 
     def update_page(self) -> bool:
-        with file_writer.file_writer(constants.myq_pagename) as f:
+        with file_writer.file_writer(kiosk_constants.myq_pagename) as f:
             f.write(
                 f"""
 <H1>Garage Door Status</H1>
index fd256696446dbe5c60e0d6ae96abbda317b325fe..db09f339d77be9dec2efccb02c0f33ebfea54719 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-import constants
+import kiosk_constants
 import globals
 import trigger
 from typing import List, Optional, Tuple
@@ -10,6 +10,6 @@ class myq_trigger(trigger.trigger):
     def get_triggered_page_list(self) -> Optional[List[Tuple[str, int]]]:
         if globals.get("myq_triggered"):
             print("****** MyQ garage door is open page trigger ******")
-            return [(constants.myq_pagename, trigger.trigger.PRIORITY_HIGH)]
+            return [(kiosk_constants.myq_pagename, trigger.trigger.PRIORITY_HIGH)]
         else:
             return None
index 00a583445d931e5ff91577ee4d28377a71af7827..c62b926cf9cfcc8c0a4881af473745d5609b5d05 100644 (file)
@@ -2,7 +2,7 @@
 
 import bellevue_city_calendar_renderer
 import bellevue_reporter_rss_renderer
-import constants
+import kiosk_constants
 import cnn_rss_renderer
 import gdata_oauth
 import gcal_renderer
@@ -14,8 +14,8 @@ import mynorthwest_rss_renderer
 import myq_renderer
 import reddit_renderer
 import seattletimes_rss_renderer
+import stevens_renderer
 import kiosk_secrets as secrets
-import stranger_renderer
 import stock_renderer
 import twitter_renderer
 import urbanist_renderer
@@ -25,7 +25,7 @@ import wsj_rss_renderer
 
 seconds = 1
 minutes = 60
-hours = constants.seconds_per_hour
+hours = kiosk_constants.seconds_per_hour
 always = seconds * 1
 
 
@@ -93,6 +93,9 @@ __registry = [
         ["/rss/RSSMarketsMain.xml", "/rss/WSJcomUSBusiness.xml"],
         "WSJBusiness",
     ),
+    stevens_renderer.stevens_renderer(
+        {'Unused': (minutes * 30)}
+    ),
     google_news_rss_renderer.google_news_rss_renderer(
         {"Fetch News": (minutes * 30), "Shuffle News": (always)},
         "news.google.com",
index bba06030b90e7924327f5238ebc8bf0f3e2957be..0916aee9fd694b1a6a1b84e0bb251c6d459b8111 100644 (file)
 #!/usr/bin/env python3
 
-import http.client
-from typing import List, Dict
-import xml.etree.ElementTree as ET
+import datetime
+import json
+import logging
+import requests
+from typing import Dict
 
-import renderer
+import datetime_utils
 import file_writer
+import renderer
+
+
+logger = logging.getLogger(__file__)
+
 
+class stevens_renderer(renderer.abstaining_renderer):
+    URL = 'https://wsdot.com/Travel/Real-time/Service/api/MountainPass/Details/10'
 
-class stevens_pass_conditions_renderer(renderer.debuggable_abstaining_renderer):
-    """Renders a page about Stevens Pass conditions."""
-
-    def __init__(
-        self, name_to_timeout_dict: Dict[str, int], feed_site: str, feed_uris: List[str]
-    ) -> None:
-        super(stevens_pass_conditions_renderer, self).__init__(
-            name_to_timeout_dict, False
-        )
-        self.feed_site = feed_site
-        self.feed_uris = feed_uris
-
-    def debug_prefix(self) -> str:
-        return "stevens"
-
-    def periodic_render(self, key: str) -> bool:
-        with file_writer.file_writer("stevens-conditions_1_86400.html") as f:
-            for uri in self.feed_uris:
-                self.conn = http.client.HTTPSConnection(self.feed_site)
-                self.conn.request("GET", uri, None, {"Accept-Charset": "utf-8"})
-                response = self.conn.getresponse()
-                if response.status == 200:
-                    raw = response.read()
-                    rss = ET.fromstring(raw)
-                    channel = rss[0]
-                    for item in channel.getchildren():
-                        if item.tag == "title":
-                            f.write(f"<h1>{item.text}</h1><hr>")
-                            f.write(
-                                '<IMG WIDTH=512 ALIGN=RIGHT HEIGHT=382 SRC="https://images.wsdot.wa.gov/nc/002vc06430.jpg?t=637059938785646824" style="padding:8px;">'
-                            )
-                        elif item.tag == "item":
-                            for x in item.getchildren():
-                                if x.tag == "description":
-                                    text = x.text
-                                    if text is not None:
-                                        text = text.replace(
-                                            "<strong>Stevens Pass US2</strong><br/>", ""
-                                        )
-                                        text = text.replace("<br/><br/>", "<BR>")
-                                        text = text.replace(
-                                            "<strong>Elevation Meters:</strong>1238<BR>", ""
-                                        )
-                                    else:
-                                        text = ""
-                                    f.write(f"<P>\n{text}\n")
-                    return True
+    def __init__(self, name_to_timeout_dict: Dict[str, int]) -> None:
+        super().__init__(name_to_timeout_dict)
+
+    def render_conditions(mp: Dict[str, str], conditions: Dict[str, str]) -> str:
+        ret = f'''
+<TABLE>
+<TR>
+  <TD><B>temperature:</B></TD>
+  <TD>{conditions['temperature']}&deg;{conditions['temperatureUnit'][0]}</TD>
+</TR>
+<TR>
+  <TD><B>weather:</B></TD>
+  <TD>{conditions['weather']}</TD>
+</TR>
+<TR>
+  <TD><B>road:</B></TD>
+  <TD>{conditions['roadCondition']}</TD>
+</TR>'''
+        if 'restrictionOne' in conditions or 'restrictionTwo' in conditions:
+            ret += '''
+<TR>
+  <TD><B>restrictions:</B></TD>
+  <TD>'''
+            if 'restrictionOne' in conditions:
+                ret += f'''
+  {conditions['restrictionOne']['travelDirectionName']}:
+  {conditions['restrictionOne']['publicPage']}<BR>'''
+            if 'restrictionTwo' in conditions:
+                ret += f'''
+  {conditions['restrictionTwo']['travelDirectionName']}:
+  {conditions['restrictionTwo']['publicPage']}'''
+        ret += '</TR></TABLE>'
+        return ret
+
+    def render_forecast(forecasts: Dict[str, str]) -> str:
+        ret = '<TABLE>'
+        fc = forecasts['forecast']['forecastData']
+        for n, f in enumerate(fc):
+            color = ''
+            if n % 2 == 0:
+                color = ' BGCOLOR="#dfefff"'
+            ret += f'''
+<TR>
+  <TD{color}><B>{f['periodText']}</B></TD>
+  <TD{color}>{f['forecastText']}</TD>
+</TR>'''
+        ret += '</TABLE>'
+        return ret
+
+    def render_image(cameras: Dict[str, str]) -> str:
+        for camera in cameras:
+            if camera['cameraId'] == 8063:
+                return f'''
+<CENTER>
+  <IMG SRC={camera['cameraUrl']} WIDTH={camera['width']}>
+  <BR>
+  <B>{camera['cameraLabel']} ({camera['direction']})</B>
+</CENTER>'''
+        return ''
+
+    def periodic_render(self, unused: str) -> bool:
+        page = requests.get(stevens_renderer.URL)
+        if page.status_code == 200:
+            contents = json.loads(page.content)
+            mp = contents['mountainPass']
+            conditions = contents['condition']
+            cameras = contents['cameras']
+            forecasts = contents['stationForecasts'][0]
+            now = datetime_utils.now_pacific()
+            tss = conditions['displayDate']
+            tss = tss.replace('Z', '+00:00')
+            ts = datetime.datetime.strptime(tss, '%Y-%m-%dT%H:%M:%S.%f%z')
+            tss = datetime_utils.describe_timedelta_briefly(now - ts)
+            with file_writer.file_writer('stevens-conditions_5_3000.html') as f:
+                f.write(f'''
+<H2>Stevens Pass Conditions ~{tss} ago:</H2>
+<HR>
+<TABLE WIDTH=90%>
+<TR>
+  <TD>
+    {stevens_renderer.render_conditions(mp, conditions)}
+  </TD>
+  <TD>
+    {stevens_renderer.render_image(cameras)}
+  </TD>
+</TR>
+<TR>
+  <TD COLSPAN=2>
+    {stevens_renderer.render_forecast(forecasts)}
+  </TD>
+</TR>
+</TABLE>''')
+            return True
         return False
+
+test = stevens_renderer({"Test", 123})
+test.periodic_render("Test")