comparison front_door_lock.py @ 1:3b82ee3b9d79

cleanup
author drewp@bigasterisk.com
date Sun, 27 Aug 2023 11:20:30 -0700
parents 4365c72c59f6
children 89d47e203fc2
comparison
equal deleted inserted replaced
0:4365c72c59f6 1:3b82ee3b9d79
16 16
17 Plus, for reliability, a simpler web control ui. 17 Plus, for reliability, a simpler web control ui.
18 """ 18 """
19 19
20 import asyncio 20 import asyncio
21 from functools import partial
22 import logging 21 import logging
23 import time 22 import time
24 from dataclasses import dataclass 23 from dataclasses import dataclass
24 from functools import partial
25 from typing import Optional, cast 25 from typing import Optional, cast
26 26
27 import aiomqtt 27 import aiomqtt
28 import background_loop
29 from patchablegraph import PatchableGraph 28 from patchablegraph import PatchableGraph
30 from patchablegraph.handler import GraphEvents, StaticGraph 29 from patchablegraph.handler import GraphEvents, StaticGraph
30 from rdfdb.patch import Patch
31 from rdflib import Literal, Namespace, URIRef 31 from rdflib import Literal, Namespace, URIRef
32 from starlette.applications import Starlette 32 from starlette.applications import Starlette
33 from starlette.exceptions import HTTPException
33 from starlette.requests import Request 34 from starlette.requests import Request
34 from starlette.responses import JSONResponse 35 from starlette.responses import JSONResponse
35 from starlette.routing import Route 36 from starlette.routing import Route
36 from starlette.exceptions import HTTPException
37 from starlette_exporter import PrometheusMiddleware, handle_metrics 37 from starlette_exporter import PrometheusMiddleware, handle_metrics
38 38
39 from get_agent import Agent, getAgent 39 from get_agent import Agent, getAgent
40 from rdfdb.patch import Patch
41 40
42 logging.basicConfig(level=logging.INFO) 41 logging.basicConfig(level=logging.INFO)
43 log = logging.getLogger() 42 log = logging.getLogger()
44 43
45 ROOM = Namespace('http://projects.bigasterisk.com/room/') 44 ROOM = Namespace('http://projects.bigasterisk.com/room/')
56 sneakGraph = current.graph # current doesn't expose __contains__ 55 sneakGraph = current.graph # current doesn't expose __contains__
57 return JSONResponse({ 56 return JSONResponse({
58 "locked": (lockUri, ROOM['state'], ROOM['locked'], ctx) in sneakGraph, 57 "locked": (lockUri, ROOM['state'], ROOM['locked'], ctx) in sneakGraph,
59 "unlocked": (lockUri, ROOM['state'], ROOM['unlocked'], ctx) in sneakGraph, 58 "unlocked": (lockUri, ROOM['state'], ROOM['unlocked'], ctx) in sneakGraph,
60 }) 59 })
61
62 60
63 61
64 def patchObjectToNone(g: PatchableGraph, ctx, subj, pred): #missing feature for patchObject 62 def patchObjectToNone(g: PatchableGraph, ctx, subj, pred): #missing feature for patchObject
65 p = g.getObjectPatch(ctx, subj, pred, URIRef('unused')) 63 p = g.getObjectPatch(ctx, subj, pred, URIRef('unused'))
66 g.patch(Patch(delQuads=p.delQuads, addQuads=[])) 64 g.patch(Patch(delQuads=p.delQuads, addQuads=[]))
99 await self.lock(agent) 97 await self.lock(agent)
100 return 98 return
101 await asyncio.sleep(.7) 99 await asyncio.sleep(.7)
102 secUntil = round(end - now, 1) 100 secUntil = round(end - now, 1)
103 self.graph.patchObject(ctx, lockUri, ROOM['secondsUntilAutoLock'], Literal(secUntil)) 101 self.graph.patchObject(ctx, lockUri, ROOM['secondsUntilAutoLock'], Literal(secUntil))
104 log.info(f"{end-now} sec until autolock") 102 log.info(f"{secUntil} sec until autolock")
105 103
106 async def lock(self, agent: Agent | None): 104 async def lock(self, agent: Agent | None):
107 if agent is None: 105 if agent is None:
108 raise HTTPException(403) 106 raise HTTPException(403)
109 if self.mqtt is None: 107 if self.mqtt is None:
116 114
117 hw: LockHardware 115 hw: LockHardware
118 topicRoot: str = 'frontdoorlock' 116 topicRoot: str = 'frontdoorlock'
119 117
120 def startup(self): 118 def startup(self):
121 asyncio.create_task(self.go()) 119 asyncio.create_task(self._go())
122 120
123 async def go(self): 121 async def _go(self):
124 self.client = aiomqtt.Client("mosquitto-frontdoor", 10210, client_id="lock-service-%s" % time.time(), keepalive=6) 122 self.client = aiomqtt.Client("mosquitto-frontdoor", 10210, client_id="lock-service-%s" % time.time(), keepalive=6)
125 while True: 123 while True:
126 try: 124 try:
127 async with self.client: 125 async with self.client:
128 await self.handleMessages() 126 await self._handleMessages()
129 except aiomqtt.MqttError: 127 except aiomqtt.MqttError:
130 log.error('mqtt reconnecting', exc_info=True) 128 log.error('mqtt reconnecting', exc_info=True)
131 await asyncio.sleep(5) 129 await asyncio.sleep(5)
132 130
133 async def handleMessages(self): 131 async def _handleMessages(self):
134 async with self.client.messages() as messages: 132 async with self.client.messages() as messages:
135 await self.client.subscribe(self.topicRoot + '/#') 133 await self.client.subscribe(self.topicRoot + '/#')
136 async for message in messages: 134 async for message in messages:
137 try: 135 try:
138 self.onMessage(message) 136 self._onMessage(message)
139 except Exception: 137 except Exception:
140 log.error(f'onMessage {message=}', exc_info=True) 138 log.error(f'onMessage {message=}', exc_info=True)
141 await asyncio.sleep(1) 139 await asyncio.sleep(1)
142 140
143 async def sendStrikeCommand(self, value: bool): 141 async def sendStrikeCommand(self, value: bool):
144 await self.client.publish(self.topicRoot + '/switch/strike/command', 'ON' if value else 'OFF', qos=0, retain=False) 142 await self.client.publish(self.topicRoot + '/switch/strike/command', 'ON' if value else 'OFF', qos=0, retain=False)
145 143
146 def stateFromMqtt(self, payload: str) -> URIRef: 144 def _stateFromMqtt(self, payload: str) -> URIRef:
147 return { 145 return {
148 'OFF': ROOM['locked'], 146 'OFF': ROOM['locked'],
149 'ON': ROOM['unlocked'], 147 'ON': ROOM['unlocked'],
150 }[payload] 148 }[payload]
151 149
152 def onMessage(self, message: aiomqtt.Message): 150 def _onMessage(self, message: aiomqtt.Message):
153 subtopic = str(message.topic).partition(self.topicRoot + '/')[2] 151 subtopic = str(message.topic).partition(self.topicRoot + '/')[2]
154 payload = cast(bytes, message.payload).decode('utf-8') 152 payload = cast(bytes, message.payload).decode('utf-8')
155 match subtopic: 153 match subtopic:
156 case 'switch/strike/command': 154 case 'switch/strike/command':
157 log.info(f'command message: {subtopic} {payload=}') 155 log.info(f'command message: {subtopic} {payload=}')
158 case 'switch/strike/state': 156 case 'switch/strike/state':
159 log.info(f'hw reports strike state = {payload}') 157 log.info(f'hw reports strike state = {payload}')
160 self.hw.writeHwLockStateToGraph(self.stateFromMqtt(payload)) 158 self.hw.writeHwLockStateToGraph(self._stateFromMqtt(payload))
161 case 'status': 159 case 'status':
162 self.hw.setOnline(payload == 'online') 160 self.hw.setOnline(payload == 'online')
163 case 'debug': 161 case 'debug':
164 log.info(f'hw debug: {payload}') # note: may include ansi colors 162 log.info(f'hw debug: {payload}') # note: may include ansi colors
165 case _: 163 case _: