Example #1
0
 def setupCameraMap(self):
     '''
     Sets up camera and map for a given level
     '''
     self.camera = Camera(self.game_screen)
     self.background = Background(self.game_display)
     self.game_map = Map(self.game_display, self.screen_dims, 32)
Example #2
0
 def __init__(self, is_picture=False):
     self.start_time = datetime.now()
     self.tank = Tank()
     self.camera = Camera()
     self.plc = PLC(self.camera.number)
     self.is_picture = is_picture
     if not is_picture:
         self.camera.start()
     self.model = TFModel(model_name='sticker')
Example #3
0
def exploratory_pipeline():
    '''
    The pipeline to calculate the lane lines in an exploratory mode that produces
    plots of the various stages along the way.
    
    Notes:
    - Calibration shall be performed at least once before using the pipeline
    - This pipeline works with for a single image
    '''
    # Read in an image
    image = mpimg.imread(test_image)

    # Undistort the image before processing and plot an example
    mtx, dist = Camera.getCalibrationData()
    undist = Camera.undistort(image, mtx, dist)

    # Process the undistorted image with thresholding to get a binary mask
    binary_mask = Thresholding.lane_detection_mask(undist, KERNEL)

    # Plot the undistorted and binary mask images
    Plotting.plotResult(undist, binary_mask)

    # Perspective transform
    warped, M, M_inv = Image_processing.perspectiveTransform(binary_mask)
    Plotting.plotResult(binary_mask, warped)

    # Get the position of the left and right lanes
    leftx_base, rightx_base = Image_processing.histogramPeaks(warped,
                                                              plot=True)

    # Get the lane line equations using the sliding window
    left_fit, right_fit = Image_processing.slidingWindowInit(warped, plot=True)

    # Calculate the sliding window of a successive image, using the lane line
    # equations that are already known from the previous image analysis
    left_fit, right_fit = Image_processing.slidingWindowFollowing(warped,
                                                                  left_fit,
                                                                  right_fit,
                                                                  plot=True)

    # Calculate the curvature of the left and right lanes
    left_curv, right_curv, curv_string = Image_processing.curvature(
        warped, left_fit, right_fit)

    # Calculate the offset of the car from the center of the lane line
    offset_m, offset_string = Image_processing.offsetFromCenter(
        undist, leftx_base, rightx_base)

    # Overlay the lane area to the undistorted image
    Image_processing.laneArea(warped,
                              undist,
                              M_inv,
                              left_fit,
                              right_fit,
                              offset_string,
                              plot=True)
Example #4
0
class TestCamera(unittest.TestCase):

	def setUp(self):
		self.camera = Camera(0)

	def test_capture(self):
		self.assertTrue(self.camera.is_opened())

	def tearDown(self):
		self.camera.release()
Example #5
0
def record(camera: Camera) -> None:
    if not camera.recording:
        logger.info("START RECORDING...")
        camera.recording = True
        now = datetime.now()
        str_date = now.strftime("%Y-%m-%d_%H%M%S")
        file_name = f"RECORDING_{str_date}.avi"
        path = "../captures/" + file_name
        writter = cv.VideoWriter_fourcc("M", "J", "P", "G")
        camera.output = cv.VideoWriter(path, writter, 10, (640, 480))
    else:
        logger.info("STOP RECORDING")
        camera.recording = False
        camera.output.release()
Example #6
0
def save_image(camera: Camera, tank: Tank, roi = False) -> None:
    _, frame = camera.read()
    now = datetime.now()
    str_date = now.strftime("%Y-%m-%d_%H%M%S")
    file_name = f"{str_date}.jpg"
    path = "../captures/" + file_name
    if roi:
        y_off_start, y_off_end, x_off_start, x_off_end = tank.get_roi(frame)
        frame = frame[y_off_start:y_off_end, x_off_start:x_off_end]
    cv.imwrite(path, frame)
    logger.info(f"Screenshot saved in " + path)
Example #7
0
    def on_connect_camera(self, data: int) -> None:
        """
        Funktion zum Verbinden mit einer angeschlossenen Kamera.

        Wird ausgeführt, sobald der Client die Nachricht "connect_camera" sendet.
        Funktion liest zuerst alle Verfügbaren Kameras aus.
        Sofern eine oder mehrere Kameras verfügbar sind und noch keine Kamera verbunden ist wird die Kamera des übetrragenenen Indizes verbundne.
        Sollte keine Kamera verfügbar sein, wird dem Client eine Fehlermeldung zurück geschickt.

        :param data: Index der zu verbindenden Kamera
        :type data: int

        :return: None
        """

        cameras = Camera.get_available_cameras()
        if cameras:
            if not self.camera_selected:
                self.camera_selected = Camera(data)
            self.send('camera_init', self.camera_selected.get_information())
        else:
            self.send('camera_not_available')
Example #8
0
def main(args):

    left_motor_pins = {
        'forward': 20,
        'reverse': 21,
        'control': 12
    }

    right_motor_pins = {
        'forward': 19,
        'reverse': 26,
        'control': 13
    }

    app = Application(
        camera=Camera(args.camera, args.width, args.height,
                      args.quality, args.stopdelay),
        driver=Driver(left_motor_pins, right_motor_pins)
    )
    app.listen(args.port)
    tornado.ioloop.IOLoop.current().start()
Example #9
0
def pipeline(image):
    '''
    The lane detection pipeline
    
    Notes:
    - Calibration shall be performed at least once before using the pipeline
    - This pipeline worlks with a video
    '''
    global left_lane
    global right_lane
    global initiate_sw
    global initiate_sw_counter
    global initiate_sw_limit

    # Undistort the image before processing and plot an example
    mtx, dist = Camera.getCalibrationData()
    undist = Camera.undistort(image, mtx, dist)

    # Process the undistorted image with thresholding to get a binary mask
    binary_mask = Thresholding.lane_detection_mask(undist, KERNEL)

    # Perspective transform
    warped, M, M_inv = Image_processing.perspectiveTransform(binary_mask)

    # Get the position of the left and right lanes
    leftx_base, rightx_base = Image_processing.histogramPeaks(warped)

    # Apply the appropriate slidinig window technique
    # Note:
    # - If there are more than a few bad frames, a fresh sliding window full
    #   search is forced to take place
    if initiate_sw:
        # Get the lane line equations using the sliding window
        left_fit, right_fit = Image_processing.slidingWindowInit(warped)

        # Reset the sliding window control variables
        initiate_sw = False
        initiate_sw_counter = 0

    else:
        # Calculate the sliding window of a successive image, using the lane line
        # equations that are already known from the previous image analysis
        left_fit, right_fit = Image_processing.slidingWindowFollowing(warped, \
                                    left_lane.current_fit, \
                                    right_lane.current_fit)

        # Update the sliding window control variables
        initiate_sw_counter = initiate_sw_counter + 1
        if initiate_sw_counter >= 5:
            initiate_sw = True

    # Sanity checks and update globals accordingly
    if Image_processing.sanityChecks(warped, left_fit, right_fit):
        # If sanity ok then update the globals with the new line equations
        left_lane.current_fit = left_fit
        right_lane.current_fit = right_fit
    else:
        # If sanity fails use the previously set globals for calculations
        left_fit = left_lane.current_fit
        right_fit = right_lane.current_fit

        # And start a fresh sliding window search
        initiate_sw = True

    # Calculate the curvature of the left and right lanes
    left_curv, right_curv, curv_string = Image_processing.curvature(
        warped, left_fit, right_fit)

    # Calculate the offset of the car from the center of the lane line
    offset_m, offset_string = Image_processing.offsetFromCenter(
        undist, leftx_base, rightx_base)

    # Overlay the lane area to the undistorted image
    result = Image_processing.laneArea(warped, undist, M_inv, left_fit,
                                       right_fit, offset_string)

    # Return processed image
    return result
