Somewhat large overhaul to move the kiosk towards using normal python
[kiosk.git] / myq_renderer.py
1 #!/usr/bin/env python3
2
3 from aiohttp import ClientSession
4 import asyncio
5 import datetime
6 from dateutil.parser import parse
7 import pymyq  # type: ignore
8 from typing import Dict, Optional
9
10 import constants
11 import file_writer
12 import renderer
13 import kiosk_secrets as secrets
14 import utils
15
16
17 class garage_door_renderer(renderer.abstaining_renderer):
18     def __init__(self, name_to_timeout_dict: Dict[str, int]) -> None:
19         super().__init__(name_to_timeout_dict)
20         self.doors: Optional[Dict] = None
21         self.last_update: Optional[datetime.datetime] = None
22
23     def debug_prefix(self) -> str:
24         return "myq"
25
26     def periodic_render(self, key: str) -> bool:
27         if key == "Poll MyQ":
28             self.last_update = datetime.datetime.now()
29             return asyncio.run(self.poll_myq())
30         elif key == "Update Page":
31             return self.update_page()
32         else:
33             raise Exception("Unknown operaiton")
34
35     async def poll_myq(self) -> bool:
36         async with ClientSession() as websession:
37             myq = await pymyq.login(
38                 secrets.myq_username, secrets.myq_password, websession
39             )
40             self.doors = myq.devices
41             assert(self.doors is not None)
42             return len(self.doors) > 0
43
44     def update_page(self) -> bool:
45         with file_writer.file_writer(constants.myq_pagename) as f:
46             f.write(
47                 f"""
48 <H1>Garage Door Status</H1>
49 <!-- Last updated at {self.last_update} -->
50 <HR>
51 <TABLE BORDER=0 WIDTH=99%>
52     <TR>
53 """
54             )
55             html = self.do_door("Near House")
56             if html is None:
57                 return False
58             f.write(html)
59
60             html = self.do_door("Middle Door")
61             if html is None:
62                 return False
63             f.write(html)
64             f.write(
65                 """
66     </TR>
67 </TABLE>"""
68             )
69         return True
70
71     def get_state_icon(self, state: str) -> str:
72         if state == "open":
73             return "/kiosk/images/garage_open.png"
74         elif state == "closed":
75             return "/kiosk/images/garage_closed.png"
76         elif state == "opening":
77             return "/kiosk/images/garage_opening.png"
78         elif state == "closing":
79             return "/kiosk/images/garage_closing.png"
80         else:
81             return str(state) + ", an unknown state for the door."
82
83     def do_door(self, name: str) -> Optional[str]:
84         if self.doors is None:
85             return None
86         for key in self.doors:
87             door = self.doors[key]
88             if door.name == name:
89                 j = self.doors[key].device_json
90                 state = j["state"]["door_state"]
91
92                 # "last_update": "2020-07-04T18:11:34.2981419Z"
93                 raw = j["state"]["last_update"]
94                 ts = parse(raw)
95                 tz_info = ts.tzinfo
96                 now = datetime.datetime.now(tz_info)
97                 delta = (now - ts).total_seconds()
98                 now = datetime.datetime.now()
99                 is_night = now.hour <= 7 or now.hour >= 21
100                 duration = utils.describe_duration_briefly(int(delta))
101                 width = 0
102                 if is_night and door.state == "open":
103                     color = "border-color: #ff0000;"
104                     width = 15
105                 else:
106                     color = ""
107                     width = 0
108                 return f"""
109 <TD WIDTH=49%>
110   <CENTER>
111   <FONT STYLE="font-size:26pt">{name}<BR>
112   <IMG SRC="{self.get_state_icon(state)}"
113        HEIGHT=250
114        STYLE="border-style: solid; border-width: {width}px; {color}">
115   <BR>
116   <B>{state}</B></FONT><BR>
117   for {duration}
118   </CENTER>
119 </TD>"""
120         return None
121
122
123 # Test
124 #x = garage_door_renderer({"Test": 1})
125 #x.periodic_render("Poll MyQ")
126 #x.periodic_render("Update Page")