annotate espNode/readcam.py @ 1754:92999dfbf321 default tip

add shelly support
author drewp@bigasterisk.com
date Tue, 04 Jun 2024 13:03:43 -0700
parents c77b5ab7b99d
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
1 #!camtest/bin/python3
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
2 import asyncio
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
3 import binascii
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
4 import io
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
5 import json
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
6 import logging
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
7 import os
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
8 import time
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
9
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
10 import apriltag
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
11 import cv2
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
12 import numpy
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
13 from aioesphomeapi.client import APIClient
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
14 from aioesphomeapi.model import CameraState
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
15 from aiohttp import web
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
16 from aiohttp.web import Response
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
17 from aiohttp_sse import sse_response
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
18 from docopt import docopt
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
19
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
20 logging.basicConfig(level=logging.INFO)
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
21 log = logging.getLogger(__name__)
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
22
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
23
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
24 class CameraReceiver:
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
25
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
26 def __init__(self, host):
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
27 self.lastFrameTime = None
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
28 self.host = host
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
29 self.lastFrame = b"", ''
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
30 self.recent = []
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
31
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
32 async def start(self):
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
33 try:
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
34 self.c = c = APIClient(self.host, 6053, 'MyPassword')
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
35 await c.connect(login=True)
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
36 await c.subscribe_states(on_state=self.on_state)
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
37 except OSError:
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
38 return
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
39 self.t = asyncio.create_task(
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
40 self.start_requesting_image_stream_forever())
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
41
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
42 async def start_requesting_image_stream_forever(self):
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
43 while True:
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
44 try:
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
45 await self.c.request_image_stream()
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
46 except AttributeError:
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
47 return
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
48 # https://github.com/esphome/esphome/blob/dev/esphome/components/esp32_camera/esp32_camera.cpp#L265 says a 'stream' is 5 sec long
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
49 await asyncio.sleep(4)
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
50
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
51 def on_state(self, s):
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
52 if isinstance(s, CameraState):
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
53 jpg = s.data
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
54 if len(self.recent) > 10:
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
55 self.recent = self.recent[-10:]
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
56
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
57 self.recent.append(jpg)
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
58 #print('recent lens: %s' % (','.join(str(len(x))
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
59 # for x in self.recent)))
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
60 else:
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
61 print('other on_state', s)
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
62
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
63 def analyze(self, jpg):
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
64 img = cv2.imdecode(numpy.asarray(bytearray(jpg)), cv2.IMREAD_GRAYSCALE)
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
65 result = detector.detect(img)
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
66 msg = {}
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
67 if result:
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
68 center = result[0].center
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
69 msg['center'] = [round(center[0], 2), round(center[1], 2)]
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
70 return msg
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
71
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
72 async def frames(self):
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
73 while True:
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
74 if self.recent:
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
75 if self.lastFrameTime and time.time() - self.lastFrameTime > 15:
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
76 print('no recent frames')
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
77 os.abort()
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
78
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
79 jpg = self.recent.pop(0)
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
80 msg = self.analyze(jpg)
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
81 yield jpg, msg
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
82 self.lastFrame = jpg, msg
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
83 self.lastFrameTime = time.time()
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
84 else:
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
85 await asyncio.sleep(.05)
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
86
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
87
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
88 def imageUri(jpg):
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
89 return 'data:image/jpeg;base64,' + binascii.b2a_base64(jpg).decode('ascii')
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
90
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
91
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
92 async def stream(request):
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
93 async with sse_response(request) as resp:
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
94 await resp.send(imageUri(recv.lastFrame[0]))
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
95 await resp.send(json.dumps(recv.lastFrame[1]))
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
96 async for frame, msg in recv.frames():
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
97 await resp.send(json.dumps(msg))
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
98 await resp.send(imageUri(frame))
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
99 return resp
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
100
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
101
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
102 async def index(request):
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
103 d = r"""
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
104 <html>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
105 <body>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
106 <style>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
107 #center {
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
108 position: absolute;
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
109 font-size: 35px;
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
110 color: orange;
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
111 text-shadow: black 0 1px 1px;
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
112 margin-left: -14px;
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
113 margin-top: -23px;
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
114 }
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
115 </style>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
116 <script>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
117 var evtSource = new EventSource("/stream");
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
118 evtSource.onmessage = function(e) {
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
119 if (e.data[0] == '{') {
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
120 const msg = JSON.parse(e.data);
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
121 const st = document.querySelector('#center').style;
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
122 if (msg.center) {
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
123 st.left = msg.center[0];
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
124 st.top = msg.center[1];
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
125 } else {
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
126 st.left = -999;
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
127 }
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
128 } else {
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
129 document.getElementById('response').src = e.data;
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
130 }
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
131 }
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
132 </script>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
133 <h1>Response from server:</h1>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
134 <div style="position: relative">
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
135 <img id="response"></img>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
136 <span id="center" style="position: absolute">&#x25ce;</span>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
137 </div>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
138 </body>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
139 </html>
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
140 """
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
141 return Response(text=d, content_type='text/html')
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
142
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
143 arguments = docopt('''
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
144 this
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
145
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
146 Usage:
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
147 this [-v] [--cam host] [--port to_serve]
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
148
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
149 Options:
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
150 --port n http server [default: 10020]
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
151 --cam host hostname of esphome server
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
152 ''')
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
153
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
154 logging.getLogger('aioesphomeapi.connection').setLevel(logging.INFO)
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
155
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
156 recv = CameraReceiver(arguments['--cam'])
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
157
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
158 detector = apriltag.Detector()
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
159
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
160 f = recv.start()
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
161 async def starter(app):
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
162 asyncio.create_task(f)
773
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
163
bc3516d02762 old changes in espNode
drewp@bigasterisk.com
parents: 726
diff changeset
164 start_time = time.time()
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
165 app = web.Application()
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
166 app.on_startup.append(starter)
685
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
167 app.router.add_route('GET', '/stream', stream)
7c5953801cf1 hall cam
drewp@bigasterisk.com
parents:
diff changeset
168 app.router.add_route('GET', '/', index)
1740
c77b5ab7b99d camera work
drewp@bigasterisk.com
parents: 773
diff changeset
169 web.run_app(app, port=int(arguments['--port']))