Example #10
0
class Controller():
    # X position that player starts level at
    player_x = PLAYER_X_VAL


    def __init__(self, game_display, game_screen, screen_dims, colour,
                                                            clock_delay):
        # Run game - used to exit game loop
        self.run = True

        # Load game settings
        with open('json/settings.JSON') as settings:
            self.settings = json.load(settings)

        # Add important game features to self
        self.game_display = game_display
        self.game_screen = game_screen
        self.screen_dims = screen_dims
        self.colour = colour
        self.clock_delay = clock_delay

        # Setup score and level displays
        self.score = Score(game_screen)
        self.level = Level(game_screen)

        # Setup key distances
        self.spawn_area = (2 * self.player_x, screen_dims[0])
        self.map_width = self.game_screen.get_width()
        self.mid_width = self.map_width // 2
        self.mid_height = self.game_screen.get_height() // 2

        self.weapon_types = list(WEAPON_TYPES.keys())

        # Setup level complete variables
        self.level_complete = False
        self.level_complete_text_1 = Text(self.game_screen,
                                        (self.mid_width, self.mid_height - 40),
                                        30,
                                        'Level Complete'
                                    )
        continue_string =  f'Press {self.settings["next_level"]} to continue'
        self.level_complete_text_2 = Text(self.game_screen,
                                        (self.mid_width, self.mid_height),
                                        30,
                                        continue_string
                                    )

        # Setup first level
        self.firstLevel()

        # Setup god mode capability - used for debugging
        self.god_mode = False
        self.cheats = 0

        # Play game music
        self.playMusic()

        self.projectiles = pygame.sprite.Group()


    def playMusic(self):

        ### Setting up game music
        # - Music code inspired by code here:
        #   https://riptutorial.com/pygame/example/24563/example-to-add-
        #   music-in-pygame
        track = TRACKS[self.settings['music']]
        if track == 'Mute':
            pygame.mixer.music.stop()
        else:        
            level_music = MUSIC_LOCATIONS[track]
            pygame.mixer.music.set_volume(level_music[1])
            pygame.mixer.music.load(level_music[0])
            pygame.mixer.music.play(-1)

    def setupCameraMap(self):
        '''
        Sets up camera and map for a given level
        '''
        self.camera = Camera(self.game_screen)
        self.background = Background(self.game_display)
        self.game_map = Map(self.game_display, self.screen_dims, 32)

    def setupPlayer(self):
        ''' Sets up player for the first level
        '''
        self.player = Player(self.game_display, self.game_map, 
                                            self.player_x, - 100)
        self.player_group = pygame.sprite.Group()
        self.player_group.add(self.player)
        self.characters = pygame.sprite.Group()
        self.characters.add(self.player)

    def addCameraTracking(self):
        '''
        Method to add all blitted objects to camera
        '''
        self.camera.addBack(self.background)
        self.camera.addMap(self.game_map)
        self.camera.addPlayer(self.player)
        for enemy in self.enemy_group:
            self.camera.add(enemy)

    def decideEnemyType(self):
        '''
        Randomly returns an enemy from the enemies list
        Consulted docs below to check how to use randint vs randrange
        https://docs.python.org/3/library/random.html
        '''
        idx = random.randrange(len(ENEMIES))
        return ENEMIES[idx]

    def decideRandomArm(self):
        '''
        Randomly determine which arms to give an enemy
        '''
        idx = random.randrange(len(self.weapon_types))
        return self.weapon_types[idx]

    def generateLevel(self):
        '''
        This function generates a new level, and enemies to fight
        '''
        # Setup enemy group for level
        self.enemy_group = pygame.sprite.Group()
        self.dropped_weapons = pygame.sprite.Group()
        # Set up enemies for level.  Level number represents number of 
        # enemies
        for n in range(self.level.val):
            enemy_type = self.decideEnemyType()
            position = random.randrange(self.spawn_area[0], self.spawn_area[1])
            enemy = NPC(self.game_display, self.game_map, position, -100, 
                                        enemy_type, self.decideRandomArm())
            enemy.addTarget(self.player_group)
            self.enemy_group.add(enemy)
            self.characters.add(enemy)

        # Tell player about enemies
        self.player.addTarget(self.enemy_group)

        # Setup tracking
        self.addCameraTracking()

    def resetPlayer(self):
        ''' Resets player to start point for new level
        '''
        self.player.changeMap(self.game_map)
        self.player.center = self.player_x, -100
        self.player.updateState('idle', self.player.state[1])
        self.player.x_y_moving = False
        self.player.max_health += 10
        self.player.health = self.player.max_health

    def firstLevel(self):
        ''' Sets up first level
        '''
        introScreen(self.game_screen, self)
        self.setupCameraMap()
        self.setupPlayer()
        self.generateLevel()

    def newLevel(self):
        ''' Function to start a new level

        Increments the level counter, and adjusts player health
        '''
        self.level.val += 1
        self.level_complete = False
        self.setupCameraMap()
        self.resetPlayer()
        self.generateLevel()

    def keyboardInput(self, event):
        ''' keyboardInput

        Called by game loop, and checking events from keyboard, and
        calling respective functions.
        Includes functionality to activate 'god mode'.
        The intention of god mode is for debugging without dying.

        We initially used the syntax:
        if event.key == pygame.K_w:
                self.player.startMove("u")
        However by browing through the documentation, we discovered that
        with pygame 2.0.0 there was a new feature:
            pygame.key.key_code().
        We can pass in the string of the key eg "space" for space, or
        "w" for "w".
        This allows us to easily produce a human readable JSON
        containing the keybindings so that the user can change the
        keybindings to those of their choice.

        We load this JSON each time we instantiate this class, as the
        intention is that if we have time between now and submission, we
        will produce a settings screen to allow the user to graphically
        change the keybindings to their preference.
        '''
        if event.type == pygame.KEYDOWN:
            # WASD for up/right/left, q for attack
            if event.key == pygame.key.key_code(self.settings['up']):
                self.player.startMove("u")
            elif event.key == pygame.key.key_code(self.settings['right']):
                self.player.startMove("r")
            elif event.key == pygame.key.key_code(self.settings['left']):
                self.player.startMove("l")
            elif event.key == pygame.key.key_code(self.settings['attack']):
                self.player.attack()
            # When level complete, space to move to next level
            elif (event.key == pygame.key.key_code(self.settings['next_level']))\
                                            and self.level_complete:
                self.newLevel()
            # Escape to pause game
            elif event.key == pygame.K_ESCAPE:
                self.player.updateState('idle', self.player.state[1])
                self.player.x_y_moving = False
                pauseScreen(self.game_screen, self)
            # Enter cheat code to enter god mode
            elif event.key == pygame.K_RSHIFT:
                self.cheats = 1
            elif (event.key == pygame.K_1) and (self.cheats == 1):
                self.cheats += 1
            elif (event.key == pygame.K_2) and (self.cheats == 2):
                self.cheats += 1
            elif (event.key == pygame.K_3) and (self.cheats == 3):
                self.cheats += 1

        elif event.type == pygame.KEYUP:
            # Toggle right/left moving
            if event.key == pygame.key.key_code(self.settings['right']):
                self.player.stopMoveX("right")
            elif event.key == pygame.key.key_code(self.settings['left']):
                self.player.stopMoveX("left")
            # Lift right shift to submit code for god mode
            elif event.key == pygame.K_RSHIFT:
                if self.cheats == 4:
                    self.initGodMode()
                    self.cheats = 0
                else:
                    self.cheats = 0

    def initGodMode(self):
        ''' God Mode

        This is here to debug the game without dying, and without having
        to edit the code.
        '''
        self.god_mode = True
        self.player.max_health = 1000000000000000000000000000
        self.player.health = self.player.max_health
        self.gt = Text(self.game_screen,
                        (110, self.game_screen.get_height() - 20),
                                        20, 'god mode activated')
        self.player.arms.strength *= 10000

    def update(self):
        ''' Update function - Used to update positions of characters on
            screen.

            This was initially encapsulated in the display function,
            however this caused issues when the map functions were
            tracking characters.  This was due to the fact that some
            changes to the characters position (such as due to gravity
            and recoil) were being applied after the map had updated.
            To avoid this, update functions were added to characters.
            These are called before we blit to the screen.
        '''
        

        # Updating character positions
        for character in self.characters:
            character.update()
            for projectile in character.thrown_projectiles:
                # Get any new projectiles and add to camera
                if not self.projectiles.has(projectile):
                    self.projectiles.add(projectile)
                    self.camera.addWeapon(projectile)
        
        # update tracked projectiles
        for projectile in self.projectiles:
            # If projectile off screen, remove from sprite groups
            if (projectile.rect.centerx < 0) or \
                    (projectile.rect.centerx > self.map_width):
                projectile.kill()
            projectile.update()

        for weapon in self.dropped_weapons:
            weapon.update()

        # Check if player is alive
        if self.player.alive == False:
            # End game
            self.run = False
            gameOver(self.game_screen, self.player.score, self.clock_delay)

        for enemy in self.enemy_group:
            if enemy.rect.bottom > self.screen_dims[1]:
                enemy.kill()
            if enemy.alive == False:
                self.camera.addWeapon(enemy.arms)
                self.dropped_weapons.add(enemy.arms)
                enemy.arms.addCharacterGroup(self.characters)
                enemy.kill()

        if len(self.enemy_group) == 0:
            self.level_complete = True

        #self.score_string.text = f'Score = {self.player.score}'
        self.score.val = self.player.score

        # Update camera position
        self.camera.scroll()

    def display(self):
        ''' Display

        This displays all our objects to the screen in order.  This
        takes place each frame.
        '''

        # Colour screen purple
        self.game_display.fill(self.colour['purple'])

        # Display background and map
        self.background.displayQ()
        self.game_map.display()

        # Display characters
        for enemy in self.enemy_group:
            enemy.display()
        
        self.player.display()

        for weapon in self.dropped_weapons:
            weapon.display()

        #print(self.projectiles)
        for projectile in self.projectiles:
            projectile.display()

        # scales the game_display to game_screen. Allows us to scale 
        # images
        scaled_surf = pygame.transform.scale(self.game_display,
                                                self.screen_dims)
        self.game_screen.blit(scaled_surf, (0, 0))

        self.score.display()
        self.level.display()

        # If in god mode, display text
        if self.god_mode:
            self.gt.display()

        # If waiting to change level, display text
        if self.level_complete:
            self.level_complete_text_1.display()
            self.level_complete_text_2.display()
