Commit 8292ebf7 authored by Joel Collins's avatar Joel Collins
Browse files

Implement hardware locks

parent a40cb9b9
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
__all__ = ['Microscope', 'config']
__all__ = ['Microscope', 'config', 'task', 'lock']
__version__ = "0.1.0"

from .microscope import Microscope
+13 −2
Original line number Diff line number Diff line
@@ -23,8 +23,9 @@ from flask_cors import CORS
from openflexure_microscope.api.utilities import list_routes

from openflexure_microscope import Microscope, config
from openflexure_microscope.lock import LockError
from openflexure_microscope.camera.pi import StreamingCamera
from openflexure_stage import OpenFlexureStage
from openflexure_microscope.stage.openflexure import Stage

import atexit
import logging, sys
@@ -66,6 +67,16 @@ def _handle_http_exception(e):
for code in default_exceptions:
    app.errorhandler(code)(_handle_http_exception)

def _handle_lock_exception(e):
    return make_response(
        jsonify({
            'status_code': 423,
            'error': e.code,
            'details': e.message
        }),
        423)

app.errorhandler(LockError)(_handle_lock_exception)

# After app starts, but before first request, attach hardware to global microscope
@app.before_first_request
@@ -77,7 +88,7 @@ def attach_microscope():
    logging.debug("Creating camera object...")
    api_camera = StreamingCamera(config=openflexurerc)
    logging.debug("Creating stage object...")
    api_stage = OpenFlexureStage("/dev/ttyUSB0")
    api_stage = Stage("/dev/ttyUSB0")

    logging.debug("Attaching devices to microscope...")
    api_microscope.attach(
+3 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ except ImportError:
from .capture import CaptureObject, capture_from_dict, BASE_CAPTURE_PATH
from openflexure_microscope.config import USER_CONFIG_DIR
from openflexure_microscope.utilities import entry_by_id
from openflexure_microscope.lock import StrictLock


def last_entry(object_list: list):
@@ -29,7 +30,6 @@ def last_entry(object_list: list):
        return None



def generate_basename():
    """Return a default filename based on the capture datetime"""
    return datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
@@ -87,6 +87,8 @@ class BaseCamera(object):
        self.thread = None  #: Background thread reading frames from camera
        self.camera = None  #: Camera object

        self.lock = StrictLock(timeout=1)  #: Strict lock controlling thread access to camera hardware

        self.frame = None  #: bytes: Current frame is stored here by background thread
        self.last_access = 0  #: time: Time of last client access to the camera
        self.event = CameraEvent()
+166 −160
Original line number Diff line number Diff line
@@ -201,6 +201,8 @@ class StreamingCamera(BaseCamera):

        paused_stream = False

        with self.lock:

            # Apply valid config params to Picamera object
            if not self.state['record_active']:  # If not recording a video

@@ -249,6 +251,7 @@ class StreamingCamera(BaseCamera):
        """
        Change the camera zoom, handling recentering and scaling.
        """
        with self.lock:
            self.state['zoom_value'] = float(zoom_value)
            if self.state['zoom_value'] < 1:
                self.state['zoom_value'] = 1
@@ -299,6 +302,7 @@ class StreamingCamera(BaseCamera):
            output_object (str/BytesIO): Target object.

        """
        with self.lock:
            # Start recording method only if a current recording is not running
            if not self.state['record_active']:

@@ -334,6 +338,7 @@ class StreamingCamera(BaseCamera):

    def stop_recording(self) -> bool:
        """Stop the last started video recording on splitter port 2."""
        with self.lock:
            # Stop the camera video recording on port 2
            logging.info("Stopping recording")
            self.camera.stop_recording(splitter_port=2)
@@ -378,7 +383,6 @@ class StreamingCamera(BaseCamera):
            splitter_port (int): Splitter port to start recording on
            resolution ((int, int)): Resolution to set the camera to, before starting recording. Defaults to `self.config['video_resolution']`.
        """

        # If no stream object exists
        if not hasattr(self, 'stream'):
            self.stream = io.BytesIO() # Create a stream object
@@ -421,6 +425,7 @@ class StreamingCamera(BaseCamera):
            resize ((int, int)): Resize the captured image.
        """

        with self.lock:
            # If output is a StreamObject
            if isinstance(output, CaptureObject):
                # Set target to capture stream
@@ -459,6 +464,7 @@ class StreamingCamera(BaseCamera):
            use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster.
            resize ((int, int)): Resize the captured image.
        """
        with self.lock:
            if use_video_port:
                resolution = self.config['video_resolution']
            else:
@@ -502,7 +508,7 @@ class StreamingCamera(BaseCamera):
            use_video_port (bool): Capture from the video port used for streaming. Lower resolution, faster.
            resize ((int, int)): Resize the captured image.
        """

        with self.lock:
            if use_video_port:
                resolution = self.config['video_resolution']
            else:
+65 −0
Original line number Diff line number Diff line
from threading import RLock, ThreadError

class LockError(ThreadError):
    ERROR_CODES = {
        'ACQUIRE_ERROR': "Unable to acquire. Lock in use by another thread.",
        'IN_USE_ERROR': "Lock in use by another thread."
    }

    def __init__(self, code, lock):
        self.code = code
        if code in LockError.ERROR_CODES:
            self.message = LockError.ERROR_CODES[code]
        else:
            self.message = "Unknown error."
        print("{}: {}".format(self.code, self.message))

        ThreadError.__init__(self)


class StrictLock(object):
    def __init__(self, timeout=1):
        self._lock = RLock()
        self.timeout = timeout

    def acquire(self, blocking=True):
        return self._lock.acquire(blocking, timeout=self.timeout)

    def __enter__(self):
        result = self._lock.acquire(blocking=True, timeout=self.timeout)
        if result:
            return result
        else:
            raise LockError('ACQUIRE_ERROR', self)

    def __exit__(self, *args):
        self._lock.release()

    def release(self):
        self._lock.release()


class CompositeLock(object):
    def __init__(self, locks, timeout=1):
        self.locks = locks
        self.timeout = timeout

    def acquire(self, blocking=True):
        return (lock.acquire(blocking=blocking, timeout=self.timeout) for lock in self.locks)

    def __enter__(self):
        result = (lock.acquire(blocking=True, timeout=self.timeout) for lock in self.locks)
        if all(result):
            return result
        else:
            msg = "Unable to acquire all locks {}.".format(self.locks)
            logging.error(msg)
            raise ThreadError(msg)

    def __exit__(self, *args):
        for lock in self.locks:
            lock.release()

    def release(self):
        for lock in self.locks:
            lock.release()
 No newline at end of file
Loading