Fix and re-enable the MyQ renderer. Small format tweaks to the Twitter renderer.
authorScott Gasch <[email protected]>
Sat, 4 Jul 2020 18:51:23 +0000 (11:51 -0700)
committerScott Gasch <[email protected]>
Sat, 4 Jul 2020 18:51:23 +0000 (11:51 -0700)
myq_renderer.py
renderer_catalog.py
twitter_renderer.py

index fa60642fb283ef080a50e2d52304450b29b7db5e..333c356a6e96891a26ddf8c36f743636c785b155 100644 (file)
-#!/usr/local/bin/python
+#!/usr/local/bin/python3.7
 
-import requests
-import os
-import json
-import time
+import pymyq
+from aiohttp import ClientSession
+import asyncio
 import constants
 import datetime
+from dateutil.parser import parse
 import file_writer
-import globals
-import http.client
-import json
 import renderer
 import secrets
 
-APP_ID = "Vj8pQggXLhLy0WHahglCD4N1nAkkXQtGYpq2HrHD7H1nvmbT55KqtN6RSF4ILB/i"
-HOST_URI = "myqexternal.myqdevice.com"
-LOGIN_ENDPOINT = "api/v4/User/Validate"
-DEVICE_LIST_ENDPOINT = "api/v4/UserDeviceDetails/Get"
-DEVICE_SET_ENDPOINT = "api/v4/DeviceAttribute/PutDeviceAttribute"
-
-class MyQDoor:
-    myq_device_id = None
-    myq_lamp_device_id = None
-    myq_device_descr = None
-    myq_device_update_ts = None
-    myq_device_state = None
-    myq_security_token = None
-
-    def __init__(self, device_id, lamp_id, descr, update_ts, state, token):
-        self.myq_device_id = device_id
-        self.myq_lamp_device_id = lamp_id
-        self.myq_device_descr = descr
-        self.myq_device_update_ts = update_ts
-        self.myq_device_state = state
-        self.myq_security_token = token
-
-    def update(self, update_ts, state):
-        self.myq_device_update_ts = update_ts
-        self.myq_device_state = state
-
-    def get_name(self):
-        return self.myq_device_descr
-
-    def get_update_ts(self):
-        return self.myq_device_update_ts
-
-    def open(self):
-        self.change_door_state("open")
-
-    def close(self):
-        self.change_door_state("close")
-
-    def lamp_on(self):
-        self.change_lamp_state("on")
-
-    def lamp_off(self):
-        self.change_lamp_state("off")
-
-    def get_status(self):
-        state = self.myq_device_state
-        if state == "1":
-            return "open"
-        elif state == "2":
-            return "closed"
-        elif state == "3":
-            return "stopped"
-        elif state == "4":
-            return "opening"
-        elif state == "5":
-            return "closing"
-        elif state == "8":
-            return "moving"
-        elif state == "9":
-            return "open"
-        else:
-            return str(state) + ", an unknown state for the door."
-
-    def get_state_icon(self):
-        state = self.myq_device_state
-        if (state == "1" or state == "9"):
-            return "/icons/garage_open.png"
-        elif (state == "2"):
-            return "/icons/garage_closed.png"
-        elif (state == "4"):
-            return "/icons/garage_opening.png"
-        elif (state == "5" or state == "8"):
-            return "/icons/garage_closing.png"
-        else:
-            return str(state) + ", an unknown state for the door."
-
-    def get_lamp_status(self):
-        state = self.check_lamp_state()
-        if state == "0":
-            return "off"
-        elif state == "1":
-            return "on"
-        else:
-            return "unknown"
-
-    def change_device_state(self, payload):
-        device_action = requests.put(
-            'https://{host_uri}/{device_set_endpoint}'.format(
-                host_uri=HOST_URI,
-                device_set_endpoint=DEVICE_SET_ENDPOINT),
-                data=payload,
-                headers={
-                    'MyQApplicationId': APP_ID,
-                    'SecurityToken': self.myq_security_token
-                }
-        )
-        return device_action.status_code == 200
-
-    def change_lamp_state(self, command):
-        newstate = 1 if command.lower() == "on" else 0
-        payload = {
-            "attributeName": "desiredlightstate",
-            "myQDeviceId": self.myq_lamp_device_id,
-            "AttributeValue": newstate
-        }
-        return self.change_device_state(payload)
-
-    def change_door_state(self, command):
-        open_close_state = 1 if command.lower() == "open" else 0
-        payload = {
-            'attributeName': 'desireddoorstate',
-            'myQDeviceId': self.myq_device_id,
-            'AttributeValue': open_close_state,
-        }
-        return self.change_device_state(payload)
-
-    def check_door_state(self):
-        return self.myq_device_state
-
-#    def check_lamp_state(self):
-#        return self.check_device_state(self.myq_lamp_device_id, "lightstate")
-
-    def __repr__(self):
-        return "MyQ device(%s/%s), last update %s, current state %s" % (
-            self.myq_device_descr,
-            self.myq_device_id,
-            self.myq_device_update_ts,
-            self.get_status());
-
-class MyQService:
-    myq_security_token = ""
-    myq_device_list = {}
-
-    def __init__(self, username, password):
-        self.username = username
-        self.password = password
-
-    def login(self):
-        params = {
-            'username': self.username,
-            'password': self.password
-        }
-        login = requests.post(
-                'https://{host_uri}/{login_endpoint}'.format(
-                    host_uri=HOST_URI,
-                    login_endpoint=LOGIN_ENDPOINT),
-                    json=params,
-                    headers={
-                        'MyQApplicationId': APP_ID
-                    }
-            )
-        auth = login.json()
-        self.myq_security_token = auth.get('SecurityToken')
-        return True
-
-    def get_raw_device_list(self):
-        devices = requests.get(
-            'https://{host_uri}/{device_list_endpoint}'.format(
-                host_uri=HOST_URI,
-                device_list_endpoint=DEVICE_LIST_ENDPOINT),
-                headers={
-                    'MyQApplicationId': APP_ID,
-                    'SecurityToken': self.myq_security_token
-                })
-        return devices.json().get('Devices')
-
-    def get_devices(self):
-        return self.myq_device_list
-
-    def update_devices(self):
-        devices = self.get_raw_device_list()
-        if devices is None:
-            return False
-
-        for dev in devices:
-            if dev["MyQDeviceTypeId"] != 7: continue
-            identifier = str(dev["MyQDeviceId"])
-            update_ts = None
-            state = None
-            name = None
-            for attr in dev["Attributes"]:
-                key = attr["AttributeDisplayName"]
-                value = attr["Value"]
-                if (key == "doorstate"):
-                    state = value
-                    ts = int(attr["UpdatedTime"]) / 1000.0
-                    update_ts = datetime.datetime.fromtimestamp(ts)
-                elif (key == "desc"):
-                    name = value
-            if (identifier in self.myq_device_list):
-                self.myq_device_list[identifier].update(
-                    update_ts, state)
-            else:
-                device = MyQDoor(identifier,
-                                 None,
-                                 name,
-                                 update_ts,
-                                 state,
-                                 self.myq_security_token)
-                self.myq_device_list[identifier] = device
-        return True
-
 class garage_door_renderer(renderer.debuggable_abstaining_renderer):
     def __init__(self, name_to_timeout_dict):
         super(garage_door_renderer, self).__init__(name_to_timeout_dict, False)
-        self.myq_service = MyQService(secrets.myq_username,
-                                      secrets.myq_password)
-        self.myq_service.login()
-        self.myq_service.update_devices()
+        self.doors = None
 
     def debug_prefix(self):
         return "myq"
 
     def periodic_render(self, key):
-        self.debug_print("*** Executing action %s ***" % key)
         if key == "Poll MyQ":
-            return self.myq_service.update_devices()
+            asyncio.get_event_loop().run_until_complete(self.poll_myq())
         elif key == "Update Page":
             return self.update_page()
         else:
             raise error("Unknown operaiton")
 
-    def do_door(self, name):
-        doors = self.myq_service.get_devices()
+    async def poll_myq(self):
+        async with ClientSession() as websession:
+            myq = await pymyq.login(secrets.myq_username,
+                                    secrets.myq_password,
+                                    websession)
+            self.doors = myq.devices
+
+    def update_page(self):
+        f = file_writer.file_writer(constants.myq_pagename)
         now = datetime.datetime.now()
-        is_night = now.hour <= 7 or now.hour >= 21
-        for key in doors:
-            door = doors[key]
-            if door.get_name() == name:
-                ts = door.get_update_ts()
-                delta = now - ts
-                d = int(delta.total_seconds())
-                days = divmod(d, constants.seconds_per_day)
+        f.write("""
+<H1>Garage Door Status</H1>
+<!-- Last updated at %s -->
+<HR>
+<TABLE BORDER=0 WIDTH=99%%>
+  <TR>
+""" % now)
+        html = self.do_door("Near House")
+        if html == None:
+            return False
+        f.write(html)
+
+        html = self.do_door("Middle Door")
+        if html == None:
+            return False
+        f.write(html)
+        f.write("""
+  </TR>
+</TABLE>""")
+        f.close()
+        return True
+
+    def get_state_icon(self, state):
+        if state == "open":
+            return "/icons/garage_open.png"
+        elif state == "closed":
+            return "/icons/garage_closed.png"
+        elif state == "opening":
+            return "/icons/garage_opening.png"
+        elif state == "closing":
+            return "/icons/garage_closing.png"
+        else:
+            return str(state) + ", an unknown state for the door."
+
+    def do_door(self, name):
+        for key in self.doors:
+            door = self.doors[key]
+            if door.name == name:
+                j = self.doors[key].json
+                state = j["state"]["door_state"]
+
+                # "last_update": "2020-07-04T18:11:34.2981419Z"
+                raw = j["state"]["last_update"]
+                ts = parse(raw)
+                tz_info = ts.tzinfo
+                now = datetime.datetime.now(tz_info)
+                delta = (now - ts).total_seconds()
+                now = datetime.datetime.now()
+                is_night = now.hour <= 7 or now.hour >= 21
+                days = divmod(delta, constants.seconds_per_day)
                 hours = divmod(days[1], constants.seconds_per_hour)
                 minutes = divmod(hours[1], constants.seconds_per_minute)
                 width = 0