Example #11
0
class Controller:

    state: AppState = AppState.INITIAL
    camera: Camera
    plc: PLC
    read_plc: PLCInterface = PLCInterface()
    write_plc: PLCWriteInterface = PLCWriteInterface(0)
    tank: Tank = Tank()
    frame: np.ndarray = np.zeros((640,480))
    drain_model: TFModel = None
    file_frame = None
    result_list = []
    final_result = False
    analyse_counter = 0
    last_request = 0
    total_reads = 3

    def __init__(self, is_picture=False):
        self.start_time = datetime.now()
        self.tank = Tank()
        self.camera = Camera()
        self.plc = PLC(self.camera.number)
        self.is_picture = is_picture
        if not is_picture:
            self.camera.start()
        self.model = TFModel(model_name='sticker')

    def get_frame(self):
        if self.is_picture:
            self.frame = self.file_frame
        else:
            _, self.frame = self.camera.read()

    def open_file(self, file):
        self.frame = cv.imread(file)
        self.file_frame = self.frame

    def process(self):
        self.tank.find(self.camera.number, self.frame, self.read_plc.partnumber)
        self.write_plc.tank_found = self.tank.found
        if self.tank.found:
            self.tank.get_sticker_position(self.frame)
            self.__predict_sticker()
            if self.read_plc.drain_camera:
                if self.drain_model == None:
                    self.drain_model = TFModel(model_name='drain')
                self.tank.get_drain_ml(self.frame, self.drain_model)

    def __predict_sticker(self):
        for sticker in self.tank.stickers:
            sticker.label_index, sticker.label = self.model.predict(sticker.image)
            sticker.update_label_info()

    def analyse(self) -> None:
        self.__clear_plc()
        sticker = Sticker()
        status: Deviation = Deviation.NONE
        qnt_stickers = len(self.tank.stickers)

        if not self.tank.found:
            print('Tank not Found')
            status = Deviation.TANK_NOT_FOUND

        if self.tank.check_drain and self.read_plc.drain_camera and (self.read_plc.drain_position != self.tank.drain_position):
            print('Drain on Wrong Position')
            status = Deviation.DRAIN_POSITION
        if qnt_stickers > 1:
            print('Found more stickers than needed')
            status = Deviation.STICKER_QUANTITY
        if qnt_stickers > 0 and not self.read_plc.sticker_camera:
            print('Found more stickers than needed')
            status = Deviation.STICKER_QUANTITY
        if self.read_plc.sticker_camera and len(self.tank.stickers) == 0:
            print('Sticker not found')
            status = Deviation.STICKER_NOT_FOUND
        if qnt_stickers:
            sticker = self.tank.stickers[0]
        if self.read_plc.sticker_camera and self.read_plc.sticker != sticker.label_char_index and qnt_stickers:
            print('Wrong Label, expected:' + str(self.read_plc.sticker) + ', received: ' + str(sticker.label))
            self.write_plc.inc_sticker = sticker.label_char_index
            status = Deviation.STICKER_VALUE
        if self.read_plc.sticker_camera and self.read_plc.sticker_angle != sticker.angle and qnt_stickers:
            print('Wrong Label Angle, expected:' + str(self.read_plc.sticker_angle) + ', received: ' + str(sticker.angle))
            self.write_plc.inc_angle = sticker.angle
            status = Deviation.STICKER_ANGLE
        if self.read_plc.sticker_camera and self.read_plc.sticker_position != sticker.quadrant and qnt_stickers:
            print('Wrong Label Position, expected:' + str(self.read_plc.sticker_position) + ', received: ' + str(sticker.quadrant))
            self.write_plc.position_inc_sticker = sticker.quadrant
            status = Deviation.STICKER_POSITION

        self.write_plc.cam_status = int(status)
        self.analyse_counter = self.analyse_counter + 1

        self.__get_final_result(status)


    def __get_final_result(self, status: Deviation):
        self.result_list.append(status)
        print(self.result_list)
        if len(self.result_list) >= self.total_reads:
            if Counter(self.result_list).most_common()[0][0] == 1:
                self.__job_done()
            self.result_list.pop(0)

    def __job_done(self):
        self.final_result = True
        self.write_plc.cam_status = Deviation.NONE
        self.write_plc.job_status = JobStatus.DONE
        self.write_plc.request_ack = False

    def __clear_plc(self) -> None:
        self.write_plc.position_inc_drain = 0
        self.write_plc.position_inc_sticker = 0
        self.write_plc.inc_sticker = 0
        self.write_plc.inc_angle = 0

    def check_skid(self):
        if not self.tank.found and self.read_plc.skid == 0:
            self.abort_job()

    def abort_job(self):
        self.__clear_plc()
        self.write_plc.cam_status = Deviation.TANK_NOT_FOUND
        self.write_plc.job_status = JobStatus.CANCELLED

    def show(self) -> None:
        frame = self.frame.copy()
        if self.tank.found:
            frame = draw_tank_center_axis(frame, self.tank)
            frame = draw_tank_rectangle(frame, self.tank)
            frame = draw_sticker(frame, self.camera, self.tank)
            frame = draw_drain_ml(frame, self.tank)
        frame = draw_roi_lines(frame, self.camera)
        frame = draw_center_axis(frame, self.camera)
        frame = draw_camera_info(frame, self.camera)
        frame = draw_plc_status(frame, self.plc, self.read_plc, self.write_plc)
        self.camera.show(frame)

    def new_request(self):
        return self.read_plc.request_number != self.last_request

    def confirm_request(self) -> None:
        print('New Job Request: ', self.read_plc.request_number)
        print(f'SKID: {self.read_plc.skid}, POPID: {self.read_plc.popid}')
        print(f'TANK: {self.read_plc.partnumber} PARAMETER: {self.read_plc.parameter}')
        print(f'STICKER: {self.read_plc.sticker}, ANGLE: {self.read_plc.sticker_angle}, DRAIN: {self.read_plc.drain_position}')
        self.last_request = self.read_plc.request_number
        self.read_plc.read_request = False
        self.write_plc.request_ack = True
        self.write_plc.cam_status = Deviation.TANK_NOT_FOUND
        self.write_plc.job_status = int(JobStatus.RUNNING)
        self.final_result = False
        self.analyse_counter = 0
        self.result_list.clear()
        sleep(1)

    def get_command(self) -> None:
        key = cv.waitKey(1) & 0xFF
        key_pressed(key, self.camera, self.tank)
        if key == ord('n'):
            self.__get_fake_parameters()
        elif key == ord('o'):
            self.__job_done()
        elif key == ord('i'):
            self.__print_plc_values()

    def send_command(self, key) -> None:
        key_pressed(key, self.camera, self.tank)

    def start_plc(self) -> None:
        logger.info('Starting PLC Thread')
        self.plc.connect()
        self.write_plc = PLCWriteInterface(self.plc.db_write['size'])
        Thread(name='thread_plc', target=self.update_plc, daemon=True).start()

    def update_plc(self) -> None:
        last_life_beat = -1

        while self.plc.enabled:
            read_data = self.plc.read()
            self.read_plc.update(read_data)
            if self.read_plc.life_beat == last_life_beat:
                logger.error('PLC is not responding... Trying to reconnect')
                self.plc.disconnect()
                sleep(5)
                self.plc.connect()
            else:
                last_life_beat = self.read_plc.life_beat
                self.write_plc.update_life_beat()
                _bytearray = self.write_plc.get_bytearray()
                self.plc.write(_bytearray)
                sleep(self.plc.update_time)
        else:
            logger.warning('PLC is not Enabled')

    def set_state(self, state: AppState) -> None:
        logger.info(f'Updating state to: {state}')
        self.state = state

    def save_result(self) -> None:
        try:
            now = datetime.now()
            path = f'../results/{now.year}/{now.month}/{now.day}/{self.read_plc.popid}'
            Path(path).mkdir(parents=True, exist_ok=True)
            file = f'{path}/{now.strftime("%H%M%S")}_{self.read_plc.partnumber}.jpg'
            logger.info(f'Saving results to {path}')
            cv.imwrite(file, self.frame)
            results_logger.info(f'{self.read_plc.popid} - {self.read_plc.partnumber} - {self.read_plc.parameter}')
        except Exception as e:
            logger.exception(e)

    def __print_plc_values(self) -> None:
        print('PLC READ:')
        print(self.read_plc.__dict__)
        print('\nPLC WRITE:')
        print(self.write_plc.__dict__)

    def __get_fake_parameters(self) -> None:
        self.read_plc.read_request = True
        self.read_plc.sticker = 1
        self.read_plc.sticker_angle = 2
        self.read_plc.sticker_position = 4
        self.read_plc.drain_position = 0
