Mercurial > code > home > repos > front-door-lock
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 _: |