comparison espNode/readcam.py @ 1740:c77b5ab7b99d

camera work
author drewp@bigasterisk.com
date Fri, 01 Sep 2023 17:13:51 -0700
parents bc3516d02762
children
comparison
equal deleted inserted replaced
1739:28a3e35bc23f 1740:c77b5ab7b99d
1 #!camtest/bin/python3 1 #!camtest/bin/python3
2 import asyncio
2 import binascii 3 import binascii
4 import io
5 import json
3 import logging 6 import logging
7 import os
4 import time 8 import time
5 import io
6 import os
7 import json
8 from docopt import docopt
9 from standardservice.logsetup import log, verboseLogging
10 9
11 logging.basicConfig(level=logging.INFO) 10 import apriltag
11 import cv2
12 import numpy
13 from aioesphomeapi.client import APIClient
14 from aioesphomeapi.model import CameraState
12 from aiohttp import web 15 from aiohttp import web
13 from aiohttp.web import Response 16 from aiohttp.web import Response
14 from aiohttp_sse import sse_response 17 from aiohttp_sse import sse_response
18 from docopt import docopt
15 19
16 import asyncio 20 logging.basicConfig(level=logging.INFO)
21 log = logging.getLogger(__name__)
17 22
18 from aioesphomeapi import APIClient
19 from aioesphomeapi.model import CameraState
20 import apriltag
21 import cv2
22 import numpy
23 23
24 class CameraReceiver: 24 class CameraReceiver:
25 def __init__(self, loop, host): 25
26 def __init__(self, host):
26 self.lastFrameTime = None 27 self.lastFrameTime = None
27 self.loop = loop
28 self.host = host 28 self.host = host
29 self.lastFrame = b"", '' 29 self.lastFrame = b"", ''
30 self.recent = [] 30 self.recent = []
31 31
32 async def start(self): 32 async def start(self):
33 try: 33 try:
34 self.c = c = APIClient(self.loop, 34 self.c = c = APIClient(self.host, 6053, 'MyPassword')
35 self.host,
36 6053, 'MyPassword')
37 await c.connect(login=True) 35 await c.connect(login=True)
38 await c.subscribe_states(on_state=self.on_state) 36 await c.subscribe_states(on_state=self.on_state)
39 except OSError: 37 except OSError:
40 loop.stop()
41 return 38 return
42 self.loop.create_task(self.start_requesting_image_stream_forever()) 39 self.t = asyncio.create_task(
40 self.start_requesting_image_stream_forever())
43 41
44 async def start_requesting_image_stream_forever(self): 42 async def start_requesting_image_stream_forever(self):
45 while True: 43 while True:
46 try: 44 try:
47 await self.c.request_image_stream() 45 await self.c.request_image_stream()
48 except AttributeError: 46 except AttributeError:
49 self.loop.stop()
50 return 47 return
51 # https://github.com/esphome/esphome/blob/dev/esphome/components/esp32_camera/esp32_camera.cpp#L265 says a 'stream' is 5 sec long 48 # https://github.com/esphome/esphome/blob/dev/esphome/components/esp32_camera/esp32_camera.cpp#L265 says a 'stream' is 5 sec long
52 await asyncio.sleep(4) 49 await asyncio.sleep(4)
53 50
54 def on_state(self, s): 51 def on_state(self, s):
55 if isinstance(s, CameraState): 52 if isinstance(s, CameraState):
56 jpg = s.image 53 jpg = s.data
57 if len(self.recent) > 10: 54 if len(self.recent) > 10:
58 self.recent = self.recent[-10:] 55 self.recent = self.recent[-10:]
59 56
60 self.recent.append(jpg) 57 self.recent.append(jpg)
61 #print('recent lens: %s' % (','.join(str(len(x)) 58 #print('recent lens: %s' % (','.join(str(len(x))
62 # for x in self.recent))) 59 # for x in self.recent)))
63 else: 60 else:
64 print('other on_state', s) 61 print('other on_state', s)
65 62
66 def analyze(self, jpg): 63 def analyze(self, jpg):
67 img = cv2.imdecode(numpy.asarray(bytearray(jpg)), 64 img = cv2.imdecode(numpy.asarray(bytearray(jpg)), cv2.IMREAD_GRAYSCALE)
68 cv2.IMREAD_GRAYSCALE)
69 result = detector.detect(img) 65 result = detector.detect(img)
70 msg = {} 66 msg = {}
71 if result: 67 if result:
72 center = result[0].center 68 center = result[0].center
73 msg['center'] = [round(center[0], 2), round(center[1], 2)] 69 msg['center'] = [round(center[0], 2), round(center[1], 2)]
90 86
91 87
92 def imageUri(jpg): 88 def imageUri(jpg):
93 return 'data:image/jpeg;base64,' + binascii.b2a_base64(jpg).decode('ascii') 89 return 'data:image/jpeg;base64,' + binascii.b2a_base64(jpg).decode('ascii')
94 90
91
95 async def stream(request): 92 async def stream(request):
96 async with sse_response(request) as resp: 93 async with sse_response(request) as resp:
97 await resp.send(imageUri(recv.lastFrame[0])) 94 await resp.send(imageUri(recv.lastFrame[0]))
98 await resp.send(json.dumps(recv.lastFrame[1])) 95 await resp.send(json.dumps(recv.lastFrame[1]))
99 async for frame, msg in recv.frames(): 96 async for frame, msg in recv.frames():
100 await resp.send(json.dumps(msg)) 97 await resp.send(json.dumps(msg))
101 await resp.send(imageUri(frame)) 98 await resp.send(imageUri(frame))
102 return resp 99 return resp
100
103 101
104 async def index(request): 102 async def index(request):
105 d = r""" 103 d = r"""
106 <html> 104 <html>
107 <body> 105 <body>
144 142
145 arguments = docopt(''' 143 arguments = docopt('''
146 this 144 this
147 145
148 Usage: 146 Usage:
149 this [-v] [--cam host] [--port to_serve] 147 this [-v] [--cam host] [--port to_serve]
150 148
151 Options: 149 Options:
152 -v --verbose more log 150 --port n http server [default: 10020]
153 --port n http server [default: 10020] 151 --cam host hostname of esphome server
154 --cam host hostname of esphome server
155 ''') 152 ''')
156 153
157 verboseLogging(arguments['--verbose'])
158 logging.getLogger('aioesphomeapi.connection').setLevel(logging.INFO) 154 logging.getLogger('aioesphomeapi.connection').setLevel(logging.INFO)
159 155
160 loop = asyncio.get_event_loop() 156 recv = CameraReceiver(arguments['--cam'])
161 157
162 recv = CameraReceiver(loop, arguments['--cam'])
163 detector = apriltag.Detector() 158 detector = apriltag.Detector()
164 159
165 f = recv.start() 160 f = recv.start()
166 loop.create_task(f) 161 async def starter(app):
162 asyncio.create_task(f)
167 163
168 start_time = time.time() 164 start_time = time.time()
169 app = web.Application() 165 app = web.Application()
166 app.on_startup.append(starter)
170 app.router.add_route('GET', '/stream', stream) 167 app.router.add_route('GET', '/stream', stream)
171 app.router.add_route('GET', '/', index) 168 app.router.add_route('GET', '/', index)
172 try: 169 web.run_app(app, port=int(arguments['--port']))
173 web.run_app(app, host='0.0.0.0', port=int(arguments['--port']))
174 except RuntimeError as e:
175 log.error(e)
176 log.info(f'run_app stopped after {time.time() - start_time} sec')