Example #12
0
def toggle_full_screen(camera: Camera):
    camera.full_screen = not camera.full_screen
    if camera.full_screen:
        cv.setWindowProperty(camera.window_name, cv.WND_PROP_FULLSCREEN, cv.WINDOW_FULLSCREEN)
    else:
        cv.setWindowProperty(camera.window_name, cv.WND_PROP_FULLSCREEN,cv.WINDOW_NORMAL)
Example #13
0
	def setUp(self):
		self.camera = Camera(0)
Example #14
0
def reload_config(camera: Camera, tank: Tank):
    logger.info("Reloading configuration...")
    tank.load_config()
    camera.load_config()
Example #15
0
def update_camera_config(camera: Camera, key, value) -> None:
    setattr(camera, key, value)
    if camera.rpi_camera:
        camera.set_hardware_rpi()
    else:
        camera.set_hardware_threaded()
Example #16
0
def pause(camera: Camera):
    camera.pause = not camera.pause
    status = "Pause" if camera.pause else "Play"
    logger.info(status + " Video Stream")
Example #17
0
def main():
    print("initializing")
    pool = ThreadPool(processes=1)
    #Time operator have to push button after product is detected
    IMG_SAVE_DELAY = 500
    #This is used to signal that if operator dont push button then save image to approved folder
    flagPhotoSensor = False
    #Resolution on image
    resolution = (1280, 720)
    #Paths to put images
    path_approved = '/media/storage/good/'
    path_not_approved = '/media/storage/bad/'
    #Create camera object that takes images and store in approved or not approved folder
    camera = Camera(resolution, 'png', path_approved, path_not_approved)
    #GPIO stuff
    on_off = GPIO(7, 'in')
    disallowed_img = GPIO(138, 'in')
    disallowed_img.edge = 'rising'
    photoSensor = GPIO(140, 'in')
    photoSensor.edge = 'rising'
    #Create Display object
    display = Display()
    #Variable to handle debounce on input from photosensor
    lastPhotoSensorValue = False
    #Variable to handle debounce on input from operator button
    lastOperatorBtnValue = False
    #Get time in ms
    currentTime_ms = lambda: int(round(time.time() * 1000))
    #Calculate new time - old time to get time difference between two events
    deltaTime_ms = lambda oldTime_ms: int(round(time.time() * 1000)
                                          ) - oldTime_ms
    #Store time to calculate time difference
    oldTime_ms = currentTime_ms()
    print("initialization done")

    #Run forever
    while True:
        #If system is turned on by switch, run program
        display.off()
        if on_off.read() or True:
            print("System started")
            #Make sure no old values are stored
            flagPhotoSensor = False
            camera.camera.start()
            #Stay in loop as long as system is turned on
            while on_off.read() or True:

                #If there is a product in front of camera, take a picture and record the time
                if photoSensor.read(
                ) and not lastPhotoSensorValue and not flagPhotoSensor:

                    #img = camera.camera.get_image()
                    async_img = pool.apply_async(camera.take_image)
                    oldTime_ms = currentTime_ms()
                    #Save that photo sensor has been high
                    flagPhotoSensor = True

                #If operator push button before IMG_SAVE_DELAY time, save image to not approved folder
                #and blink red light once to inform operator
                while deltaTime_ms(oldTime_ms) <= IMG_SAVE_DELAY:
                    if disallowed_img.read() and not lastOperatorBtnValue:
                        img = async_img.get()
                        threading.Thread(target=display.show_img,
                                         args=(img, False)).start()
                        camera.save_img(img, False)
                        flagPhotoSensor = False

                lastOperatorBtnValue = disallowed_img.read()

                #if operator dont push button before IMG_SAVE_DELAY time run out, save
                #image to approved folder and blink green light.
                if (deltaTime_ms(oldTime_ms) >
                        IMG_SAVE_DELAY) and flagPhotoSensor:
                    img = async_img.get()
                    threading.Thread(target=display.show_img,
                                     args=(img, True)).start()
                    camera.save_img(img, True)
                    flagPhotoSensor = False

            camera.camera.stop()
            lastPhotoSensorValue = photoSensor.read()
