imager: move and split into submodules
(cherry picked from commit 0716f25fb7b78140ed1960445810557ad9e6840c)
This commit is contained in:
parent
24aeee4c46
commit
7ad48da054
|
@ -26,10 +26,13 @@ import planktoscope.mqtt
|
||||||
import planktoscope.light
|
import planktoscope.light
|
||||||
|
|
||||||
# import planktoscope.streamer
|
# import planktoscope.streamer
|
||||||
import planktoscope.imager_state_machine
|
import planktoscope.imager.state_machine
|
||||||
|
|
||||||
# import raspimjpeg module
|
# import raspimjpeg module
|
||||||
import planktoscope.raspimjpeg
|
import planktoscope.imager.raspimjpeg
|
||||||
|
|
||||||
|
# import streamer module
|
||||||
|
import planktoscope.imager.streamer
|
||||||
|
|
||||||
# Integrity verification module
|
# Integrity verification module
|
||||||
import planktoscope.integrity
|
import planktoscope.integrity
|
||||||
|
@ -38,89 +41,11 @@ import planktoscope.integrity
|
||||||
import planktoscope.uuidName
|
import planktoscope.uuidName
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
# Libraries for the streaming server
|
||||||
# Streaming PiCamera over server
|
|
||||||
################################################################################
|
|
||||||
import socketserver
|
|
||||||
import http.server
|
|
||||||
import threading
|
import threading
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Classes for the PiCamera Streaming
|
|
||||||
################################################################################
|
|
||||||
class StreamingHandler(http.server.BaseHTTPRequestHandler):
|
|
||||||
# Webpage content containing the PiCamera Streaming
|
|
||||||
PAGE = """\
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>PlanktonScope v2 | PiCamera Streaming</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<img src="stream.mjpg" width="100%" height="100%" />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, delay, *args, **kwargs):
|
|
||||||
self.delay = delay
|
|
||||||
super(StreamingHandler, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@logger.catch
|
|
||||||
def do_GET(self):
|
|
||||||
if self.path == "/":
|
|
||||||
self.send_response(301)
|
|
||||||
self.send_header("Location", "/index.html")
|
|
||||||
self.end_headers()
|
|
||||||
elif self.path == "/index.html":
|
|
||||||
content = self.PAGE.encode("utf-8")
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header("Content-Type", "text/html")
|
|
||||||
self.send_header("Content-Length", len(content))
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(content)
|
|
||||||
elif self.path == "/stream.mjpg":
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header("Age", 0)
|
|
||||||
self.send_header("Cache-Control", "no-cache, private")
|
|
||||||
self.send_header("Pragma", "no-cache")
|
|
||||||
self.send_header(
|
|
||||||
"Content-Type", "multipart/x-mixed-replace; boundary=FRAME"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.end_headers()
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
with open("/dev/shm/mjpeg/cam.jpg", "rb") as jpeg: # nosec
|
|
||||||
frame = jpeg.read()
|
|
||||||
except FileNotFoundError as e:
|
|
||||||
logger.error(f"Camera has not been started yet")
|
|
||||||
time.sleep(5)
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(f"An exception occured {e}")
|
|
||||||
else:
|
|
||||||
self.wfile.write(b"--FRAME\r\n")
|
|
||||||
self.send_header("Content-Type", "image/jpeg")
|
|
||||||
self.send_header("Content-Length", len(frame))
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(frame)
|
|
||||||
self.wfile.write(b"\r\n")
|
|
||||||
time.sleep(self.delay)
|
|
||||||
|
|
||||||
except BrokenPipeError as e:
|
|
||||||
logger.info(f"Removed streaming client {self.client_address}")
|
|
||||||
else:
|
|
||||||
self.send_error(404)
|
|
||||||
self.end_headers()
|
|
||||||
|
|
||||||
|
|
||||||
class StreamingServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
|
||||||
allow_reuse_address = True
|
|
||||||
daemon_threads = True
|
|
||||||
|
|
||||||
|
|
||||||
logger.info("planktoscope.imager is loaded")
|
logger.info("planktoscope.imager is loaded")
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -158,7 +83,7 @@ class ImagerProcess(multiprocessing.Process):
|
||||||
self.__camera_type = configuration.get("camera_type", self.__camera_type)
|
self.__camera_type = configuration.get("camera_type", self.__camera_type)
|
||||||
|
|
||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
self.__imager = planktoscope.imager_state_machine.Imager()
|
self.__imager = planktoscope.imager.state_machine.Imager()
|
||||||
self.__img_goal = 0
|
self.__img_goal = 0
|
||||||
self.__img_done = 0
|
self.__img_done = 0
|
||||||
self.__sleep_before = None
|
self.__sleep_before = None
|
||||||
|
@ -170,7 +95,7 @@ class ImagerProcess(multiprocessing.Process):
|
||||||
|
|
||||||
# Initialise the camera and the process
|
# Initialise the camera and the process
|
||||||
# Also starts the streaming to the temporary file
|
# Also starts the streaming to the temporary file
|
||||||
self.__camera = planktoscope.raspimjpeg.raspimjpeg()
|
self.__camera = planktoscope.imager.raspimjpeg.raspimjpeg()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.__camera.start()
|
self.__camera.start()
|
||||||
|
@ -179,7 +104,7 @@ class ImagerProcess(multiprocessing.Process):
|
||||||
f"An exception has occured when starting up raspimjpeg: {e}"
|
f"An exception has occured when starting up raspimjpeg: {e}"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
self.__camera.start(true)
|
self.__camera.start(True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
f"A second exception has occured when starting up raspimjpeg: {e}"
|
f"A second exception has occured when starting up raspimjpeg: {e}"
|
||||||
|
@ -281,7 +206,7 @@ class ImagerProcess(multiprocessing.Process):
|
||||||
logger.error(f"The received message has the wrong argument {last_message}")
|
logger.error(f"The received message has the wrong argument {last_message}")
|
||||||
self.imager_client.client.publish("status/imager", '{"status":"Error"}')
|
self.imager_client.client.publish("status/imager", '{"status":"Error"}')
|
||||||
return
|
return
|
||||||
self.__imager.change(planktoscope.imager_state_machine.Imaging)
|
self.__imager.change(planktoscope.imager.state_machine.Imaging)
|
||||||
|
|
||||||
# Get duration to wait before an image from the different received arguments
|
# Get duration to wait before an image from the different received arguments
|
||||||
self.__sleep_before = float(last_message["sleep"])
|
self.__sleep_before = float(last_message["sleep"])
|
||||||
|
@ -496,7 +421,7 @@ class ImagerProcess(multiprocessing.Process):
|
||||||
)
|
)
|
||||||
if self.__imager.state.name == "waiting":
|
if self.__imager.state.name == "waiting":
|
||||||
if self.imager_client.msg["payload"]["status"] == "Done":
|
if self.imager_client.msg["payload"]["status"] == "Done":
|
||||||
self.__imager.change(planktoscope.imager_state_machine.Capture)
|
self.__imager.change(planktoscope.imager.state_machine.Capture)
|
||||||
self.imager_client.client.unsubscribe("status/pump")
|
self.imager_client.client.unsubscribe("status/pump")
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -634,7 +559,7 @@ class ImagerProcess(multiprocessing.Process):
|
||||||
|
|
||||||
self.__pump_message()
|
self.__pump_message()
|
||||||
|
|
||||||
self.__imager.change(planktoscope.imager_state_machine.Waiting)
|
self.__imager.change(planktoscope.imager.state_machine.Waiting)
|
||||||
|
|
||||||
def __state_capture(self):
|
def __state_capture(self):
|
||||||
planktoscope.light.imaging()
|
planktoscope.light.imaging()
|
||||||
|
@ -695,7 +620,7 @@ class ImagerProcess(multiprocessing.Process):
|
||||||
|
|
||||||
self.__pump_message()
|
self.__pump_message()
|
||||||
|
|
||||||
self.__imager.change(planktoscope.imager_state_machine.Waiting)
|
self.__imager.change(planktoscope.imager.state_machine.Waiting)
|
||||||
|
|
||||||
def __capture_error(self, message=""):
|
def __capture_error(self, message=""):
|
||||||
logger.error(f"An error occurred during the capture: {message}")
|
logger.error(f"An error occurred during the capture: {message}")
|
||||||
|
@ -709,7 +634,7 @@ class ImagerProcess(multiprocessing.Process):
|
||||||
self.__img_done = 0
|
self.__img_done = 0
|
||||||
self.__img_goal = 0
|
self.__img_goal = 0
|
||||||
self.__error = 0
|
self.__error = 0
|
||||||
self.__imager.change(planktoscope.imager_state_machine.Stop)
|
self.__imager.change(planktoscope.imager.state_machine.Stop)
|
||||||
else:
|
else:
|
||||||
self.__error += 1
|
self.__error += 1
|
||||||
self.imager_client.client.publish(
|
self.imager_client.client.publish(
|
||||||
|
@ -762,10 +687,12 @@ class ImagerProcess(multiprocessing.Process):
|
||||||
|
|
||||||
logger.info("Starting the streaming server thread")
|
logger.info("Starting the streaming server thread")
|
||||||
address = ("", 8000)
|
address = ("", 8000)
|
||||||
fps = 16
|
fps = 15
|
||||||
refresh_delay = 1 / fps
|
refresh_delay = 1 / fps
|
||||||
handler = functools.partial(StreamingHandler, refresh_delay)
|
handler = functools.partial(
|
||||||
server = StreamingServer(address, handler)
|
planktoscope.imager.streamer.StreamingHandler, refresh_delay
|
||||||
|
)
|
||||||
|
server = planktoscope.imager.streamer.StreamingServer(address, handler)
|
||||||
self.streaming_thread = threading.Thread(
|
self.streaming_thread = threading.Thread(
|
||||||
target=server.serve_forever, daemon=True
|
target=server.serve_forever, daemon=True
|
||||||
)
|
)
|
|
@ -53,12 +53,7 @@ class raspimjpeg(object):
|
||||||
if force:
|
if force:
|
||||||
# let's kill all rogue Raspimjpeg first
|
# let's kill all rogue Raspimjpeg first
|
||||||
try:
|
try:
|
||||||
subprocess.run( # nosec
|
self.killall()
|
||||||
"sudo killall -9 raspimjpeg".split(),
|
|
||||||
shell=True,
|
|
||||||
timeout=1,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Killing Raspimjpeg failed because of {e}")
|
logger.exception(f"Killing Raspimjpeg failed because of {e}")
|
||||||
# The input to this call are perfectly controlled
|
# The input to this call are perfectly controlled
|
||||||
|
@ -494,3 +489,8 @@ class raspimjpeg(object):
|
||||||
"""Kill the process."""
|
"""Kill the process."""
|
||||||
logger.debug("Killing raspimjpeg in a very dirty way")
|
logger.debug("Killing raspimjpeg in a very dirty way")
|
||||||
self.__process.terminate()
|
self.__process.terminate()
|
||||||
|
|
||||||
|
def killall(self):
|
||||||
|
"""Literally erases the raspimjpeg process(es)"""
|
||||||
|
logger.debug("Killing raspimjpeg in a very ugly dirty way")
|
||||||
|
subprocess.run("sudo killall -q -9 raspimjpeg".split(), timeout=1) # nosec
|
60
scripts/planktoscope/imager/streamer.py
Normal file
60
scripts/planktoscope/imager/streamer.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
import socketserver
|
||||||
|
import http.server
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Classes for the PiCamera Streaming
|
||||||
|
################################################################################
|
||||||
|
class StreamingHandler(http.server.BaseHTTPRequestHandler):
|
||||||
|
def __init__(self, delay, *args, **kwargs):
|
||||||
|
self.delay = delay
|
||||||
|
super(StreamingHandler, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path == "/":
|
||||||
|
self.send_response(301)
|
||||||
|
self.send_header("Location", "/stream.mjpg")
|
||||||
|
self.end_headers()
|
||||||
|
elif self.path == "/stream.mjpg":
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Age", 0)
|
||||||
|
self.send_header("Cache-Control", "no-cache, private")
|
||||||
|
self.send_header("Pragma", "no-cache")
|
||||||
|
self.send_header(
|
||||||
|
"Content-Type", "multipart/x-mixed-replace; boundary=FRAME"
|
||||||
|
)
|
||||||
|
self.end_headers()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
with open("/dev/shm/mjpeg/cam.jpg", "rb") as jpeg: # nosec
|
||||||
|
frame = jpeg.read()
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
logger.error(f"Camera has not been started yet")
|
||||||
|
time.sleep(5)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"An exception occured {e}")
|
||||||
|
else:
|
||||||
|
self.wfile.write(b"--FRAME\r\n")
|
||||||
|
self.send_header("Content-Type", "image/jpeg")
|
||||||
|
self.send_header("Content-Length", len(frame))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(frame)
|
||||||
|
self.wfile.write(b"\r\n")
|
||||||
|
time.sleep(self.delay)
|
||||||
|
|
||||||
|
except BrokenPipeError as e:
|
||||||
|
logger.info(f"Removed streaming client {self.client_address}")
|
||||||
|
else:
|
||||||
|
self.send_error(404)
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
|
||||||
|
class StreamingServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
||||||
|
allow_reuse_address = True
|
||||||
|
daemon_threads = True
|
Loading…
Reference in a new issue