diff --git a/debian/control b/debian/control index d15a261e..7e87570d 100644 --- a/debian/control +++ b/debian/control @@ -10,6 +10,6 @@ Package: fabscanpi-server Architecture: all Homepage: http://fabscan.org X-Python-Version: ${python:Versions} -Depends: ${misc:Depends}, ${python:Depends}, python-opencv, libtbb2, python-pil, python-serial, python-pykka, python-picamera, python-rpi.gpio, python-semver, xvfb, meshlab, strace, avrdude, python-scipy +Depends: ${misc:Depends}, ${python:Depends}, python-opencv, python-tornado, libtbb2, python-pil, python-serial, python-pykka, python-picamera, python-rpi.gpio, python-semver, xvfb, meshlab, strace, avrdude, python-scipy Description: FabScanPi Server ist the backend of the Stand-alone, Web-enabled, Open Source 3D laser scanner. diff --git a/requirements.txt b/requirements.txt index 1dd7ae7c..9fdfb839 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ pillow ==2.7 Pykka >=1.2 numpy >=1.9 semver >=2.0 +tornado mock \ No newline at end of file diff --git a/src/fabscan/__init__.py b/src/fabscan/__init__.py index c069ca86..ec7843cc 100755 --- a/src/fabscan/__init__.py +++ b/src/fabscan/__init__.py @@ -4,7 +4,7 @@ __maintainer__ = "Mario Lukas" __email__ = "info@mariolukas.de" -from fabscan.server import FSServer +from fabscan.server import FSScanServer from fabscan.daemon import Daemon from fabscan.FSVersion import __version__ import logging @@ -28,10 +28,10 @@ def __init__(self, pidfile, configfile, basedir, host, port, debug, allowRoot, l def run(self): - #server = FSServer(config_file) - #fabscan = FSServer(self._configfile, self._basedir, self._host, self._port, self._debug, self._allowRoot) + #server = FSScanServer(config_file) + #fabscan = FSScanServer(self._configfile, self._basedir, self._host, self._port, self._debug, self._allowRoot) - fabscan = FSServer(self._configfile) + fabscan = FSScanServer(self._configfile) fabscan.run() def main(): @@ -130,7 +130,7 @@ def main(): elif "restart" == args.daemon: daemon.restart() else: - fabscan = FSServer(args.config, args.settings) + fabscan = FSScanServer(args.config, args.settings) fabscan.run() except Exception, e: logger.fatal("Fatal error: %s", e) diff --git a/src/fabscan/scanner/laserscanner/FSCamera.py b/src/fabscan/scanner/laserscanner/FSCamera.py index 6c495a5d..fd260311 100755 --- a/src/fabscan/scanner/laserscanner/FSCamera.py +++ b/src/fabscan/scanner/laserscanner/FSCamera.py @@ -286,68 +286,412 @@ def flush_stream(self): settings=SettingsInterface ) class USBCam(threading.Thread): - camera = None - def __init__(self, cam_ring_buffer, config, settings): - threading.Thread.__init__(self) - self._logger = logging.getLogger(__name__) + __author__ = "Mario Lukas" + __copyright__ = "Copyright 2017" + __license__ = "GPL v2" + __maintainer__ = "Mario Lukas" + __email__ = "info@mariolukas.de" + + import cv2 + import logging + import numpy as np + import io + import os + import time + import sys, re, threading, collections + import traceback + import PIL + from cStringIO import StringIO + + from fabscan.util.FSInject import inject, singleton + from fabscan.FSConfig import ConfigInterface + from fabscan.FSSettings import SettingsInterface + from fabscan.scanner.interfaces.FSImageProcessor import ImageProcessorInterface + + try: + import picamera + except: + pass - self.config = config - self.settings = settings - self.timestamp = int(round(time.time() * 1000)) + @inject( + config=ConfigInterface + ) + class FSCamera(): - self.camera = None - self.output = None - self.camera_buffer = cam_ring_buffer - self.idle = True - self.start() + def __init__(self, config): + self.camera_buffer = FSRingBuffer(10) + config = config - def run(self): - while True: + if config.camera.type == 'PICAM': + self.device = PiCam(self.camera_buffer) + + if config.camera.type == 'USBCAM': + self.device = USBCam(self.camera_buffer) + + if config.camera.type == 'dummy': + self.device = DummyCam() - if not self.idle and self.camera.isOpened(): + def is_connected(self): + return self.device.isAlive() + + class FSRingBuffer(threading.Thread): + + # Initialize the buffer. + def __init__(self, size_max): + self.max = size_max + self.data = collections.deque(maxlen=size_max) + self.sync = threading.Event() + self._lock = threading.RLock() + + # Append an element to the ring buffer. + def append(self, x): + with self._lock: + if len(self.data) == self.max: + self.data.pop() + self.data.append(x) + + # Retrieve the newest element in the buffer. + def get(self): + with self._lock: + if len(self.data) >= 1: + image = self.data[-1] + else: + image = None + return image + + def isSync(self): + return self._sync + + def flush(self): + # with self._lock: + self.sync.set() + self.data.clear() + self.sync.clear() + + @inject( + config=ConfigInterface, + settings=SettingsInterface, + imageprocessor=ImageProcessorInterface + ) + class CamProcessor(threading.Thread): + def __init__(self, owner, fs_ring_buffer, resolution, mode, config, settings, imageprocessor): + super(CamProcessor, self).__init__() + self.stream = io.BytesIO() + self.event = threading.Event() + self.terminated = False + self.config = config + self.settings = settings + self.imageprocessor = imageprocessor + self.imageprocessor.init(resolution) + + self._logger = logging.getLogger(__name__) + self.owner = owner + self.mode = mode + self.fs_ring_buffer = fs_ring_buffer + self.start() + + def run(self): + # This method runs in a separate thread + while not self.terminated: + # Wait for an image to be written to the stream + if self.event.wait(1): try: - ret, image = self.camera.read() - if image is not None: - ret, jpg = cv2.imencode('.jpg', image) - self.output.write(jpg) - except StandardError, e: + + self.stream.seek(0) + data = np.fromstring(self.stream.getvalue(), dtype=np.uint8) + image = cv2.imdecode(data, 1) + + if self.config.camera.rotate == "True": + image = cv2.transpose(image) + if self.config.camera.hflip == "True": + image = cv2.flip(image, 1) + if self.config.camera.vflip == "True": + image = cv2.flip(image, 0) + + if self.mode == "settings": + image = self.imageprocessor.get_laser_stream_frame(image) + + while not self.fs_ring_buffer.sync.wait(1): + self.fs_ring_buffer.append(image) + + except: pass + + finally: + # Reset the stream and event + self.stream.seek(0) + self.stream.truncate() + self.event.clear() + + # Return ourselves to the available pool + with self.owner.lock: + self.owner.pool.append(self) + + class ProcessCamOutput(object): + def __init__(self, fs_ring_buffer, resolution, mode="default"): + self.done = False + # Construct a pool of 4 image processors along with a lock + # to control access between threads + self.lock = threading.Lock() + self.pool = [CamProcessor(self, fs_ring_buffer, resolution, mode) for i in range(4)] + self.processor = None + self._logger = logging.getLogger(__name__) + self.format_is_mjpeg = True + + def set_no_mjepeg(self): + self.format_is_mjpeg = False + + def write(self, buf): + + try: + if ((buf[0][0] == 255) and (buf[1][0] == 216)) or buf.startswith(b'\xff\xd8'): + # New frame; set the current processor going and grab + # a spare one + if self.processor: + self.processor.event.set() + with self.lock: + if self.pool: + self.processor = self.pool.pop() + else: + # No processor's available, we'll have to skip + # this frame; you may want to print a warning + # here to see whether you hit this case + self.processor = None + if self.processor: + self.processor.stream.write(buf) + except: + pass + + def flush(self): + # When told to flush (this indicates end of recording), shut + # down in an orderly fashion. First, add the current processor + # back to the pool + if self.processor: + with self.lock: + self.pool.append(self.processor) + self.processor = None + # Now, empty the pool, joining each thread as we go + while True: + with self.lock: + try: + proc = self.pool.pop() + except IndexError: + pass # pool is empty + try: + proc.terminated = True + proc.join() + except StandardError as e: + pass + if self.pool.empty: + break + + @inject( + config=ConfigInterface, + settings=SettingsInterface + ) + class PiCam(threading.Thread): + camera = None + + def __init__(self, cam_ring_buffer, config, settings): + threading.Thread.__init__(self) + self._logger = logging.getLogger(__name__) + + self.config = config + self.settings = settings + self.timestamp = int(round(time.time() * 1000)) + + self.camera = None + self.output = None + # self.resolution = (self.config.camera.preview_resolution.width, self.config.camera.preview_resolution.height) + + self.camera_buffer = cam_ring_buffer + # self.camera = picamera.PiCamera(resolution=self.resolution) + self.idle = True + self.start() + + def run(self): + while True: + if not self.idle and self.camera.recording: + self.camera.wait_recording(0.5) else: time.sleep(0.05) - def get_frame(self): - image = None - while image is None: - image = self.camera_buffer.get() - return image + def get_frame(self): + image = None + while image is None: + image = self.camera_buffer.get() + return image + + def set_mode(self, mode): + camera_mode = { + "calibration": self.set_calibration_mode, + "settings": self.set_settings_preview, + "default": self.set_default_mode, + "alignment": self.set_alignement_preview + } + camera_mode[mode]() + + def set_alignement_preview(self): + self.resolution = ( + self.config.camera.preview_resolution.width, self.config.camera.preview_resolution.height) + self.output = ProcessCamOutput(self.camera_buffer, self.resolution, mode="alignment") + + def set_settings_preview(self): + self.resolution = ( + self.config.camera.preview_resolution.width, self.config.camera.preview_resolution.height) + self.output = ProcessCamOutput(self.camera_buffer, self.resolution, mode="settings") + + def set_default_mode(self): + self.resolution = (self.config.camera.resolution.width, self.config.camera.resolution.height) + self.output = ProcessCamOutput(self.camera_buffer, self.resolution) + + def set_calibration_mode(self): + self.resolution = (self.config.camera.resolution.width, self.config.camera.resolution.height) + self.output = ProcessCamOutput(self.camera_buffer, self.resolution) + + def start_stream(self, mode="default"): - def set_mode(self, mode): - camera_mode = { - "calibration": self.set_calibration_mode, - "settings": self.set_settings_preview, - "default": self.set_default_mode, - "alignment": self.set_alignement_preview - } - camera_mode[mode]() + try: + self.flush_stream() + self.set_mode(mode) - def set_alignement_preview(self): - self.resolution = (self.config.camera.preview_resolution.width, self.config.camera.preview_resolution.height) - self.output = ProcessCamOutput(self.camera_buffer, self.resolution, mode="alignment") + if self.camera is None: + self.camera = picamera.PiCamera(resolution=self.resolution) - def set_settings_preview(self): - self.resolution = (self.config.camera.preview_resolution.width, self.config.camera.preview_resolution.height) - self.output = ProcessCamOutput(self.camera_buffer, self.resolution, mode="settings") + self.idle = False + self.camera.start_recording(self.output, format='mjpeg') + self._logger.debug("Cam Stream with Resolution " + str(self.resolution) + " started") + except StandardError as e: + self._logger.error("Not able to initialize Raspberry Pi Camera.") + self._logger.error(e) - def set_default_mode(self): - self.resolution = (self.config.camera.resolution.width, self.config.camera.resolution.height) - self.output = ProcessCamOutput(self.camera_buffer, self.resolution) + def stop_stream(self): + time.sleep(0.5) + try: + if self.camera.recording: + self.camera.stop_recording() + while self.camera.recording: + time.sleep(0.4) + self.camera.close() + self.camera = None + self.idle = True + self._logger.debug("Cam Stream with Resolution " + str(self.resolution) + " stopped") - def set_calibration_mode(self): - self.resolution = (self.config.camera.resolution.width, self.config.camera.resolution.height) - self.output = ProcessCamOutput(self.camera_buffer, self.resolution) + except StandardError as e: + self._logger.error("Not able to stop camera.") + self._logger.error(e) - def start_stream(self, mode="default"): + def is_idle(self): + return self.idle + + def flush_stream(self): + self.camera_buffer.flush() + + ### + # This class is used to catch openCV errors which are not catchable by python + # see https://stackoverflow.com/questions/9131992/how-can-i-catch-corrupt-jpegs-when-loading-an-image-with-imread-in-opencv/45055195 + ## + # FIXME: Causes too many open files.... + class CaptureLibOpenCVStderr: + + def __init__(self, what): + self.what = what + + def __enter__(self): + self.r, w = os.pipe() + self.original = os.dup(self.what.fileno()) # save old file descriptor + self.what.flush() # flush cache before replacing + os.dup2(w, self.what.fileno()) # overwrite with pipe + os.write(w, ' ') # so that subsequent read does not block + os.close(w) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.what.flush() # flush again before reading and restoring + self.data = os.read(self.r, 1000).strip() + os.dup2(self.original, self.what.fileno()) # restore original + os.close(self.r) + + def __str__(self): + return self.data + + @inject( + config=ConfigInterface, + settings=SettingsInterface + ) + class USBCam(threading.Thread): + camera = None + + def __init__(self, cam_ring_buffer, config, settings): + threading.Thread.__init__(self) + self._logger = logging.getLogger(__name__) + + self.config = config + self.settings = settings + self.timestamp = int(round(time.time() * 1000)) + + # self.sync = threading.Event() + self.camera = None + self.output = None + self.camera_buffer = cam_ring_buffer + self.idle = True + self.start() + + def run(self): + while True: + + if self.camera and not self.idle and self.camera.isOpened(): + try: + # catch opencv warnings, to make the log more quiet + with CaptureLibOpenCVStderr(sys.stderr) as output: + ret, image = self.camera.read() + # self._logger.debug(output) + if image is not None: + encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90] + ret, jpg = cv2.imencode('.jpg', image) + self.output.write(jpg) + + except: + pass + else: + time.sleep(0.05) + + def get_frame(self): + image = None + while image is None: + with self.camera_buffer._lock: + image = self.camera_buffer.get() + return image + + def set_mode(self, mode): + camera_mode = { + "calibration": self.set_calibration_mode, + "settings": self.set_settings_preview, + "default": self.set_default_mode, + "alignment": self.set_alignement_preview + } + camera_mode[mode]() + + def set_alignement_preview(self): + self.resolution = ( + self.config.camera.preview_resolution.width, self.config.camera.preview_resolution.height) + self.output = ProcessCamOutput(self.camera_buffer, self.resolution, mode="alignment") + + def set_settings_preview(self): + self.resolution = ( + self.config.camera.preview_resolution.width, self.config.camera.preview_resolution.height) + self.output = ProcessCamOutput(self.camera_buffer, self.resolution, mode="settings") + + def set_default_mode(self): + self.resolution = (self.config.camera.resolution.width, self.config.camera.resolution.height) + self.output = ProcessCamOutput(self.camera_buffer, self.resolution) + + def set_calibration_mode(self): + self.resolution = (self.config.camera.resolution.width, self.config.camera.resolution.height) + self.output = ProcessCamOutput(self.camera_buffer, self.resolution) + + def start_stream(self, mode="default"): self._logger.debug("WebCam Started") try: self.set_mode(mode) @@ -356,10 +700,10 @@ def start_stream(self, mode="default"): self.camera = cv2.VideoCapture(0) self.output.format_is_mjpeg = False - #HIGHT - self.camera.set(3, self.resolution[1]) - #WIDTH - self.camera.set(4, self.resolution[0]) + # HEIGHT + self.camera.set(4, self.resolution[1]) + # WIDTH + self.camera.set(3, self.resolution[0]) self.idle = False @@ -368,26 +712,62 @@ def start_stream(self, mode="default"): self._logger.error("Not able to initialize USB Camera.") self._logger.error(e) - def stop_stream(self): - time.sleep(0.5) - try: - if self.camera.isOpened(): - self.camera.release() + def stop_stream(self): + time.sleep(0.5) + try: + if self.camera and self.camera.isOpened(): + self.camera.release() - self.camera = None - self.idle = True - self._logger.debug("Cam Stream with Resolution "+str(self.resolution)+" stopped") + self.camera = None + self.idle = True + self._logger.debug("Cam Stream with Resolution " + str(self.resolution) + " stopped") - except StandardError as e: - self._logger.error("Not able to stop camera.") - self._logger.error(e) + except StandardError as e: + self._logger.error("Not able to stop camera.") + self._logger.error(e) - def is_idle(self): - return self.idle + def is_idle(self): + return self.idle - def flush_stream(self): - self.camera_buffer.flush() + def flush_stream(self): + self.camera_buffer.flush() + + class DummyCam: + + def __init__(self): + self.image_count = 1 + + def take_picture(self, number=0): + ''' + dummy camera device loads images from defined dummy folder. + can be used for simulating a scan without hardware. + ''' + basedir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + path = os.path.join(basedir, 'static/data/scans/debug/dummy_img') + + img = None + i = 1 + for file in os.listdir(path): + if i == self.image_count: + file = os.path.join(path, file) + img = cv2.imread(file) + break + i += 1 + + self.image_count += 1 + + if self.image_count == 214: + self.image_count = 1 + i = 1 + time.sleep(0.5) + return img + + def set_exposure(self): + pass + + def close(self): + pass class DummyCam: diff --git a/src/fabscan/server/FSScanServer.py b/src/fabscan/server/FSScanServer.py new file mode 100644 index 00000000..c799d1e5 --- /dev/null +++ b/src/fabscan/server/FSScanServer.py @@ -0,0 +1,115 @@ +__author__ = "Mario Lukas" +__copyright__ = "Copyright 2017" +__license__ = "GPL v2" +__maintainer__ = "Mario Lukas" +__email__ = "info@mariolukas.de" + +import time +import logging +import sys +import os +import signal + +from FSWebServer import FSWebServer +from fabscan.FSVersion import __version__ +from fabscan.util.FSInject import injector +from fabscan.util.FSUtil import FSSystem, FSSystemExit +from fabscan.server.websockets import FSWebSocketServer, FSWebSocketServerInterface +from fabscan.FSScanner import FSScanner, FSCommand +from fabscan.FSEvents import FSEventManagerSingleton, FSEventManagerInterface, FSEvents +from fabscan.FSConfig import ConfigInterface, ConfigSingleton, Config +from fabscan.FSSettings import SettingsInterface, SettingsSingleton, Settings +from fabscan.scanner.interfaces import FSScannerFactory +from fabscan.util.FSUpdate import do_upgrade + + +class FSScanServer(object): + def __init__(self,config_file, settings_file): + self.system_exit = FSSystemExit() + self.config_file = config_file + self.settings_file = settings_file + self.exit = False + self.restart = False + self.upgrade = False + self.reboot = False + self.shutdown = False + self.scanner = None + self.webserver = None + self._logger = logging.getLogger(__name__) + + def on_server_command(self, mgr, event): + command = event.command + + if command == FSCommand.UPGRADE_SERVER: + self.upgrade = True + self.system_exit.kill() + + + if command == FSCommand.RESTART_SERVER: + self.restart = True + self.system_exit.kill() + + + def restart_server(self): + try: + self.restart = False + FSSystem.run_command("/etc/init.d/fabscanpi-server restart", blocking=True) + except StandardError, e: + self._logger.error(e) + + def update_server(self): + try: + do_upgrade() + except StandardError, e: + self._logger.error(e) + + def run(self): + self._logger.info("FabScanPi-Server "+str(__version__)) + + try: + injector.provide(FSEventManagerInterface, FSEventManagerSingleton) + injector.provide_instance(FSWebSocketServerInterface, FSWebSocketServer()) + injector.provide_instance(ConfigInterface, Config(self.config_file, True)) + + injector.provide_instance(SettingsInterface, Settings(self.settings_file, True)) + + # inject "dynamic" classes + self.config = injector.get_instance(ConfigInterface) + FSScannerFactory.injectScannerType(self.config.scanner_type) + + # start server services + websocket_server = injector.get_instance(FSWebSocketServerInterface) + websocket_server.start() + + self.webserver = FSWebServer() + self.webserver.start() + + self.scanner = FSScanner() + self.scanner.start() + FSEventManagerSingleton().instance.subscribe(FSEvents.COMMAND, self.on_server_command) + + while not self.system_exit.kill: + time.sleep(0.3) + + if self.upgrade: + self._logger.info("Upgrading FabScanPi Server") + self.update_server() + self.restart = True + + if self.restart: + self._logger.info("Restarting FabScanPi Server") + self.restart_server() + + self.webserver.kill() + self._logger.debug("Waiting for Webserver exit...") + + self.scanner.kill() + self._logger.debug("Waiting for Scanner exit...") + self.scanner.join() + + self._logger.info("FabScan Server Exit. Bye!") + os._exit(1) + + except (KeyboardInterrupt, SystemExit): + self._logger.info("FabScan Server Exit. Bye!") + sys.exit(0) diff --git a/src/fabscan/server/FSWebServer.py b/src/fabscan/server/FSWebServer.py new file mode 100755 index 00000000..29a3d3ab --- /dev/null +++ b/src/fabscan/server/FSWebServer.py @@ -0,0 +1,48 @@ +__author__ = "Mario Lukas" +__copyright__ = "Copyright 2017" +__license__ = "GPL v2" +__maintainer__ = "Mario Lukas" +__email__ = "info@mariolukas.de" + +import threading + +import tornado.ioloop +import tornado.web +import os + +from fabscan.server.handler.api.FSFilterHandler import FSFilterHandler +from fabscan.server.handler.api.FSScansHandler import FSScansHandler +from fabscan.scanner.interfaces.FSScanProcessor import FSScanProcessorInterface +from fabscan.FSConfig import ConfigSingleton, ConfigInterface +from fabscan.util.FSInject import inject + +@inject( + config=ConfigInterface, + scanprocessor=FSScanProcessorInterface +) +class FSWebServer(threading.Thread): + + def __init__(self, config, scanprocessor): + threading.Thread.__init__(self) + self.config = config + self.exit = False + self.scanprocessor = scanprocessor + self.www_folder = os.path.join(os.path.dirname(__file__), self.config.folders.www) + + def routes(self): + return tornado.web.Application([ + (r"/api/v1/filters", FSFilterHandler), + (r"/api/v1/scans", FSScansHandler, dict(config=self.config)), + (r"/(.*)", tornado.web.StaticFileHandler, {"path": self.www_folder, "default_filename": "index.html"}) + + # (r"/api/v1/scans/", FSFilterHandler), + # (r"/api/v1/scans/([0 - 9]+)", FSFilterHandler), + ]) + + def run(self): + webserver = self.routes() + webserver.listen(8080) + tornado.ioloop.IOLoop.current().start() + + def kill(self): + tornado.ioloop.IOLoop.instance().stop() \ No newline at end of file diff --git a/src/fabscan/server/WebServer.py b/src/fabscan/server/WebServer.py deleted file mode 100755 index 7ba26d22..00000000 --- a/src/fabscan/server/WebServer.py +++ /dev/null @@ -1,53 +0,0 @@ -__author__ = "Mario Lukas" -__copyright__ = "Copyright 2017" -__license__ = "GPL v2" -__maintainer__ = "Mario Lukas" -__email__ = "info@mariolukas.de" - -import threading -import FSHttpRequestHandler - -from SocketServer import ThreadingMixIn -from SocketServer import TCPServer -from BaseHTTPServer import HTTPServer -from fabscan.scanner.interfaces.FSScanProcessor import FSScanProcessorInterface -from fabscan.FSConfig import ConfigSingleton, ConfigInterface -from fabscan.util.FSInject import inject - -class ThreadedHTTPServer(ThreadingMixIn, TCPServer): - def __init__(self, config, scanprocessor): - pass - -#class WebServer(ThreadingMixIn, HTTPServer): -class WebServer(HTTPServer): - - def __init__(self, config, scanprocessor): - handler = FSHttpRequestHandler.CreateRequestHandler(config, scanprocessor) - HTTPServer.__init__(self, ('', 8080), handler) - self.exit = False - - def serve_forever(self): - while not self.exit: - self.handle_request() - - def kill(self): - self.exit = True - -@inject( - config=ConfigInterface, - scanprocessor=FSScanProcessorInterface -) -class FSWebServer(threading.Thread): - - def __init__(self, config, scanprocessor): - threading.Thread.__init__(self) - self.config = config - self.exit = False - self.scanprocessor = scanprocessor - - def run(self): - self.webserver = WebServer(self.config, self.scanprocessor) - self.webserver.serve_forever() - - def kill(self): - self.webserver.kill() \ No newline at end of file diff --git a/src/fabscan/server/__init__.py b/src/fabscan/server/__init__.py index 69c94d69..3c5bcf36 100755 --- a/src/fabscan/server/__init__.py +++ b/src/fabscan/server/__init__.py @@ -4,112 +4,5 @@ __maintainer__ = "Mario Lukas" __email__ = "info@mariolukas.de" -import time -import logging -import sys -import os -import signal +from FSScanServer import FSScanServer -from WebServer import FSWebServer -from fabscan.FSVersion import __version__ -from fabscan.util.FSInject import injector -from fabscan.util.FSUtil import FSSystem, FSSystemExit -from fabscan.server.websockets import FSWebSocketServer, FSWebSocketServerInterface -from fabscan.FSScanner import FSScanner, FSCommand -from fabscan.FSEvents import FSEventManagerSingleton, FSEventManagerInterface, FSEvents -from fabscan.FSConfig import ConfigInterface, ConfigSingleton, Config -from fabscan.FSSettings import SettingsInterface, SettingsSingleton, Settings -from fabscan.scanner.interfaces import FSScannerFactory -from fabscan.util.FSUpdate import do_upgrade - - -class FSServer(object): - def __init__(self,config_file, settings_file): - self.system_exit = FSSystemExit() - self.config_file = config_file - self.settings_file = settings_file - self.exit = False - self.restart = False - self.upgrade = False - self.reboot = False - self.shutdown = False - self.scanner = None - self.webserver = None - self._logger = logging.getLogger(__name__) - - def on_server_command(self, mgr, event): - command = event.command - - if command == FSCommand.UPGRADE_SERVER: - self.upgrade = True - self.system_exit.kill() - - - if command == FSCommand.RESTART_SERVER: - self.restart = True - self.system_exit.kill() - - - def restart_server(self): - try: - self.restart = False - FSSystem.run_command("/etc/init.d/fabscanpi-server restart", blocking=True) - except StandardError, e: - self._logger.error(e) - - def update_server(self): - try: - do_upgrade() - except StandardError, e: - self._logger.error(e) - - def run(self): - self._logger.info("FabScanPi-Server "+str(__version__)) - - try: - injector.provide(FSEventManagerInterface, FSEventManagerSingleton) - injector.provide_instance(FSWebSocketServerInterface, FSWebSocketServer()) - injector.provide_instance(ConfigInterface, Config(self.config_file, True)) - - injector.provide_instance(SettingsInterface, Settings(self.settings_file, True)) - - # inject "dynamic" classes - self.config = injector.get_instance(ConfigInterface) - FSScannerFactory.injectScannerType(self.config.scanner_type) - - # start server services - websocket_server = injector.get_instance(FSWebSocketServerInterface) - websocket_server.start() - - self.webserver = FSWebServer() - self.webserver.start() - - self.scanner = FSScanner() - self.scanner.start() - FSEventManagerSingleton().instance.subscribe(FSEvents.COMMAND, self.on_server_command) - - while not self.system_exit.kill: - time.sleep(0.3) - - if self.upgrade: - self._logger.info("Upgrading FabScanPi Server") - self.update_server() - self.restart = True - - if self.restart: - self._logger.info("Restarting FabScanPi Server") - self.restart_server() - - self.webserver.kill() - self._logger.debug("Waiting for Webserver exit...") - - self.scanner.kill() - self._logger.debug("Waiting for Scanner exit...") - self.scanner.join() - - self._logger.info("FabScan Server Exit. Bye!") - os._exit(1) - - except (KeyboardInterrupt, SystemExit): - self._logger.info("FabScan Server Exit. Bye!") - sys.exit(0) diff --git a/src/fabscan/server/handler/__init__.py b/src/fabscan/server/handler/__init__.py new file mode 100644 index 00000000..047ebbd7 --- /dev/null +++ b/src/fabscan/server/handler/__init__.py @@ -0,0 +1,5 @@ +__author__ = "Mario Lukas" +__copyright__ = "Copyright 2017" +__license__ = "GPL v2" +__maintainer__ = "Mario Lukas" +__email__ = "info@mariolukas.de" diff --git a/src/fabscan/server/handler/api/FSFilterHandler.py b/src/fabscan/server/handler/api/FSFilterHandler.py new file mode 100644 index 00000000..e4f20deb --- /dev/null +++ b/src/fabscan/server/handler/api/FSFilterHandler.py @@ -0,0 +1,24 @@ +import os +import json +import tornado.web + + +class FSFilterHandler(tornado.web.RequestHandler): + + def get(self): + basedir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) + + filters = dict() + filters['filters'] = [] + for file in os.listdir(basedir+"/mlx"): + if file.endswith(".mlx"): + if os.path.os.path.exists(basedir+"/mlx/"+file): + filter = dict() + name, extension = os.path.splitext(file) + + filter['name'] = name + filter['file_name'] = file + + filters['filters'].append(filter) + + self.write(json.dumps(filters)) \ No newline at end of file diff --git a/src/fabscan/server/handler/api/FSPreviewHandler.py b/src/fabscan/server/handler/api/FSPreviewHandler.py new file mode 100644 index 00000000..e69de29b diff --git a/src/fabscan/server/handler/api/FSScansHandler.py b/src/fabscan/server/handler/api/FSScansHandler.py new file mode 100644 index 00000000..81a7b51c --- /dev/null +++ b/src/fabscan/server/handler/api/FSScansHandler.py @@ -0,0 +1,30 @@ +import os +import json +import tornado.web + +class FSScansHandler(tornado.web.RequestHandler): + + def initialize(self, config): + self.config = config + + def get(self): + scans = self.get_list_of_scans() + self.write(json.dumps(scans)) + + def get_list_of_scans(self): + basedir = self.config.folders.scans + + subdirectories = sorted(os.listdir(str(basedir)),reverse=True) + response = dict() + response['scans'] = [] + + for dir in subdirectories: + if dir != "debug": + if os.path.os.path.exists(basedir+dir+"/scan_"+dir+".ply"): + scan = dict() + scan['id'] = str(dir) + scan['pointcloud'] = str("http://"+self.request.host+"/scans/"+dir+"/scan_"+dir+".ply") + scan['thumbnail'] = str("http://"+self.request.host+"/scans/"+dir+"/thumbnail_"+dir+".png") + response['scans'].append(scan) + + return response \ No newline at end of file diff --git a/src/fabscan/server/handler/api/__init__.py b/src/fabscan/server/handler/api/__init__.py new file mode 100644 index 00000000..dc7daffb --- /dev/null +++ b/src/fabscan/server/handler/api/__init__.py @@ -0,0 +1,5 @@ +__author__ = "Mario Lukas" +__copyright__ = "Copyright 2018" +__license__ = "GPL v2" +__maintainer__ = "Mario Lukas" +__email__ = "info@mariolukas.de" \ No newline at end of file diff --git a/src/fabscan/server/handler/streams/__init__.py b/src/fabscan/server/handler/streams/__init__.py new file mode 100644 index 00000000..c6402eb4 --- /dev/null +++ b/src/fabscan/server/handler/streams/__init__.py @@ -0,0 +1,5 @@ +__author__ = "Mario Lukas" +__copyright__ = "Copyright 2017" +__license__ = "GPL v2" +__maintainer__ = "Mario Lukas" +__email__ = "info@mariolukas.de" \ No newline at end of file diff --git a/src/fabscan/server/handler/websocket/__init__.py b/src/fabscan/server/handler/websocket/__init__.py new file mode 100644 index 00000000..c6402eb4 --- /dev/null +++ b/src/fabscan/server/handler/websocket/__init__.py @@ -0,0 +1,5 @@ +__author__ = "Mario Lukas" +__copyright__ = "Copyright 2017" +__license__ = "GPL v2" +__maintainer__ = "Mario Lukas" +__email__ = "info@mariolukas.de" \ No newline at end of file