@@ -267,34 +107,14 @@ class garage_door_renderer(renderer.debuggable_abstaining_renderer):
   for %d day(s), %02d:%02d.
   </CENTER>
 </TD>""" % (name,
-            door.get_state_icon(),
+            self.get_state_icon(state),
             width,
             color,
-            door.get_status(),
+            state,
             days[0], hours[0], minutes[0])
         return None
 
-    def update_page(self):
-        f = file_writer.file_writer(constants.myq_pagename)
-        now = datetime.datetime.now()
-        f.write("""
-<H1>Garage Door Status</H1>
-<!-- Last updated at %s -->
-<HR>
-<TABLE BORDER=0 WIDTH=99%%>
-  <TR>
-""" % now)
-        html = self.do_door("Near House")
-        if html == None:
-            return False
-        f.write(html)
-
-        html = self.do_door("Middle Door")
-        if html == None:
-            return False
-        f.write(html)
-        f.write("""
-  </TR>
-</TABLE>""")
-        f.close()
-        return True
+# Test
+#x = garage_door_renderer({"Test" : 1})
+#x.periodic_render("Poll MyQ")
+#x.periodic_render("Update Page")
index b3b3e112fd7cb2fab1f028a93ada9ae87aab823f..3cd8406b4d2b8e02770dc54682162d3874df829f 100644 (file)
@@ -43,9 +43,9 @@ __registry = [
                       "Shuffle Events" : (always)}),
 #                 pollen_renderer.pollen_count_renderer(
 #                     {"Poll" : (hours * 1)}),
-#                 myq_renderer.garage_door_renderer(
-#                     {"Poll MyQ" : (minutes * 5),
-#                      "Update Page" : (minutes * 5)}),
+                 myq_renderer.garage_door_renderer(
+                     {"Poll MyQ" : (minutes * 5),
+                      "Update Page" : (minutes * 5)}),
                  bellevue_reporter_rss_renderer.bellevue_reporter_rss_renderer(
                      {"Fetch News" : (hours * 1),
                       "Shuffle News" : (always)},
index 304cf81b7b69aa1dbc2aa6f148a1ad825b61ecc3..8a82e5e645731ba43eacb517db5c10891c138ca3 100644 (file)
@@ -72,8 +72,8 @@ class twitter_renderer(renderer.debuggable_abstaining_renderer):
         handle = self.handles_by_author[author]
         tweets = self.tweets_by_author[author]
         already_seen = set()
-        f = file_writer.file_writer('twitter_5_none.html')
-        f.write('<TABLE WIDTH=96%><TR><TD WIDTH=80%>')
+        f = file_writer.file_writer('twitter_10_none.html')
+        f.write('<TABLE WIDTH=96%><TR><TD WIDTH=86%>')
         f.write('<H2>%s (@%s)</H2></TD>\n' % (author, handle))
         f.write('<TD ALIGN="right" VALIGN="top">')
         f.write('<IMG SRC="twitter.png" WIDTH=42></TD></TR></TABLE>\n')