Example #18
0
class Server:
    """
    Server für die Kommunikation mit dem Client und allen angeschlossenen Geräten.

    Kümmert sich um die Steuerung des Schrittmotors, der Ultraschallsensoren und der Kamera.
    """
    def __init__(self):
        """
        Konstruktor der Klasse Server.

        Erstellt den Socket für die Netzwerk-Kommunikation.
        Initialisiert die für den Server notwendigen Einstellugnen und Variablen
        """
        self.port = 50007  # Initialisiere den Host-Port auf 50007
        self.ip = ""  # Initialisiere die Host-IP auf einen leeren String
        self.header_size = 10  # Setze die Header Größe auf 10 fest
        self.socket = socket.socket(
            socket.AF_INET,
            socket.SOCK_STREAM)  # Erstelle einen neuen TCP/IPv4 Socket
        self.socket.bind(
            (self.ip, self.port)
        )  # Startet den Socket und bindet ihn an die eigene IP und den festgelegten Port
        self.conn, self.conn_ip, self.conn_port = None, None, None  # Initialisiere die Variablen für die spätere Verbindung mit dem Client mit None
        self.exit = False  # Variable "exit" als Abbruchbedingung für die Threads mit False initialisieren
        self.thread_recv = Thread(
            target=self.receive
        )  # Erstelle einen neuen Thread mit der Funktion "receive" als Ziel
        self.camera_selected = None  # Initialisiere Variable mit None
        self.pictures_to_take = None  # -
        self.motor = None  # -
        self.motor_running = False  # Initialisiere Variable mit False
        self.motor_data = None  # Initialisiere Variable mit None
        self.hc_sr04_left = None  # -
        self.hc_sr04_right = None  # -

    def run(self) -> None:
        """
        Funktion zum Starten des Servers und einrichten einer eingehenden Verbindung.

        Server wartet auf eine eingehende Verbindung des Clients und startet anschließend den Thread zum Empfangen von Nachrichten.

        :return: None
        """

        self.socket.listen(1)  # Warte auf eine eingehende Verbindung
        print("Server gestartet")
        self.conn, (self.conn_ip, self.conn_port) = self.socket.accept(
        )  # Akzeptiere die eingehende Verbindung und speichere diese mit der dazugehörigen IP und dem Port
        print(f"{self.conn_ip} verbunden")
        try:
            self.thread_recv.start(
            )  # Starte den Thread zum Empfangen von Nachrichten
        except:
            pass

    def stop(self) -> None:
        """
        Funktion zum Beenden des Servers.

        Falls noch eine aktive Verbindung zum Client besteht wird diese beendet.
        Beendet anschließend den ausgehenden Socket und das Programm.

        :return: None
        """

        if self.conn:
            self.on_disconnect_client()
        self.socket.close()
        sys.exit()

    def receive(self) -> None:
        """
        Funktion zum Empfangen von Nachrichten und Daten vom Client.

        Die Funktion läuft in einer while-Schleife, um so kontinuirlich Daten zu empfangen.

        Empfängt vom Client nacheinander 2 (falls keine Daten vorhanden sind) bzw. 3 (falls Daten vorhanden sind) Nachrichten:
        1.) 10 Byte großer Header, der die Größe des nachfolgenden Befehls-Objekts beinhaltet.
        2.) Befehels-Objekt mit variabler Größe. Beinhaltet einen 10 Byte-Header mit der Größe des nachfolgenden Daten-Objekts, falls vorhanden, und den Befehl des Clients.
        3.) Das Daten-Objekt mit variabler Größe, falls vorhanden.

        Die empfangenen Nachrichten werden anschließend dekodiert, in ihren ursprünglichen Datentyp umgewandelt und zu einer Nachricht zusammengefügt.
        Die zusammengefügte Nachricht wird zur Weiterverarbeitung durch den Server an eine separate Funktion übergeben.

        Durch das separate Empfangen von Befehl und Daten mit einem jeweiligen Header kann gewährleistet werden, dass Befehl und Daten mit unterschiedlicher Größe ordnungsgemäß empfangen werden.

        :return: None
        """

        command = None
        command_len = None
        data = None
        data_len = None

        while not self.exit:  # Dauerschleife, während "_connected" True ist
            try:
                command_len = self.conn.recv(
                    self.header_size
                )  # Empfange den ersten 10 Byte großen Header
                command_len = int(command_len.decode().strip(
                ))  # Dekodiere den Header und konvertiere ihn in einen int
                if command_len > 0:  # Überprüfe, ob der Header größer als 0 ist
                    command = self.conn.recv(
                        command_len
                    )  # -> Empfange das Befehls-Objekt mit der im Header angegebenen Größe
                    data_len = int(
                        command[:self.header_size].decode().strip()
                    )  # -> Extrahiere aus dem Befehls-Objekt den 10 Byte großen Header für das Datenobjekt, dekodiere diesen und wandle ihn in einen int um
                    command = command[
                        self.
                        header_size:]  # -> Extrahiere aus dem Befehls-Objekt den Befehls-Teil
                    command = pickle.loads(command)  # -> Dekodiere den Befehl
                    if data_len > 0:  # Überprüfe, ob der Daten-Header größer als 0 ist
                        data = self.conn.recv(
                            data_len)  # -> Empfange das Daten-Objekt
                        data = pickle.loads(
                            data)  # -> Dekodiere das Daten-Objekt
                print(
                    f"message received. command_len = {command_len}, command = {command}, data_len = {data_len}, data = {data}"
                )
                self.handle_command(
                    command, data
                )  # Übergebe den Befehl und die Daten an eine separate Funktion zur weiteren Verarbeitung
            except Exception as e:  # Falls ein Fehler auftritt
                print(e)  # -> Gebe den Fehler in der Konsole aus
            finally:
                command = None
                command_len = None
                data = None
                data_len = None

    def send(self, command: str, data: Any = None) -> None:
        """
        Funktion zum Senden von Befehlen und Daten an den Client.

        Nimmt einen Befehl und optional Daten in beliebiger Form (int, str, list, tuple) entgegen.
        Mit Hilfe der "pickle"-Bibliothek werden der Befehl und die Daten jeweils in ein Byte-Objekt konvertiert.
        Anschließend wird die jeweilige Größe der Byte-Objekte berechnet.

        Die zu verschickende Nachricht besteht aus drei Teilen, die separat nacheinander geschickt werden:
        1.) 10 Byte großer Header, der die Größe des nachfolgenden Befehls-Objekts beinhaltet.
        2.) Befehels-Objekt mit variabler Größe. Beinhaltet einen 10 Byte-Header mit der Größe des nachfolgenden Daten-Objekts, falls vorhanden, und den Befehl des Clients.
        3.) Das Daten-Objekt mit variabler Größe, falls vorhanden.

        Falls keine Daten zum versenden vorhanden sind, fällt das Daten-Objekt weg und der dazugehörige Header im Befehls-Objekt beinhaltet eine 0 als Größe.

        Durch das seperate versenden von Befehl und Daten mit einem jeweiligen Header kann gewährleistet werden, dass Befehel und Daten mit unterschiedlicher Größe ordnungsgemäß übertragen werden.

        :param command: zu verschickendes Kommando bzw. Nachricht
        :param data: zu verschickende Daten, falls vorhanden
        :type command: str
        :type data: Any

        :return: None
        """

        try:
            if data is not None:  # Überprüfe, ob Daten übergeben wurden
                data = pickle.dumps(
                    data
                )  # -> Konvertiere die übergebenen Daten in ein Byte-Objekt
                data_len = bytes(
                    f"{len(data):<{self.header_size}}", 'utf-8'
                )  # -> Ersetlle einen 10 Byte großen Header mit der Größe des Daten-Objekts als Inhalt
            else:  # Falls keine Daten übergeben wurden
                data_len = bytes(
                    f"{0:<{self.header_size}}", 'utf-8'
                )  # -> Erstelle einen 10 Byte großen Header mit 0 als Größe des Daten-Objektes als Inhalt
            command = pickle.dumps(
                command)  # Konvertiere den Befehl in ein Byte-Objekt
            command = data_len + command  # Der Daten-Header wird dem Befehl im Befehls-Objekt vorangestellt
            command_len = bytes(
                f"{len(command):<{self.header_size}}", 'utf-8'
            )  # Erstelle einen 10 Byte großen Header mit der Größe des Befehls-Objekts als Inhalt
            self.conn.send(command_len)  # Verschicke den Befehls-Header
            self.conn.send(command)  # Verschicke das Befehls-Objekt
            if data:  # Falls Daten übergeben wurden
                self.conn.send(data)  # -> Schicke das Daten-Objekt
            print(
                f"message send. command_len = {command_len}, command = {command}, data_len = {data_len}, data = {data}"
            )
        except Exception as e:  # Falls ein Fehler auftritt
            print(e)  #   -> Fehler wird in der Konsole ausgegeben

    def handle_command(self, command: str, data: Any = None) -> None:
        """
        Funktion zum verarbeiten der empfangenen Befehle und Daten.

        Es wird der übergebene Befehl mit zu erwartenden Befehlen verglichen und bei Übereinstimmung eine entsprechende Funktion zum ausführen des Befehls aufgerufen.

        :param command: übergebener Befehl
        :param data: übergebene Daten, falls vorhanden
        :type command: str
        :type data: Any

        :return: None
        """

        if command == "connect_camera":
            self.on_connect_camera(data)
        elif command == "get_camera_options":
            self.on_get_camera_options()
        elif command == "disconnect_camera":
            self.on_disconnect_camera()
        elif command == "disconnect_client":
            self.on_disconnect_client()
        elif command == "connect_motor":
            self.on_connect_motor()
        elif command == "disconnect_motor":
            self.on_disconnect_motor()
        elif command == "connect_sensors":
            self.on_connect_sensors()
        elif command == "disconnect_sensors":
            self.on_disconnect_sensors()
        elif command == "set_slider_settings":
            self.on_set_slider_settings(data)
        elif command == "set_slider_settings_manual":
            self.on_set_slider_settings_manual(data)
        elif command == "set_camera_settings":
            self.on_set_camera_settings(data)
        elif command == "set_camera_settings_takes":
            self.on_set_camera_settings_takes(data)
        elif command == "take_picture":
            self.on_take_picture()
        elif command == "start_slider":
            self.on_start_slider()
        elif command == "measure_distance":
            self.measure_distance()
        elif command == "position_slider":
            self.on_position_slider(data)

    def on_connect_camera(self, data: int) -> None:
        """
        Funktion zum Verbinden mit einer angeschlossenen Kamera.

        Wird ausgeführt, sobald der Client die Nachricht "connect_camera" sendet.
        Funktion liest zuerst alle Verfügbaren Kameras aus.
        Sofern eine oder mehrere Kameras verfügbar sind und noch keine Kamera verbunden ist wird die Kamera des übetrragenenen Indizes verbundne.
        Sollte keine Kamera verfügbar sein, wird dem Client eine Fehlermeldung zurück geschickt.

        :param data: Index der zu verbindenden Kamera
        :type data: int

        :return: None
        """

        cameras = Camera.get_available_cameras()
        if cameras:
            if not self.camera_selected:
                self.camera_selected = Camera(data)
            self.send('camera_init', self.camera_selected.get_information())
        else:
            self.send('camera_not_available')

    def on_get_camera_options(self) -> None:
        """
        Funktion zum Versenden der verfügbaren Kamera Einstellungsmöglichkeiten.

        Wird ausgeführt, sobald der Client die Nachricht "get_camera_options" sendet.
        Liest von der verbundenen Kamera die verfügbaren Einstellungsmöglichkeiten aus.
        Anschließend werden diese an den Client gesendet.

        :return: None
        """

        options = self.camera_selected.get_options()
        self.send('camera_options', options)

    def on_disconnect_camera(self) -> None:
        """
        Funktion zum Trennen der Verbindung mit der Kamera.

        Wird ausgeführt, sobald der Client die Nachricht "disconnect_camera" sendet.
        Beendet die Verbindung mit der Kamera und schickt eine Meldung an den Client.

        :return: None
        """

        self.camera_selected.exit()
        self.camera_selected = None
        self.send('camera_disconnected')

    def on_disconnect_client(self) -> None:
        """
        Fnktion zum Trennen der Verbindung mit dem Client.

        Wird ausgeführt, sobald der Client die Nachricht "disconnect_client" sendet.
        Sendet dem Client eine Bestätigung, dass die Verbindung getrennt wird.
        Anschließend wird der Socket geschlossen und die Abbruchbedingung für die Threads auf True gesetzt.
        Dadurch terminieren die Threads und der Server beendet sich.

        :return: None
        """

        print(f"{self.conn_ip} hat die Verbindung getrennt")
        self.send("disconnect")
        sleep(0.5)
        self.exit = True
        self.conn.close()

    def on_connect_motor(self) -> None:
        """
        Funktion zum Verbinden des Schrittmotors.

        Wird ausgeführt, sobald der Server die Nachricht "connect_motor" sendet.
        Instanziiert einen neuen Schrittmotor und schickt dem Client eine Bestätigugn.

        :return: None
        """

        self.motor = SchrittMotor()
        self.send('motor_connected')

    def on_disconnect_motor(self) -> None:
        """
        Funktion zum Trennen der Verbindung mit dem Schrittmotor.

        Wird ausgeführt, sobald der Client die Nachricht "disconnect_motor" sendet.
        Deaktiviert den Schrittmotor, setzt die Verbindung zurück und sendet dem Client eine Bestätigung.

        :return: None
        """

        self.motor.disable()
        self.motor = None
        self.send('motor_disconnected')

    def on_connect_sensors(self) -> None:
        """
        Funktion zum Verbinden der Ultraschallsensoren.

        Wird ausgeführt, sobald der Client die Nachricht "connect_sensors" sendet.
        Stellt zuerst eine Verbindung mit den zwei Ultraschallsensoren (links, rechts) her.
        Sendet eine Bestätigung, dass die Sensoren verbunden wurden.
        Misst zum Schluß die Abstände der beiden Sensoren.

        :return: None
        """

        self.hc_sr04_left = HcSr04(Pins.Ultrasonic.LEFT_TRIGGER,
                                   Pins.Ultrasonic.LEFT_ECHO)
        self.hc_sr04_right = HcSr04(Pins.Ultrasonic.RIGHT_TRIGGER,
                                    Pins.Ultrasonic.RIGHT_ECHO)
        self.send('sensors_connected')
        sleep(0.1)
        self.measure_distance()

    def on_disconnect_sensors(self) -> None:
        """
        Funktion zum Beenden der Verbindung mit den Ultraschallsensoren.

        Wird ausgeführt, sobald der Client die Nachricht "disconnect_sensors" sendet.
        Deaktiviert zuerst die beiden Sensoren, setzt die Verbindung zurück und sendet eine Besätigung.

        :return: None
        """

        self.hc_sr04_left.stop()
        self.hc_sr04_right.stop()
        self.hc_sr04_left = None
        self.hc_sr04_right = None
        self.send('sensors_disconnected')

    def on_set_slider_settings(self, data: Dict) -> None:
        """
        Funktion zum Setzen der Slider-Einstellungen.

        Wird ausgeführt, sobald der Client die Nachricht "set_slider_settings" sendet.
        Speichert die Einstellungen in einer Variable, setzt die Frequenz des Motors und sendet eine Bestätigung.

        :param data: Einstellungen des Sliders
        :type data: dict

        :return: None
        """

        self.motor_data = data
        self.motor.set_frequency(int(self.motor_data['frequency']))
        self.send('slider_settings_set', data)

    def on_set_slider_settings_manual(self, data: Dict) -> None:
        """
        Funktion zum manuellen positionieren des Sliders.

        Wird ausgeführt, sobald der Client die Nachricht "set_slider_settings_manual" sendet.
        Erstellt zwei neue Threads. Einen Thread mit der Funktion "move_manual" als Ziel, um den Slider zu positionieren.
        Der Zweite Thread mit der Funktion "measure_distance_thread" als Ziel dient zum kontinuirlichen Messen der Abstände.
        Vor dem Starten wird an den Client gesendet, dass der Slider gestartet wurde.
        Nach erfolgreichem Beenden der Threads wird die Nachricht, dass der Slider-Lauf beendet wurde, an den Client geschickt.

        :param data: Manuelle Einstellungen des Sliders
        :type data: dict

        :return: None
        """

        self.send('slider_started')
        thread_slider = Thread(target=self.move_manual, args=(data, ))
        thread_distance = Thread(target=self.measure_distance_thread)
        thread_slider.start()
        sleep(0.1)
        thread_distance.start()
        thread_slider.join()
        thread_distance.join()
        self.send('slider_finished')

    def on_start_slider(self) -> None:
        """
        Funktion zum Starten des Slider-Laufs.

        Wird ausgeführt, sobald der Client die Nachricht "start_slider" sendet.
        Sendet zuerst eine Nachricht an den Client, dass der Slider gestartet wurde.
        Erstellt anschließend zwei Threads.
        Der erste, mit der Funktion "move_camera" als Ziel, dient zum Bewegen des Sliders.
        Der zweite, mit der Funktion "measure_distance_thread" als Ziel, dient zum kontinuirlichen Messen der Abstände.
        Sobald beide Threads terminiert sind wird an den Client die Nachricht gesendet, dass der Slider-Lauf beendet ist.

        :return: None
        """

        self.send('slider_started')
        thread_camera = Thread(target=self.move_camera)
        thread_distance = Thread(target=self.measure_distance_thread)
        thread_camera.start()
        sleep(0.1)
        thread_distance.start()
        thread_camera.join()
        thread_distance.join()
        self.send('slider_finished')

    def on_set_camera_settings(self, data: Dict) -> None:
        """
        Funktion zum Setzen der neuen Kamnera Einstellungen.

        Wird ausgeführt, sobald der Client die Nachricht "set_camera_settings" sendet.
        Stellt bei der verbundenen Kamera die gesendeten Optionen ein.
        Sendet anschließend die neuen Einstellungen an den Client zurück.

        :param data: neue Einstellungen der Kamera
        :type data: dict

        :return: None
        """

        self.camera_selected.set_focal(data['focal'])
        self.camera_selected.set_shutter_speed(data['shutter'])
        self.camera_selected.set_iso(data['iso'])
        self.send('camera_values', self.camera_selected.get_information())

    def on_set_camera_settings_takes(self, data: str) -> None:
        """
        Funktion zum Einstellen der zu tätigen Aufnamhen.

        Wird ausgeführt, sobald der Client die Nachricht "set_camera_settings_sendet".

        :param data: Anzahl der zu tätigen Aufnahmen
        :type data: str

        :return: None
        """

        self.pictures_to_take = int(data)

    def on_take_picture(self) -> None:
        """
        Funktion zum Aufnehmen eines einzelnen Bildes.

        Wird ausgefführt, sobald der Client die Nachricht "take_picture" sendet.

        :return: None
        """

        self.camera_selected.take_picture()

    def on_position_slider(self, data: str) -> None:
        """
        Funktion zum automatischen Positionieren des Sliders.

        Wird ausgeführt, sobald der Client die Nachricht "position_slider" sendet.
        Sendet zuerst an den Client, dass der Slider gestartet wurde.
        Erstellt anschließend zwei Threads.
        Der erste, mit der Funktion "position_slider" als Ziel, dient zum Bewegen/Positionieren des Sliders.
        Der zweite, mit der Funktion "measure_distance_thread" als Ziel, dient zum kontinuirlichen Messen der Abstände.
        Nachdem beide Threads terminiert haben wird an den Client gesendet, dass der Slider-lauf beendet ist.

        :param data: Einstellung zum Positionieren des Sliders.
        :type data: str

        :return: None
        """

        self.send('slider_started')
        thread_position = Thread(target=self.position_slider, args=(data, ))
        thread_distance = Thread(target=self.measure_distance_thread)
        thread_position.start()
        sleep(0.2)
        thread_distance.start()
        thread_position.join()
        thread_distance.join()
        self.send('slider_finished')

    def position_slider(self, new_position: str) -> None:
        """
        Funktion zum automatischen Positioneren des Sliders.

        Misst zuerst die Abstände beider Sensoren und speichert diese.
        Berechnet anhand der neuen Zielposition den Abstand zu dieser und daraus die zu fahrenden Schritte für den Motor.
        Fährt den Slider anschließend zur neuen Position.

        :param new_position: Neue Position des Sliders
        :type new_position: str

        :return: None
        """

        self.motor.set_frequency(900)  # Setzt die Frequenz des Motors
        self.measure_distance()  # Misst die Entfernungen neu
        distance_left = self.hc_sr04_left.get_distance(
        )  # Speichert die Entfernung des linken Sensors
        distance_right = self.hc_sr04_right.get_distance(
        )  # Speichert die Entfernung des rechten Sensors
        rotate = None  # Initialisiert Variable mit None
        distance = None  # -
        self.motor_running = True  # Setzt "motor_running" auf True (Für Ultraschallsensoren wichtig)
        if new_position == "Links":  # Falls neue Position "Links" ist
            rotate = self.motor.rotate_counterclockwise  #   -> weise "rotate" die Funktion "rotate_counterclockwise" des Motors zu
            distance = distance_left - 5  #   -> Berechne die zu fahrende Distanz
        elif new_position == "Rechts":  # Falls neue Position "Rechts" ist
            rotate = self.motor.rotate_clockwise  #   -> weise "rotate" die Funktion "rotate_clockwise" des Motors zu
            distance = distance_right - 5  #   -> Berechne die zu fahrende Distanz
        elif new_position == "Mitte":  # Falls neue Position "Mitte" ist
            if distance_left > distance_right:  #   -> Überprüfe, ob der Abstand links größer ist als rechts
                rotate = self.motor.rotate_counterclockwise  #       -> weise "rotate" die Funktion "rotate_counterclockwise" des Motors zu
                distance = (distance_left - distance_right
                            ) / 2  #       -> Berechne die zu fahrende Distanz
            elif distance_left < distance_right:  #   -> Überprüfe, ob der Abstand links kleiner ist als rechts
                rotate = self.motor.rotate_clockwise  #       -> weise "rotate" die Funktion "rotate_clockwise" des Motors zu
                distance = (distance_right - distance_left
                            ) / 2  #       -> Berechne die zu fahrende Distanz
        if distance > 0:  # Überprüft, ob die berechnete Distanz größer 0 ist
            steps = int(
                distance / 4 * 200
            )  #   -> Berechnet aus der Distanz die zu fahrenden Schritte

            for _ in range(0, int(
                    steps)):  # Rotiere für die Anzahl der berechneten Schritte
                rotate()  # -

        self.motor_running = False  # Setze "motor_running" auf False
        self.motor.disable()  # Stoppe den Schrittmotor

    def move_camera(self) -> None:
        """
        Funktion zum Bewegen des Sliders mit Auslösen der Kamera.

        Überprüft, in welche Richtung der Motor drehen muss.
        Nimmt anschließend das erste Bild auf und berechnet, in welchen Abständen die weiteren Bilder aufzunehmen sind.
        Bewegt den Slider an die nächste Position und nimmt ein Bild auf.
        Wiederholt die Prozedur solange, bis alle Bilder aufgenommen sind.

        :return: None
        """

        rotate = self.motor.rotate_counterclockwise if self.motor_data[
            'direction'] == "Links" else self.motor.rotate_clockwise  # Überprüft, in welche Richtung der Motor drehen soll
        self.motor_running = True  # Setzt "motor_running" auf True
        self.camera_selected.take_picture()  # Nimmt ein Bild auf
        self.pictures_to_take -= 1  # Dekrementiere die Anzahl, der aufzunehmenden Bilder um eins
        steps = (int(self.motor_data['distance']) /
                 4) * 200  # Rechne die Distanz in Schritte um
        steps_per_cycle = int(
            steps
        ) // self.pictures_to_take  # Berechne, wie viele Schritte bis zum nächsten Bild zu machen sind
        for _ in range(
                self.pictures_to_take
        ):  # Wiederholle Vorgang, bis alle Bilder aufgenommen sind
            for _ in range(
                    steps_per_cycle
            ):  #   -> Rotiere die berechnete Anzahl an Schritten
                rotate()  #   -> -
            self.motor.disable()  # Deaktiviere den Motor
            sleep(0.5)
            self.camera_selected.take_picture()  # Machen ein Bild
        self.motor_running = False  # Setze "motor_running" auf False
        self.motor.disable()  # Deaktiviere den Motor

    def move_manual(self, data: Dict) -> None:
        """
        Funktion bewegt den Slider in manuellen Schritten.

        Stellt zuerst die Frequenz des Motors ein.
        Übeprüft dann, in welche Richtung der Motor sich drehen muss und rotiert anschließend für die übertragene Anzahl an Schritten.
        Zum Schluss wird der Motor deaktiviert.

        :param data: manuelle Einstellungen des Sliders
        :type data: dict

        :return: None
        """

        self.motor.set_frequency(data['frequency'])
        rotate = self.motor.rotate_counterclockwise if data[
            'direction'] == "Links" else self.motor.rotate_clockwise
        self.motor_running = True
        steps = int(data['distance'])
        for _ in range(steps):
            rotate()
        self.motor_running = False
        self.motor.disable()

    def measure_distance(self) -> None:
        """
        Funktion zum einmaligen Messen der Abstände der beiden Sensoren.

        Misst am linken und rechten Sensor die Abstönde.
        Anschließend werden die Messungen an den Client gesendet.

        :return: None
        """

        self.hc_sr04_left.measure(samples=5)
        self.hc_sr04_right.measure(samples=5)
        self.send('distance',
                  data={
                      'left': self.hc_sr04_left.get_distance(),
                      'right': self.hc_sr04_right.get_distance()
                  })
        sleep(0.1)

    def measure_distance_thread(self) -> None:
        """
        Funktion zum kontinuirlichen Messen der Abstände, solange "motor_running" True ist.

        Misst am linken und rechten Sensor die Abstönde.
        Anschließend werden die Messungen an den Client gesendet.

        :return: None
        """

        while self.motor_running:
            self.hc_sr04_left.measure()
            self.hc_sr04_right.measure()
            print(
                f"Links: {self.hc_sr04_left.get_distance()} cm/ Rechts: {self.hc_sr04_right.get_distance()} cm"
            )
            self.send('distance',
                      data={
                          'left': self.hc_sr04_left.get_distance(),
                          'right': self.hc_sr04_right.get_distance()
                      })
            sleep(0.1)
Example #19
0
    print("> -c: Calibrate the camera")
    print("> -e: Explore pipeline and plot inner stages")
    print("> Do not enter flag to run the pipeline")
    print()


def parseCommands():
    '''Parse the command line arguments'''

    command = Commands.NONE
    if len(sys.argv) > 1:
        if sys.argv[1] == '-c':
            command = Commands.CALIBRATE
        elif sys.argv[1] == '-e':
            command = Commands.EXPLORE
        else:
            help()
            exit(0)

    return command


if __name__ == '__main__':
    command = parseCommands()
    if command == Commands.CALIBRATE:
        Camera.calibrate()
    elif command == Commands.EXPLORE:
        exploratory_pipeline()
    else:
        createVideo(video_in, video_out)
Example #20
0
def play(screen, height):
    global current_level
    smooth_end = 0
    finished = []

    alpha_surf = pygame.Surface(screen.get_size(), pygame.SRCALPHA)
    alpha_surf.fill((255, 255, 255, 220), special_flags=pygame.BLEND_RGBA_MULT)

    camera = Camera(screen.get_size()[1])

    running = True
    player1.isMoving = True
    player1.isMovingDown = True
    player2.isMoving = True
    player2.isMovingDown = True
    draw_sprites = pygame.sprite.Group()

    while running:
        playable = player1.isPlayable and player2.isPlayable
        draw_sprites.empty()

        time_delta = clock.tick(fps) / 1000

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                terminate()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    terminate()
                else:
                    eval(str(buttons.get(event.key)))

        alpha_surf.fill((255, 255, 255, 250),
                        special_flags=pygame.BLEND_RGBA_MULT)
        new_alpha = pygame.Surface(screen.get_size(), pygame.SRCALPHA)
        screen.fill((0, 0, 0))

        if not player1.move(time_delta, player_group, tile_group, all_sprites,
                            alpha_surf, finish):
            finished.append(1)

        if not player2.move(time_delta, player_group, tile_group, all_sprites,
                            alpha_surf, finish):
            finished.append(2)

        if playable and not finished:
            if player1.rect.y > screen.get_size()[1]:
                finish.rect.y = player2.rect.y - finish.rect.h
                finished.append(2)
            if player2.rect.y > screen.get_size()[1]:
                finish.rect.y = player1.rect.y - finish.rect.h
                finished.append(1)

        elif ((not player1.isPlayable and not player2.isPlayable)
              and (not finished and
                   (player1.rect.y <= screen.get_size()[1] * 0.6
                    or player2.rect.y <= screen.get_size()[1] * 0.6))):
            player1.isPlayable = True
            player2.isPlayable = True
            alpha_surf.blit(
                pygame.transform.scale(load_image("start.png"),
                                       screen.get_size()), (0, 0))

        player1.inverted += player2.other_inverted * 3
        player2.inverted += player1.other_inverted * 3
        player1.other_inverted, player2.other_inverted = 0, 0

        y = camera.scroll_for(
            player1 if player1.rect.y < player2.rect.y else player2)

        new_alpha.blit(alpha_surf, (0, -y))
        alpha_surf = new_alpha

        for sprite in all_sprites:
            camera.apply(sprite)
        camera.apply(finish)

        screen.blit(alpha_surf, (0, 0))

        for sprite in tile_group:
            if 0 - 2 / 3 * tile_size < sprite.rect.y < height:  # + tile_size:
                sprite.add(draw_sprites)

        draw_sprites.draw(screen)

        pygame.display.flip()

        if finished:
            if playable:
                result = "Player " + str(finished[0]) + " has won!" if len(
                    finished) == 1 else "draw!"
                if result == "draw!":
                    player1.isPlayable = False
                    player2.isPlayable = False
                else:
                    k = ("player" +
                         str(int(result.split("Player ")[1][0]) % 2 + 1) +
                         ".switch_off()")
                    eval(k)

            smooth_end += time_delta

            if smooth_end > 1.22:
                time.sleep(0.3)
                running = False
                SCORE[1] += 1 in finished
                SCORE[2] += 2 in finished
                current_level += 1
                return result