← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~alisonken1/openlp/pjlink2-l into lp:openlp

 

Ken Roberts has proposed merging lp:~alisonken1/openlp/pjlink2-l into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~alisonken1/openlp/pjlink2-l/+merge/333537

- Move openlp.core.lib.projector to openlp.core.projectors
- Move openlp.core.ui.projector to openlp.core.projectors
- Move tests.functional.openlp_core/lib/test_projector* to tests.functional.openlp.core.projectors
- Fix imports for new projector location
- Fix delete projector item in ui.manager
- Fix projector tests

All tests pass local execution.

--------------------------------
lp:~alisonken1/openlp/pjlink2-l (revision 2783)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2267/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2169/
[FAILURE] https://ci.openlp.io/job/Branch-03-Interface-Tests/2050/
Stopping after failure

Multiple test failures are all in servicemanager. Example:

ERROR: test_start_time_context_menu (tests.interfaces.openlp_core.ui.test_servicemanager.TestServiceManager)
...
RuntimeError: wrapped C/C++ object of type QAction has been deleted

-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~alisonken1/openlp/pjlink2-l into lp:openlp.
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py	2017-10-10 07:08:44 +0000
+++ openlp/core/lib/__init__.py	2017-11-10 12:12:52 +0000
@@ -620,6 +620,3 @@
 from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css, build_chords_css
 from .imagemanager import ImageManager
 from .mediamanageritem import MediaManagerItem
-from .projector.db import ProjectorDB, Projector
-from .projector.pjlink import PJLink
-from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING

=== removed directory 'openlp/core/lib/projector'
=== added directory 'openlp/core/projectors'
=== renamed file 'openlp/core/lib/projector/__init__.py' => 'openlp/core/projectors/__init__.py'
--- openlp/core/lib/projector/__init__.py	2016-12-31 11:01:36 +0000
+++ openlp/core/projectors/__init__.py	2017-11-10 12:12:52 +0000
@@ -25,10 +25,22 @@
     Initialization for the openlp.core.ui.projector modules.
 """
 
-
+__all__ = ['PJLINK_PORT', 'ERROR_MSG', 'ERROR_STRING', 'DialogSourceStyle', 'PJLink', 'Projector',
+           'ProjectorDB', 'ProjectorEditForm', 'ProjectorManager', 'ProjectorTab']
+
+
+# Due to circular dependencies, put the imports after defines
 class DialogSourceStyle(object):
     """
     An enumeration for projector dialog box type.
     """
     Tabbed = 0
     Single = 1
+
+
+from .constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
+from .db import Projector, ProjectorDB
+from .editform import ProjectorEditForm
+from .manager import ProjectorManager
+from .pjlink import PJLink
+from .tab import ProjectorTab

=== renamed file 'openlp/core/lib/projector/constants.py' => 'openlp/core/projectors/constants.py'
=== renamed file 'openlp/core/lib/projector/db.py' => 'openlp/core/projectors/db.py'
--- openlp/core/lib/projector/db.py	2017-10-23 22:09:57 +0000
+++ openlp/core/projectors/db.py	2017-11-10 12:12:52 +0000
@@ -43,8 +43,8 @@
 from sqlalchemy.orm import relationship
 
 from openlp.core.lib.db import Manager, init_db, init_url
-from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
-from openlp.core.lib.projector import upgrade
+from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES
+from openlp.core.projectors import upgrade
 
 Base = declarative_base(MetaData())
 

=== renamed file 'openlp/core/ui/projector/editform.py' => 'openlp/core/projectors/editform.py'
--- openlp/core/ui/projector/editform.py	2017-10-07 07:05:07 +0000
+++ openlp/core/projectors/editform.py	2017-11-10 12:12:52 +0000
@@ -30,8 +30,8 @@
 from openlp.core.common import verify_ip_address
 from openlp.core.common.i18n import translate
 from openlp.core.lib import build_icon
-from openlp.core.lib.projector.db import Projector
-from openlp.core.lib.projector.constants import PJLINK_PORT
+from openlp.core.projectors.db import Projector
+from openlp.core.projectors.constants import PJLINK_PORT
 
 log = logging.getLogger(__name__)
 log.debug('editform loaded')

=== renamed file 'openlp/core/ui/projector/manager.py' => 'openlp/core/projectors/manager.py'
--- openlp/core/ui/projector/manager.py	2017-10-23 22:09:57 +0000
+++ openlp/core/projectors/manager.py	2017-11-10 12:12:52 +0000
@@ -34,14 +34,14 @@
 from openlp.core.common.registry import RegistryBase
 from openlp.core.common.settings import Settings
 from openlp.core.lib.ui import create_widget_action
-from openlp.core.lib.projector import DialogSourceStyle
-from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \
+from openlp.core.projectors import DialogSourceStyle
+from openlp.core.projectors.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \
     E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
     S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
-from openlp.core.lib.projector.db import ProjectorDB
-from openlp.core.lib.projector.pjlink import PJLink, PJLinkUDP
-from openlp.core.ui.projector.editform import ProjectorEditForm
-from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
+from openlp.core.projectors.db import ProjectorDB
+from openlp.core.projectors.pjlink import PJLink, PJLinkUDP
+from openlp.core.projectors.editform import ProjectorEditForm
+from openlp.core.projectors.sourceselectform import SourceSelectTabs, SourceSelectSingle
 from openlp.core.widgets.toolbar import OpenLPToolbar
 
 log = logging.getLogger(__name__)
@@ -518,7 +518,7 @@
         projector.thread.quit()
         new_list = []
         for item in self.projector_list:
-            if item.link.dbid == projector.link.dbid:
+            if item.link.db_item.id == projector.link.db_item.id:
                 continue
             new_list.append(item)
         self.projector_list = new_list
@@ -730,7 +730,6 @@
         thread.started.connect(item.link.thread_started)
         thread.finished.connect(item.link.thread_stopped)
         thread.finished.connect(thread.deleteLater)
-        item.link.projectorNetwork.connect(self.update_status)
         item.link.changeStatus.connect(self.update_status)
         item.link.projectorAuthentication.connect(self.authentication_error)
         item.link.projectorNoAuthentication.connect(self.no_authentication_error)

=== renamed file 'openlp/core/lib/projector/pjlink.py' => 'openlp/core/projectors/pjlink.py'
--- openlp/core/lib/projector/pjlink.py	2017-10-23 22:09:57 +0000
+++ openlp/core/projectors/pjlink.py	2017-11-10 12:12:52 +0000
@@ -54,7 +54,7 @@
 
 from openlp.core.common import qmd5_hash
 from openlp.core.common.i18n import translate
-from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \
+from openlp.core.projectors.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \
     E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \
     E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \
     PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
@@ -520,7 +520,6 @@
     """
     # Signals sent by this module
     changeStatus = QtCore.pyqtSignal(str, int, str)
-    projectorNetwork = QtCore.pyqtSignal(int)  # Projector network activity
     projectorStatus = QtCore.pyqtSignal(int)  # Status update
     projectorAuthentication = QtCore.pyqtSignal(str)  # Authentication error
     projectorNoAuthentication = QtCore.pyqtSignal(str)  # PIN set and no authentication needed
@@ -846,7 +845,6 @@
             log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip))
             return self.receive_data_signal()
         self.socket_timer.stop()
-        self.projectorNetwork.emit(S_NETWORK_RECEIVED)
         return self.get_data(buff=read, ip=self.ip)
 
     def get_data(self, buff, ip):
@@ -925,7 +923,6 @@
         if cmd not in PJLINK_VALID_CMD:
             log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip))
             return
-        self.projectorNetwork.emit(S_NETWORK_SENDING)
         log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip,
                                                                                                command=cmd,
                                                                                                data=opts,
@@ -996,7 +993,6 @@
         log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip()))
         log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue))
         self.socket_timer.start()
-        self.projectorNetwork.emit(S_NETWORK_SENDING)
         sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii')))
         self.waitForBytesWritten(2000)  # 2 seconds should be enough
         if sent == -1:

=== renamed file 'openlp/core/ui/projector/sourceselectform.py' => 'openlp/core/projectors/sourceselectform.py'
--- openlp/core/ui/projector/sourceselectform.py	2017-10-07 07:05:07 +0000
+++ openlp/core/projectors/sourceselectform.py	2017-11-10 12:12:52 +0000
@@ -31,8 +31,8 @@
 from openlp.core.common import is_macosx
 from openlp.core.common.i18n import translate
 from openlp.core.lib import build_icon
-from openlp.core.lib.projector.db import ProjectorSource
-from openlp.core.lib.projector.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES
+from openlp.core.projectors.db import ProjectorSource
+from openlp.core.projectors.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES
 
 log = logging.getLogger(__name__)
 

=== renamed file 'openlp/core/ui/projector/tab.py' => 'openlp/core/projectors/tab.py'
--- openlp/core/ui/projector/tab.py	2017-10-07 07:05:07 +0000
+++ openlp/core/projectors/tab.py	2017-11-10 12:12:52 +0000
@@ -29,7 +29,7 @@
 from openlp.core.common.i18n import UiStrings, translate
 from openlp.core.common.settings import Settings
 from openlp.core.lib import SettingsTab
-from openlp.core.lib.projector import DialogSourceStyle
+from openlp.core.projectors import DialogSourceStyle
 
 log = logging.getLogger(__name__)
 log.debug('projectortab module loaded')

=== renamed file 'openlp/core/lib/projector/upgrade.py' => 'openlp/core/projectors/upgrade.py'
=== modified file 'openlp/core/ui/__init__.py'
--- openlp/core/ui/__init__.py	2017-06-05 18:22:14 +0000
+++ openlp/core/ui/__init__.py	2017-11-10 12:12:52 +0000
@@ -115,9 +115,10 @@
 from .shortcutlistform import ShortcutListForm
 from .servicemanager import ServiceManager
 from .thememanager import ThemeManager
-from .projector.manager import ProjectorManager
-from .projector.tab import ProjectorTab
-from .projector.editform import ProjectorEditForm
+
+from openlp.core.projectors import ProjectorManager
+from openlp.core.projectors import ProjectorTab
+from openlp.core.projectors import ProjectorEditForm
 
 __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
            'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'Display', 'AudioPlayer',

=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py	2017-10-23 22:09:57 +0000
+++ openlp/core/ui/mainwindow.py	2017-11-10 12:12:52 +0000
@@ -55,7 +55,7 @@
 from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager
 from openlp.core.ui.media import MediaController
 from openlp.core.ui.printserviceform import PrintServiceForm
-from openlp.core.ui.projector.manager import ProjectorManager
+from openlp.core.projectors import ProjectorManager
 from openlp.core.ui.style import PROGRESSBAR_STYLE, get_library_stylesheet
 from openlp.core.version import get_version
 

=== removed directory 'openlp/core/ui/projector'
=== removed file 'openlp/core/ui/projector/__init__.py'
--- openlp/core/ui/projector/__init__.py	2016-12-31 11:01:36 +0000
+++ openlp/core/ui/projector/__init__.py	1970-01-01 00:00:00 +0000
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2017 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# This program is free software; you can redistribute it and/or modify it     #
-# under the terms of the GNU General Public License as published by the Free  #
-# Software Foundation; version 2 of the License.                              #
-#                                                                             #
-# This program is distributed in the hope that it will be useful, but WITHOUT #
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
-# more details.                                                               #
-#                                                                             #
-# You should have received a copy of the GNU General Public License along     #
-# with this program; if not, write to the Free Software Foundation, Inc., 59  #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
-###############################################################################
-"""
-The Projector driver module.
-"""

=== modified file 'openlp/core/ui/settingsform.py'
--- openlp/core/ui/settingsform.py	2017-10-23 22:09:57 +0000
+++ openlp/core/ui/settingsform.py	2017-11-10 12:12:52 +0000
@@ -32,7 +32,7 @@
 from openlp.core.lib import build_icon
 from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
 from openlp.core.ui.media import PlayerTab
-from openlp.core.ui.projector.tab import ProjectorTab
+from openlp.core.projectors import ProjectorTab
 from openlp.core.ui.settingsdialog import Ui_SettingsDialog
 
 log = logging.getLogger(__name__)

=== removed file 'tests/functional/openlp_core/lib/test_projector_constants.py'
--- tests/functional/openlp_core/lib/test_projector_constants.py	2017-08-06 07:23:26 +0000
+++ tests/functional/openlp_core/lib/test_projector_constants.py	1970-01-01 00:00:00 +0000
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2015 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# This program is free software; you can redistribute it and/or modify it     #
-# under the terms of the GNU General Public License as published by the Free  #
-# Software Foundation; version 2 of the License.                              #
-#                                                                             #
-# This program is distributed in the hope that it will be useful, but WITHOUT #
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
-# more details.                                                               #
-#                                                                             #
-# You should have received a copy of the GNU General Public License along     #
-# with this program; if not, write to the Free Software Foundation, Inc., 59  #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
-###############################################################################
-"""
-Package to test the openlp.core.lib.projector.constants package.
-"""
-from unittest import TestCase
-
-
-class TestProjectorConstants(TestCase):
-    """
-    Test specific functions in the projector constants module.
-    """
-    def test_build_pjlink_video_label(self):
-        """
-        Test building PJLINK_DEFAULT_CODES dictionary
-        """
-        # GIVEN: Test data
-        from tests.resources.projector.data import TEST_VIDEO_CODES
-
-        # WHEN: Import projector PJLINK_DEFAULT_CODES
-        from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
-
-        # THEN: Verify dictionary was build correctly
-        self.assertEqual(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match')

=== removed file 'tests/functional/openlp_core/lib/test_projector_db.py'
--- tests/functional/openlp_core/lib/test_projector_db.py	2017-09-22 12:03:28 +0000
+++ tests/functional/openlp_core/lib/test_projector_db.py	1970-01-01 00:00:00 +0000
@@ -1,462 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2017 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# This program is free software; you can redistribute it and/or modify it     #
-# under the terms of the GNU General Public License as published by the Free  #
-# Software Foundation; version 2 of the License.                              #
-#                                                                             #
-# This program is distributed in the hope that it will be useful, but WITHOUT #
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
-# more details.                                                               #
-#                                                                             #
-# You should have received a copy of the GNU General Public License along     #
-# with this program; if not, write to the Free Software Foundation, Inc., 59  #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
-###############################################################################
-"""
-Package to test the openlp.core.ui.projectordb  find, edit, delete
-record functions.
-
-PREREQUISITE: add_record() and get_all() functions validated.
-"""
-import os
-import shutil
-from tempfile import mkdtemp
-
-from unittest import TestCase
-from unittest.mock import patch
-
-from openlp.core.lib.projector import upgrade
-from openlp.core.lib.db import upgrade_db
-from openlp.core.lib.projector.constants import PJLINK_PORT
-from openlp.core.lib.projector.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source
-
-from tests.resources.projector.data import TEST_DB_PJLINK1, TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA
-from tests.utils.constants import TEST_RESOURCES_PATH
-
-
-def compare_data(one, two):
-    """
-    Verify two Projector() instances contain the same data
-    """
-    return one is not None and \
-        two is not None and \
-        one.ip == two.ip and \
-        one.port == two.port and \
-        one.name == two.name and \
-        one.location == two.location and \
-        one.notes == two.notes and \
-        one.sw_version == two.sw_version and \
-        one.serial_no == two.serial_no and \
-        one.model_filter == two.model_filter and \
-        one.model_lamp == two.model_lamp
-
-
-def compare_source(one, two):
-    """
-    Verify two ProjectorSource instances contain the same data
-    """
-    return one is not None and \
-        two is not None and \
-        one.projector_id == two.projector_id and \
-        one.code == two.code and \
-        one.text == two.text
-
-
-def add_records(projector_db, test):
-    """
-    Add record if not in database
-    """
-    record_list = projector_db.get_projector_all()
-    if len(record_list) < 1:
-        added = False
-        for record in test:
-            added = projector_db.add_projector(record) or added
-        return added
-
-    for new_record in test:
-        added = None
-        for record in record_list:
-            if compare_data(record, new_record):
-                break
-            added = projector_db.add_projector(new_record)
-    return added
-
-
-class TestProjectorDBUpdate(TestCase):
-    """
-    Test case for upgrading Projector DB.
-    NOTE: Separate class so I don't have to look for upgrade tests.
-    """
-    def setUp(self):
-        """
-        Setup for tests
-        """
-        self.tmp_folder = mkdtemp(prefix='openlp_')
-
-    def tearDown(self):
-        """
-        Clean up after tests
-        """
-        # Ignore errors since windows can have problems with locked files
-        shutil.rmtree(self.tmp_folder, ignore_errors=True)
-
-    def test_upgrade_old_projector_db(self):
-        """
-        Test that we can upgrade an old song db to the current schema
-        """
-        # GIVEN: An old prjector db
-        old_db = os.path.join(TEST_RESOURCES_PATH, "projector", TEST_DB_PJLINK1)
-        tmp_db = os.path.join(self.tmp_folder, TEST_DB)
-        shutil.copyfile(old_db, tmp_db)
-        db_url = 'sqlite:///{db}'.format(db=tmp_db)
-
-        # WHEN: upgrading the db
-        updated_to_version, latest_version = upgrade_db(db_url, upgrade)
-
-        # THEN: the song db should have been upgraded to the latest version
-        self.assertEqual(updated_to_version, latest_version,
-                         'The projector DB should have been upgrade to the latest version')
-
-
-class TestProjectorDB(TestCase):
-    """
-    Test case for ProjectorDB
-    """
-    @patch('openlp.core.lib.projector.db.init_url')
-    def setUp(self, mocked_init_url):
-        """
-        Set up anything necessary for all tests
-        """
-        self.tmp_folder = mkdtemp(prefix='openlp_')
-        tmpdb_url = 'sqlite:///{db}'.format(db=os.path.join(self.tmp_folder, TEST_DB))
-        mocked_init_url.return_value = tmpdb_url
-        self.projector = ProjectorDB()
-
-    def tearDown(self):
-        """
-        Clean up
-        """
-        self.projector.session.close()
-        self.projector = None
-        # Ignore errors since windows can have problems with locked files
-        shutil.rmtree(self.tmp_folder, ignore_errors=True)
-
-    def test_find_record_by_ip(self):
-        """
-        Test find record by IP
-        """
-        # GIVEN: Record entries in database
-        add_records(self.projector, [Projector(**TEST1_DATA), Projector(**TEST2_DATA)])
-
-        # WHEN: Search for record using IP
-        record = self.projector.get_projector_by_ip(TEST2_DATA['ip'])
-
-        # THEN: Verify proper record returned
-        self.assertTrue(compare_data(Projector(**TEST2_DATA), record),
-                        'Record found should have been test_2 data')
-
-    def test_find_record_by_name(self):
-        """
-        Test find record by name
-        """
-        # GIVEN: Record entries in database
-        add_records(self.projector, [Projector(**TEST1_DATA), Projector(**TEST2_DATA)])
-
-        # WHEN: Search for record using name
-        record = self.projector.get_projector_by_name(TEST2_DATA['name'])
-
-        # THEN: Verify proper record returned
-        self.assertTrue(compare_data(Projector(**TEST2_DATA), record),
-                        'Record found should have been test_2 data')
-
-    def test_record_delete(self):
-        """
-        Test record can be deleted
-        """
-        # GIVEN: Record in database
-        add_records(self.projector, [Projector(**TEST3_DATA), ])
-        record = self.projector.get_projector_by_ip(TEST3_DATA['ip'])
-
-        # WHEN: Record deleted
-        self.projector.delete_projector(record)
-
-        # THEN: Verify record not retrievable
-        found = self.projector.get_projector_by_ip(TEST3_DATA['ip'])
-        self.assertFalse(found, 'test_3 record should have been deleted')
-
-    def test_record_edit(self):
-        """
-        Test edited record returns the same record ID with different data
-        """
-        # GIVEN: Record entries in database
-        add_records(self.projector, [Projector(**TEST1_DATA), Projector(**TEST2_DATA)])
-
-        # WHEN: We retrieve a specific record
-        record = self.projector.get_projector_by_ip(TEST1_DATA['ip'])
-        record_id = record.id
-
-        # WHEN: Data is changed
-        record.ip = TEST3_DATA['ip']
-        record.port = TEST3_DATA['port']
-        record.pin = TEST3_DATA['pin']
-        record.name = TEST3_DATA['name']
-        record.location = TEST3_DATA['location']
-        record.notes = TEST3_DATA['notes']
-        record.sw_version = TEST3_DATA['sw_version']
-        record.serial_no = TEST3_DATA['serial_no']
-        record.model_filter = TEST3_DATA['model_filter']
-        record.model_lamp = TEST3_DATA['model_lamp']
-        updated = self.projector.update_projector(record)
-        self.assertTrue(updated, 'Save updated record should have returned True')
-        record = self.projector.get_projector_by_ip(TEST3_DATA['ip'])
-
-        # THEN: Record ID should remain the same, but data should be changed
-        self.assertEqual(record_id, record.id, 'Edited record should have the same ID')
-        self.assertTrue(compare_data(Projector(**TEST3_DATA), record), 'Edited record should have new data')
-
-    def test_source_add(self):
-        """
-        Test source entry for projector item
-        """
-        # GIVEN: Record entries in database
-        projector1 = Projector(**TEST1_DATA)
-        self.projector.add_projector(projector1)
-        item = self.projector.get_projector_by_id(projector1.id)
-        item_id = item.id
-
-        # WHEN: A source entry is saved for item
-        source = ProjectorSource(projector_id=item_id, code='11', text='First RGB source')
-        self.projector.add_source(source)
-
-        # THEN: Projector should have the same source entry
-        item = self.projector.get_projector_by_id(item_id)
-        self.assertTrue(compare_source(item.source_list[0], source))
-
-    def test_manufacturer_repr(self):
-        """
-        Test Manufacturer.__repr__() text
-        """
-        # GIVEN: Test object
-        manufacturer = Manufacturer()
-
-        # WHEN: Name is set
-        manufacturer.name = 'OpenLP Test'
-
-        # THEN: __repr__ should return a proper string
-        self.assertEqual(str(manufacturer), '<Manufacturer(name="OpenLP Test")>',
-                         'Manufacturer.__repr__() should have returned a proper representation string')
-
-    def test_model_repr(self):
-        """
-        Test Model.__repr__() text
-        """
-        # GIVEN: Test object
-        model = Model()
-
-        # WHEN: Name is set
-        model.name = 'OpenLP Test'
-
-        # THEN: __repr__ should return a proper string
-        self.assertEqual(str(model), '<Model(name='"OpenLP Test"')>',
-                         'Model.__repr__() should have returned a proper representation string')
-
-    def test_source_repr(self):
-        """
-        Test Source.__repr__() text
-        """
-        # GIVEN: Test object
-        source = Source()
-
-        # WHEN: Source() information is set
-        source.pjlink_name = 'Test object'
-        source.pjlink_code = '11'
-        source.text = 'Input text'
-
-        # THEN: __repr__ should return a proper string
-        self.assertEqual(str(source), '<Source(pjlink_name="Test object", pjlink_code="11", text="Input text")>',
-                         'Source.__repr__() should have returned a proper representation string')
-
-    def test_projector_repr(self):
-        """
-        Test Projector.__repr__() text
-        """
-        # GIVEN: Test object
-        projector = Projector()
-
-        # WHEN: projector() is populated
-        # NOTE: projector.[pin, other, sources, sw_version, serial_no, sw_version, model_lamp, model_filter]
-        #           should all return None.
-        #       projector.source_list should return an empty list
-        projector.id = 0
-        projector.ip = '127.0.0.1'
-        projector.port = PJLINK_PORT
-        projector.name = 'Test One'
-        projector.location = 'Somewhere over the rainbow'
-        projector.notes = 'Not again'
-        projector.pjlink_name = 'TEST'
-        projector.manufacturer = 'IN YOUR DREAMS'
-        projector.model = 'OpenLP'
-
-        # THEN: __repr__ should return a proper string
-        self.assertEqual(str(projector),
-                         '< Projector(id="0", ip="127.0.0.1", port="4352", mac_adx="None", pin="None", '
-                         'name="Test One", location="Somewhere over the rainbow", notes="Not again", '
-                         'pjlink_name="TEST", manufacturer="IN YOUR DREAMS", model="OpenLP", serial_no="None", '
-                         'other="None", sources="None", source_list="[]", model_filter="None", model_lamp="None", '
-                         'sw_version="None") >',
-                         'Projector.__repr__() should have returned a proper representation string')
-
-    def test_projectorsource_repr(self):
-        """
-        Test ProjectorSource.__repr__() text
-        """
-        # GIVEN: test setup
-        projector1 = Projector(**TEST1_DATA)
-        self.projector.add_projector(projector1)
-        item = self.projector.get_projector_by_id(projector1.id)
-        item_id = item.id
-
-        # WHEN: A source entry is saved for item
-        source = ProjectorSource(projector_id=item_id, code='11', text='First RGB source')
-        self.projector.add_source(source)
-
-        # THEN: __repr__ should return a proper string
-        self.assertEqual(str(source),
-                         '<ProjectorSource(id="1", code="11", text="First RGB source", projector_id="1")>',
-                         'ProjectorSource.__repr__)_ should have returned a proper representation string')
-
-    def test_get_projector_by_id_none(self):
-        """
-        Test get_projector_by_id() returns None if no db entry
-        """
-        # GIVEN: Test object and data
-        projector = self.projector
-
-        # WHEN: DB search for entry not saved
-        results = projector.get_projector_by_id(dbid=123134556409824506)
-
-        # THEN: Verify return was None
-        self.assertEqual(results, None, 'Returned results should have equaled None')
-
-    def test_get_projector_all_none(self):
-        """
-        Test get_projector_all() with no projectors in db
-        """
-        # GIVEN: Test object with no data
-        projector = self.projector
-
-        # WHEN: We retrieve the database entries
-        results = projector.get_projector_all()
-
-        # THEN: Verify results is None
-        self.assertEqual(results, [], 'Returned results should have returned an empty list')
-
-    def test_get_projector_all_one(self):
-        """
-        Test get_projector_all() with one entry in db
-        """
-        # GIVEN: One entry in database
-        projector = Projector(**TEST1_DATA)
-        self.projector.add_projector(projector)
-
-        # WHEN: We retrieve the database entries
-        results = self.projector.get_projector_all()
-
-        # THEN: We should have a list with one entry
-        self.assertEqual(len(results), 1, 'Returned results should have returned a list with one entry')
-        self.assertTrue((projector in results), 'Result should have been equal to TEST1_DATA')
-
-    def test_get_projector_all_many(self):
-        """
-        Test get_projector_all() with multiple entries in db
-        """
-        # GIVEN: multiple entries in database
-        projector_list = []
-        projector_list.append(Projector(**TEST1_DATA))
-        projector_list.append(Projector(**TEST2_DATA))
-        projector_list.append(Projector(**TEST3_DATA))
-        for projector in projector_list:
-            self.projector.add_projector(projector)
-
-        # WHEN: We retrieve the database entries
-        results = self.projector.get_projector_all()
-
-        # THEN: We should have a list with three entries
-        self.assertEqual(len(results), len(projector_list),
-                         'Returned results should have returned a list with three entries')
-        for projector in results:
-            self.assertTrue((projector in projector_list),
-                            'Projector DB entry should have been in expected list')
-
-    def test_get_projector_by_name_fail(self):
-        """
-        Test get_projector_by_name() fail
-        """
-        # GIVEN: Test entry in database
-        self.projector.add_projector(Projector(**TEST1_DATA))
-
-        # WHEN: We attempt to get a projector that's not in database
-        results = self.projector.get_projector_by_name(name=TEST2_DATA['name'])
-
-        # THEN: We should have None
-        self.assertEqual(results, None, 'projector.get_projector_by_name() should have returned None')
-
-    def test_add_projector_fail(self):
-        """
-        Test add_projector() fail
-        """
-        # GIVEN: Test entry in the database
-        self.projector.add_projector(Projector(**TEST1_DATA))
-
-        # WHEN: Attempt to add same projector entry
-        results = self.projector.add_projector(Projector(**TEST1_DATA))
-
-        # THEN: We should have failed to add new entry
-        self.assertFalse(results, 'add_projector() should have failed')
-
-    def test_update_projector_default_fail(self):
-        """
-        Test update_projector() with no options fails
-        """
-        # GIVEN: projector instance
-        projector = self.projector
-
-        # WHEN: attempt to update a projector entry with no options
-        results = projector.update_projector()
-
-        # THEN: We should have failed
-        self.assertFalse(results, 'update_projector(projector=None) should have returned False')
-
-    def test_update_projector_not_in_db_fail(self):
-        """
-        Test update_projector() when entry not in database
-        """
-        # GIVEN: Projector entry in database
-        self.projector.add_projector(Projector(**TEST1_DATA))
-        projector = Projector(**TEST2_DATA)
-
-        # WHEN: Attempt to update data with a different ID
-        results = self.projector.update_projector(projector)
-
-        # THEN: Results should be False
-        self.assertFalse(results, 'update_projector(projector=projector) should have returned False')
-
-    def test_delete_projector_fail(self):
-        """
-        Test delete_projector(projector) fails to delete record
-        """
-        # GIVEN: Test entry in db
-        self.projector.add_projector(Projector(**TEST1_DATA))
-
-        # wHEN: Attempting to delete an entry not in the databae
-        results = self.projector.delete_projector(Projector(**TEST2_DATA))
-
-        # THEN: Results should be False
-        self.assertFalse(results, 'delete_projector() should have returned False')

=== removed file 'tests/functional/openlp_core/lib/test_projector_pjlink_base.py'
--- tests/functional/openlp_core/lib/test_projector_pjlink_base.py	2017-09-22 12:03:28 +0000
+++ tests/functional/openlp_core/lib/test_projector_pjlink_base.py	1970-01-01 00:00:00 +0000
@@ -1,209 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2015 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# This program is free software; you can redistribute it and/or modify it     #
-# under the terms of the GNU General Public License as published by the Free  #
-# Software Foundation; version 2 of the License.                              #
-#                                                                             #
-# This program is distributed in the hope that it will be useful, but WITHOUT #
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
-# more details.                                                               #
-#                                                                             #
-# You should have received a copy of the GNU General Public License along     #
-# with this program; if not, write to the Free Software Foundation, Inc., 59  #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
-###############################################################################
-"""
-Package to test the openlp.core.lib.projector.pjlink base package.
-"""
-from unittest import TestCase
-from unittest.mock import call, patch, MagicMock
-
-from openlp.core.lib.projector.db import Projector
-from openlp.core.lib.projector.pjlink import PJLink
-from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED
-
-from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA
-
-pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
-
-
-class TestPJLinkBase(TestCase):
-    """
-    Tests for the PJLink module
-    """
-    @patch.object(pjlink_test, 'readyRead')
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(pjlink_test, 'waitForReadyRead')
-    @patch('openlp.core.common.qmd5_hash')
-    def test_authenticated_connection_call(self,
-                                           mock_qmd5_hash,
-                                           mock_waitForReadyRead,
-                                           mock_send_command,
-                                           mock_readyRead):
-        """
-        Ticket 92187: Fix for projector connect with PJLink authentication exception.
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-
-        # WHEN: Calling check_login with authentication request:
-        pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
-
-        # THEN: Should have called qmd5_hash
-        self.assertTrue(mock_qmd5_hash.called_with(TEST_SALT,
-                                                   "Connection request should have been called with TEST_SALT"))
-        self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
-                                                   "Connection request should have been called with TEST_PIN"))
-
-    @patch.object(pjlink_test, 'change_status')
-    def test_status_change(self, mock_change_status):
-        """
-        Test process_command call with ERR2 (Parameter) status
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-
-        # WHEN: process_command is called with "ERR2" status from projector
-        pjlink.process_command('POWR', 'ERR2')
-
-        # THEN: change_status should have called change_status with E_UNDEFINED
-        #       as first parameter
-        mock_change_status.called_with(E_PARAMETER,
-                                       'change_status should have been called with "{}"'.format(
-                                           ERROR_STRING[E_PARAMETER]))
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(pjlink_test, 'waitForReadyRead')
-    @patch.object(pjlink_test, 'projectorAuthentication')
-    @patch.object(pjlink_test, 'timer')
-    @patch.object(pjlink_test, 'socket_timer')
-    def test_bug_1593882_no_pin_authenticated_connection(self,
-                                                         mock_socket_timer,
-                                                         mock_timer,
-                                                         mock_authentication,
-                                                         mock_ready_read,
-                                                         mock_send_command):
-        """
-        Test bug 1593882 no pin and authenticated request exception
-        """
-        # GIVEN: Test object and mocks
-        pjlink = pjlink_test
-        pjlink.pin = None
-        mock_ready_read.return_value = True
-
-        # WHEN: call with authentication request and pin not set
-        pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
-
-        # THEN: 'No Authentication' signal should have been sent
-        mock_authentication.emit.assert_called_with(pjlink.ip)
-
-    @patch.object(pjlink_test, 'waitForReadyRead')
-    @patch.object(pjlink_test, 'state')
-    @patch.object(pjlink_test, '_send_command')
-    @patch.object(pjlink_test, 'timer')
-    @patch.object(pjlink_test, 'socket_timer')
-    def test_bug_1593883_pjlink_authentication(self,
-                                               mock_socket_timer,
-                                               mock_timer,
-                                               mock_send_command,
-                                               mock_state,
-                                               mock_waitForReadyRead):
-        """
-        Test bugfix 1593883 pjlink authentication
-        """
-        # GIVEN: Test object and data
-        pjlink = pjlink_test
-        pjlink.pin = TEST_PIN
-        mock_state.return_value = pjlink.ConnectedState
-        mock_waitForReadyRead.return_value = True
-
-        # WHEN: Athenticated connection is called
-        pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
-
-        # THEN: send_command should have the proper authentication
-        self.assertEqual("{test}".format(test=mock_send_command.call_args),
-                         "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
-
-    @patch.object(pjlink_test, 'disconnect_from_host')
-    def test_socket_abort(self, mock_disconnect):
-        """
-        Test PJLink.socket_abort calls disconnect_from_host
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-
-        # WHEN: Calling socket_abort
-        pjlink.socket_abort()
-
-        # THEN: disconnect_from_host should be called
-        self.assertTrue(mock_disconnect.called, 'Should have called disconnect_from_host')
-
-    def test_poll_loop_not_connected(self):
-        """
-        Test PJLink.poll_loop not connected return
-        """
-        # GIVEN: Test object and mocks
-        pjlink = pjlink_test
-        pjlink.state = MagicMock()
-        pjlink.timer = MagicMock()
-        pjlink.state.return_value = False
-        pjlink.ConnectedState = True
-
-        # WHEN: PJLink.poll_loop called
-        pjlink.poll_loop()
-
-        # THEN: poll_loop should exit without calling any other method
-        self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method')
-
-    @patch.object(pjlink_test, 'send_command')
-    def test_poll_loop_start(self, mock_send_command):
-        """
-        Test PJLink.poll_loop makes correct calls
-        """
-        # GIVEN: test object and test data
-        pjlink = pjlink_test
-        pjlink.state = MagicMock()
-        pjlink.timer = MagicMock()
-        pjlink.timer.interval = MagicMock()
-        pjlink.timer.setInterval = MagicMock()
-        pjlink.timer.start = MagicMock()
-        pjlink.poll_time = 20
-        pjlink.power = S_ON
-        pjlink.source_available = None
-        pjlink.other_info = None
-        pjlink.manufacturer = None
-        pjlink.model = None
-        pjlink.pjlink_name = None
-        pjlink.ConnectedState = S_CONNECTED
-        pjlink.timer.interval.return_value = 10
-        pjlink.state.return_value = S_CONNECTED
-        call_list = [
-            call('POWR', queue=True),
-            call('ERST', queue=True),
-            call('LAMP', queue=True),
-            call('AVMT', queue=True),
-            call('INPT', queue=True),
-            call('INST', queue=True),
-            call('INFO', queue=True),
-            call('INF1', queue=True),
-            call('INF2', queue=True),
-            call('NAME', queue=True),
-        ]
-
-        # WHEN: PJLink.poll_loop is called
-        pjlink.poll_loop()
-
-        # THEN: proper calls were made to retrieve projector data
-        # First, call to update the timer with the next interval
-        self.assertTrue(pjlink.timer.setInterval.called, 'Should have updated the timer')
-        # Next, should have called the timer to start
-        self.assertTrue(pjlink.timer.start.called, 'Should have started the timer')
-        # Finally, should have called send_command with a list of projetctor status checks
-        mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries')

=== removed file 'tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py'
--- tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py	2017-09-22 12:03:28 +0000
+++ tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py	1970-01-01 00:00:00 +0000
@@ -1,224 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2015 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# This program is free software; you can redistribute it and/or modify it     #
-# under the terms of the GNU General Public License as published by the Free  #
-# Software Foundation; version 2 of the License.                              #
-#                                                                             #
-# This program is distributed in the hope that it will be useful, but WITHOUT #
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
-# more details.                                                               #
-#                                                                             #
-# You should have received a copy of the GNU General Public License along     #
-# with this program; if not, write to the Free Software Foundation, Inc., 59  #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
-###############################################################################
-"""
-Package to test the openlp.core.lib.projector.pjlink class command routing.
-"""
-
-from unittest import TestCase
-from unittest.mock import patch, MagicMock
-
-import openlp.core.lib.projector.pjlink
-from openlp.core.lib.projector.db import Projector
-from openlp.core.lib.projector.pjlink import PJLink
-from openlp.core.lib.projector.constants import PJLINK_ERRORS, \
-    E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED
-
-'''
-from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
-    PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
-'''
-from tests.resources.projector.data import TEST_PIN, TEST1_DATA
-
-pjlink_test = PJLink(Projector(**TEST1_DATA), pin=TEST_PIN, no_poll=True)
-pjlink_test.ip = '127.0.0.1'
-
-
-class TestPJLinkRouting(TestCase):
-    """
-    Tests for the PJLink module command routing
-    """
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_process_command_call_clss(self, mock_log):
-        """
-        Test process_command calls proper function
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        log_text = '(127.0.0.1) Calling function for CLSS'
-        mock_log.reset_mock()
-        mock_process_clss = MagicMock()
-        pjlink.pjlink_functions['CLSS'] = mock_process_clss
-
-        # WHEN: process_command is called with valid function and data
-        pjlink.process_command(cmd='CLSS', data='1')
-
-        # THEN: Process method should have been called properly
-        mock_log.debug.assert_called_with(log_text)
-        mock_process_clss.assert_called_with('1')
-
-    @patch.object(pjlink_test, 'change_status')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_process_command_err1(self, mock_log, mock_change_status):
-        """
-        Test ERR1 - Undefined projector function
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        log_text = '(127.0.0.1) Projector returned error "ERR1"'
-        mock_change_status.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: process_command called with ERR1
-        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNDEFINED])
-
-        # THEN: Error should be logged and status_change should be called
-        mock_change_status.assert_called_once_with(E_UNDEFINED, 'Undefined Command: "CLSS"')
-        mock_log.error.assert_called_with(log_text)
-
-    @patch.object(pjlink_test, 'change_status')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_process_command_err2(self, mock_log, mock_change_status):
-        """
-        Test ERR2 - Parameter Error
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        log_text = '(127.0.0.1) Projector returned error "ERR2"'
-        mock_change_status.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: process_command called with ERR2
-        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PARAMETER])
-
-        # THEN: Error should be logged and status_change should be called
-        mock_change_status.assert_called_once_with(E_PARAMETER)
-        mock_log.error.assert_called_with(log_text)
-
-    @patch.object(pjlink_test, 'change_status')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_process_command_err3(self, mock_log, mock_change_status):
-        """
-        Test ERR3 - Unavailable error
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        log_text = '(127.0.0.1) Projector returned error "ERR3"'
-        mock_change_status.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: process_command called with ERR3
-        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNAVAILABLE])
-
-        # THEN: Error should be logged and status_change should be called
-        mock_change_status.assert_called_once_with(E_UNAVAILABLE)
-        mock_log.error.assert_called_with(log_text)
-
-    @patch.object(pjlink_test, 'change_status')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_process_command_err4(self, mock_log, mock_change_status):
-        """
-        Test ERR3 - Unavailable error
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        log_text = '(127.0.0.1) Projector returned error "ERR4"'
-        mock_change_status.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: process_command called with ERR3
-        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PROJECTOR])
-
-        # THEN: Error should be logged and status_change should be called
-        mock_change_status.assert_called_once_with(E_PROJECTOR)
-        mock_log.error.assert_called_with(log_text)
-
-    @patch.object(pjlink_test, 'projectorAuthentication')
-    @patch.object(pjlink_test, 'change_status')
-    @patch.object(pjlink_test, 'disconnect_from_host')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_process_command_erra(self, mock_log, mock_disconnect, mock_change_status, mock_err_authenticate):
-        """
-        Test ERRA - Authentication Error
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        log_text = '(127.0.0.1) Projector returned error "ERRA"'
-        mock_change_status.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: process_command called with ERRA
-        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_AUTHENTICATION])
-
-        # THEN: Error should be logged and several methods should be called
-        self.assertTrue(mock_disconnect.called, 'disconnect_from_host should have been called')
-        mock_change_status.assert_called_once_with(E_AUTHENTICATION)
-        mock_log.error.assert_called_with(log_text)
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_process_command_future(self, mock_log):
-        """
-        Test command valid but no method to process yet
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        log_text = "(127.0.0.1) Unable to process command='CLSS' (Future option)"
-        mock_log.reset_mock()
-        # Remove a valid command so we can test valid command but not available yet
-        pjlink.pjlink_functions.pop('CLSS')
-
-        # WHEN: process_command called with an unknown command
-        with patch.object(pjlink, 'pjlink_functions') as mock_functions:
-            pjlink.process_command(cmd='CLSS', data='DONT CARE')
-
-        # THEN: Error should be logged and no command called
-        self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
-        mock_log.warning.assert_called_once_with(log_text)
-
-    @patch.object(pjlink_test, 'pjlink_functions')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_process_command_invalid(self, mock_log, mock_functions):
-        """
-        Test not a valid command
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_functions.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: process_command called with an unknown command
-        pjlink.process_command(cmd='Unknown', data='Dont Care')
-        log_text = "(127.0.0.1) Ignoring command='Unknown' (Invalid/Unknown)"
-
-        # THEN: Error should be logged and no command called
-        self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
-        mock_log.error.assert_called_once_with(log_text)
-
-    @patch.object(pjlink_test, 'pjlink_functions')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_process_command_ok(self, mock_log, mock_functions):
-        """
-        Test command returned success
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_functions.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: process_command called with an unknown command
-        pjlink.process_command(cmd='CLSS', data='OK')
-        log_text = '(127.0.0.1) Command "CLSS" returned OK'
-
-        # THEN: Error should be logged and no command called
-        self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
-        self.assertEqual(mock_log.debug.call_count, 2, 'log.debug() should have been called twice')
-        # Although we called it twice, only the last log entry is saved
-        mock_log.debug.assert_called_with(log_text)

=== removed file 'tests/functional/openlp_core/lib/test_projector_pjlink_commands.py'
--- tests/functional/openlp_core/lib/test_projector_pjlink_commands.py	2017-09-22 12:03:28 +0000
+++ tests/functional/openlp_core/lib/test_projector_pjlink_commands.py	1970-01-01 00:00:00 +0000
@@ -1,1149 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2015 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# This program is free software; you can redistribute it and/or modify it     #
-# under the terms of the GNU General Public License as published by the Free  #
-# Software Foundation; version 2 of the License.                              #
-#                                                                             #
-# This program is distributed in the hope that it will be useful, but WITHOUT #
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
-# more details.                                                               #
-#                                                                             #
-# You should have received a copy of the GNU General Public License along     #
-# with this program; if not, write to the Free Software Foundation, Inc., 59  #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
-###############################################################################
-"""
-Package to test the openlp.core.lib.projector.pjlink commands package.
-"""
-from unittest import TestCase
-from unittest.mock import patch
-
-import openlp.core.lib.projector.pjlink
-from openlp.core.lib.projector.db import Projector
-from openlp.core.lib.projector.pjlink import PJLink
-from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
-    PJLINK_POWR_STATUS, \
-    E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \
-    S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY
-
-from tests.resources.projector.data import TEST_PIN, TEST1_DATA
-
-pjlink_test = PJLink(Projector(**TEST1_DATA), pin=TEST_PIN, no_poll=True)
-pjlink_test.ip = '127.0.0.1'
-
-# Create a list of ERST positional data so we don't have to redo the same buildup multiple times
-PJLINK_ERST_POSITIONS = []
-for pos in range(0, len(PJLINK_ERST_DATA)):
-    if pos in PJLINK_ERST_DATA:
-        PJLINK_ERST_POSITIONS.append(PJLINK_ERST_DATA[pos])
-
-
-class TestPJLinkCommands(TestCase):
-    """
-    Tests for the PJLink module
-    """
-    @patch.object(pjlink_test, 'changeStatus')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_change_status_connection_error(self, mock_log, mock_change_status):
-        """
-        Test change_status with connection error
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.projector_status = 0
-        pjlink.status_connect = 0
-        test_code = E_UNKNOWN_SOCKET_ERROR
-        mock_change_status.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: change_status called with unknown socket error
-        pjlink.change_status(status=test_code, msg=None)
-
-        # THEN: Proper settings should change and signals sent
-        self.assertEqual(pjlink.projector_status, E_NOT_CONNECTED, 'Projector status should be NOT CONNECTED')
-        self.assertEqual(pjlink.status_connect, E_NOT_CONNECTED, 'Status connect should be NOT CONNECTED')
-        mock_change_status.emit.assert_called_once_with(pjlink.ip, E_UNKNOWN_SOCKET_ERROR,
-                                                        'An unidentified error occurred')
-        self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
-
-    @patch.object(pjlink_test, 'changeStatus')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_change_status_connection_status_connecting(self, mock_log, mock_change_status):
-        """
-        Test change_status with connection status
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.projector_status = 0
-        pjlink.status_connect = 0
-        test_code = S_CONNECTING
-        mock_change_status.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: change_status called with unknown socket error
-        pjlink.change_status(status=test_code, msg=None)
-
-        # THEN: Proper settings should change and signals sent
-        self.assertEqual(pjlink.projector_status, S_NOT_CONNECTED, 'Projector status should be NOT CONNECTED')
-        self.assertEqual(pjlink.status_connect, S_CONNECTING, 'Status connect should be CONNECTING')
-        mock_change_status.emit.assert_called_once_with(pjlink.ip, S_CONNECTING, 'Connecting')
-        self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
-
-    @patch.object(pjlink_test, 'changeStatus')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_change_status_connection_status_connected(self, mock_log, mock_change_status):
-        """
-        Test change_status with connection status
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.projector_status = 0
-        pjlink.status_connect = 0
-        test_code = S_ON
-        mock_change_status.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: change_status called with unknown socket error
-        pjlink.change_status(status=test_code, msg=None)
-
-        # THEN: Proper settings should change and signals sent
-        self.assertEqual(pjlink.projector_status, S_ON, 'Projector status should be ON')
-        self.assertEqual(pjlink.status_connect, S_CONNECTED, 'Status connect should be CONNECTED')
-        mock_change_status.emit.assert_called_once_with(pjlink.ip, S_ON, 'Power is on')
-        self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
-
-    @patch.object(pjlink_test, 'changeStatus')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_change_status_connection_status_with_message(self, mock_log, mock_change_status):
-        """
-        Test change_status with connection status
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.projector_status = 0
-        pjlink.status_connect = 0
-        test_message = 'Different Status Message than default'
-        test_code = S_ON
-        mock_change_status.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: change_status called with unknown socket error
-        pjlink.change_status(status=test_code, msg=test_message)
-
-        # THEN: Proper settings should change and signals sent
-        self.assertEqual(pjlink.projector_status, S_ON, 'Projector status should be ON')
-        self.assertEqual(pjlink.status_connect, S_CONNECTED, 'Status connect should be CONNECTED')
-        mock_change_status.emit.assert_called_once_with(pjlink.ip, S_ON, test_message)
-        self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_av_mute_status(self, mock_log, mock_send_command):
-        """
-        Test sending command to retrieve shutter/audio state
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'AVMT'
-        test_log = '(127.0.0.1) Sending AVMT command'
-
-        # WHEN: get_av_mute_status is called
-        pjlink.get_av_mute_status()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_available_inputs(self, mock_log, mock_send_command):
-        """
-        Test sending command to retrieve avaliable inputs
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'INST'
-        test_log = '(127.0.0.1) Sending INST command'
-
-        # WHEN: get_available_inputs is called
-        pjlink.get_available_inputs()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_error_status(self, mock_log, mock_send_command):
-        """
-        Test sending command to retrieve projector error status
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'ERST'
-        test_log = '(127.0.0.1) Sending ERST command'
-
-        # WHEN: get_error_status is called
-        pjlink.get_error_status()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_input_source(self, mock_log, mock_send_command):
-        """
-        Test sending command to retrieve current input
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'INPT'
-        test_log = '(127.0.0.1) Sending INPT command'
-
-        # WHEN: get_input_source is called
-        pjlink.get_input_source()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_lamp_status(self, mock_log, mock_send_command):
-        """
-        Test sending command to retrieve lamp(s) status
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'LAMP'
-        test_log = '(127.0.0.1) Sending LAMP command'
-
-        # WHEN: get_lamp_status is called
-        pjlink.get_lamp_status()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_manufacturer(self, mock_log, mock_send_command):
-        """
-        Test sending command to retrieve manufacturer name
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'INF1'
-        test_log = '(127.0.0.1) Sending INF1 command'
-
-        # WHEN: get_manufacturer is called
-        pjlink.get_manufacturer()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_model(self, mock_log, mock_send_command):
-        """
-        Test sending command to get model information
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'INF2'
-        test_log = '(127.0.0.1) Sending INF2 command'
-
-        # WHEN: get_model is called
-        pjlink.get_model()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_name(self, mock_log, mock_send_command):
-        """
-        Test sending command to get user-assigned name
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'NAME'
-        test_log = '(127.0.0.1) Sending NAME command'
-
-        # WHEN: get_name is called
-        pjlink.get_name()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_other_info(self, mock_log, mock_send_command):
-        """
-        Test sending command to retrieve other information
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'INFO'
-        test_log = '(127.0.0.1) Sending INFO command'
-
-        # WHEN: get_other_info is called
-        pjlink.get_other_info()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_get_power_status(self, mock_log, mock_send_command):
-        """
-        Test sending command to retrieve current power state
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        mock_log.reset_mock()
-        mock_send_command.reset_mock()
-        test_data = 'POWR'
-        test_log = '(127.0.0.1) Sending POWR command'
-
-        # WHEN: get_power_status called
-        pjlink.get_power_status()
-
-        # THEN: log data and send_command should have been called
-        mock_log.debug.assert_called_once_with(test_log)
-        mock_send_command.assert_called_once_with(cmd=test_data)
-
-    def test_projector_get_status_error(self):
-        """
-        Test to check returned information for error code
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        test_string = 'E_SOCKET_ADDRESS_NOT_AVAILABLE'
-        test_message = 'The address specified to socket.bind() does not belong to the host'
-
-        # WHEN: get_status called
-        string, message = pjlink._get_status(status=E_SOCKET_ADDRESS_NOT_AVAILABLE)
-
-        # THEN: Proper strings should have been returned
-        self.assertEqual(string, test_string, 'Code as string should have been returned')
-        self.assertEqual(message, test_message, 'Description of code should have been returned')
-
-    def test_projector_get_status_invalid(self):
-        """
-        Test to check returned information for error code
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        test_string = 'Test string since get_status will only work with int'
-        test_message = 'Invalid status code'
-
-        # WHEN: get_status called
-        string, message = pjlink._get_status(status=test_string)
-
-        # THEN: Proper strings should have been returned
-        self.assertEqual(string, -1, 'Should have returned -1 as a bad status check')
-        self.assertEqual(message, test_message, 'Error message should have been returned')
-
-    def test_projector_get_status_status(self):
-        """
-        Test to check returned information for status codes
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        test_string = 'S_NOT_CONNECTED'
-        test_message = 'Not connected'
-
-        # WHEN: get_status called
-        string, message = pjlink._get_status(status=S_NOT_CONNECTED)
-
-        # THEN: Proper strings should have been returned
-        self.assertEqual(string, test_string, 'Code as string should have been returned')
-        self.assertEqual(message, test_message, 'Description of code should have been returned')
-
-    def test_projector_get_status_unknown(self):
-        """
-        Test to check returned information for unknown code
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        test_string = 999999
-        test_message = 'Unknown status'
-
-        # WHEN: get_status called
-        string, message = pjlink._get_status(status=test_string)
-
-        # THEN: Proper strings should have been returned
-        self.assertEqual(string, test_string, 'Received code should have been returned')
-        self.assertEqual(message, test_message, 'Unknown status string should have been returned')
-
-    def test_projector_process_inf1(self):
-        """
-        Test saving INF1 data (manufacturer)
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.manufacturer = None
-        test_data = 'TEst INformation MultiCase'
-
-        # WHEN: process_inf called with test data
-        pjlink.process_inf1(data=test_data)
-
-        # THEN: Data should be saved
-        self.assertEqual(pjlink.manufacturer, test_data, 'Test data should have been saved')
-
-    def test_projector_process_inf2(self):
-        """
-        Test saving INF2 data (model)
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.model = None
-        test_data = 'TEst moDEl MultiCase'
-
-        # WHEN: process_inf called with test data
-        pjlink.process_inf2(data=test_data)
-
-        # THEN: Data should be saved
-        self.assertEqual(pjlink.model, test_data, 'Test data should have been saved')
-
-    def test_projector_process_info(self):
-        """
-        Test saving INFO data (other information)
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.other_info = None
-        test_data = 'TEst ExtrANEous MultiCase INformatoin that MFGR might Set'
-
-        # WHEN: process_inf called with test data
-        pjlink.process_info(data=test_data)
-
-        # THEN: Data should be saved
-        self.assertEqual(pjlink.other_info, test_data, 'Test data should have been saved')
-
-    @patch.object(pjlink_test, 'projectorUpdateIcons')
-    def test_projector_process_avmt_bad_data(self, mock_UpdateIcons):
-        """
-        Test avmt bad data fail
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.shutter = True
-        pjlink.mute = True
-
-        # WHEN: Called with an invalid setting
-        pjlink.process_avmt('36')
-
-        # THEN: Shutter should be closed and mute should be True
-        self.assertTrue(pjlink.shutter, 'Shutter should changed')
-        self.assertTrue(pjlink.mute, 'Audio should not have changed')
-        self.assertFalse(mock_UpdateIcons.emit.called, 'Update icons should NOT have been called')
-
-    @patch.object(pjlink_test, 'projectorUpdateIcons')
-    def test_projector_process_avmt_closed_muted(self, mock_UpdateIcons):
-        """
-        Test avmt status shutter closed and mute off
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.shutter = False
-        pjlink.mute = False
-
-        # WHEN: Called with setting shutter to closed and mute on
-        pjlink.process_avmt('31')
-
-        # THEN: Shutter should be closed and mute should be True
-        self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
-        self.assertTrue(pjlink.mute, 'Audio should be muted')
-        self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
-
-    @patch.object(pjlink_test, 'projectorUpdateIcons')
-    def test_projector_process_avmt_shutter_closed(self, mock_UpdateIcons):
-        """
-        Test avmt status shutter closed and audio unchanged
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.shutter = False
-        pjlink.mute = True
-
-        # WHEN: Called with setting shutter closed and mute off
-        pjlink.process_avmt('11')
-
-        # THEN: Shutter should be True and mute should be False
-        self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
-        self.assertTrue(pjlink.mute, 'Audio should not have changed')
-        self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
-
-    @patch.object(pjlink_test, 'projectorUpdateIcons')
-    def test_projector_process_avmt_audio_muted(self, mock_UpdateIcons):
-        """
-        Test avmt status shutter unchanged and mute on
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.shutter = True
-        pjlink.mute = False
-
-        # WHEN: Called with setting shutter closed and mute on
-        pjlink.process_avmt('21')
-
-        # THEN: Shutter should be closed and mute should be True
-        self.assertTrue(pjlink.shutter, 'Shutter should not have changed')
-        self.assertTrue(pjlink.mute, 'Audio should be off')
-        self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
-
-    @patch.object(pjlink_test, 'projectorUpdateIcons')
-    def test_projector_process_avmt_open_unmuted(self, mock_UpdateIcons):
-        """
-        Test avmt status shutter open and mute off
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.shutter = True
-        pjlink.mute = True
-
-        # WHEN: Called with setting shutter to closed and mute on
-        pjlink.process_avmt('30')
-
-        # THEN: Shutter should be closed and mute should be True
-        self.assertFalse(pjlink.shutter, 'Shutter should have been set to open')
-        self.assertFalse(pjlink.mute, 'Audio should be on')
-        self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
-
-    def test_projector_process_clss_one(self):
-        """
-        Test class 1 sent from projector
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-
-        # WHEN: Process class response
-        pjlink.process_clss('1')
-
-        # THEN: Projector class should be set to 1
-        self.assertEqual(pjlink.pjlink_class, '1',
-                         'Projector should have set class=1')
-
-    def test_projector_process_clss_two(self):
-        """
-        Test class 2 sent from projector
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-
-        # WHEN: Process class response
-        pjlink.process_clss('2')
-
-        # THEN: Projector class should be set to 1
-        self.assertEqual(pjlink.pjlink_class, '2',
-                         'Projector should have set class=2')
-
-    def test_projector_process_clss_nonstandard_reply_optoma(self):
-        """
-        Bugfix 1550891: CLSS request returns non-standard reply with Optoma projector
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-
-        # WHEN: Process non-standard reply
-        pjlink.process_clss('Class 1')
-
-        # THEN: Projector class should be set with proper value
-        self.assertEqual(pjlink.pjlink_class, '1',
-                         'Non-standard class reply should have set class=1')
-
-    def test_projector_process_clss_nonstandard_reply_benq(self):
-        """
-        Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-
-        # WHEN: Process non-standard reply
-        pjlink.process_clss('Version2')
-
-        # THEN: Projector class should be set with proper value
-        # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify
-        self.assertEqual(pjlink.pjlink_class, '2',
-                         'Non-standard class reply should have set class=2')
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_clss_invalid_nan(self, mock_log):
-        """
-        Test CLSS reply has no class number
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-
-        # WHEN: Process invalid reply
-        pjlink.process_clss('Z')
-        log_text = "(127.0.0.1) NAN clss version reply 'Z' - defaulting to class '1'"
-
-        # THEN: Projector class should be set with default value
-        self.assertEqual(pjlink.pjlink_class, '1',
-                         'Non-standard class reply should have set class=1')
-        mock_log.error.assert_called_once_with(log_text)
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_clss_invalid_no_version(self, mock_log):
-        """
-        Test CLSS reply has no class number
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-
-        # WHEN: Process invalid reply
-        pjlink.process_clss('Invalid')
-        log_text = "(127.0.0.1) No numbers found in class version reply 'Invalid' - defaulting to class '1'"
-
-        # THEN: Projector class should be set with default value
-        self.assertEqual(pjlink.pjlink_class, '1',
-                         'Non-standard class reply should have set class=1')
-        mock_log.error.assert_called_once_with(log_text)
-
-    def test_projector_process_erst_all_ok(self):
-        """
-        Test test_projector_process_erst_all_ok
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        chk_test = PJLINK_ERST_STATUS['OK']
-        chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
-
-        # WHEN: process_erst with no errors
-        pjlink.process_erst(chk_param)
-
-        # THEN: PJLink instance errors should be None
-        self.assertIsNone(pjlink.projector_errors, 'projector_errors should have been set to None')
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_erst_data_invalid_length(self, mock_log):
-        """
-        Test test_projector_process_erst_data_invalid_length
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.projector_errors = None
-        log_text = "127.0.0.1) Invalid error status response '11111111': length != 6"
-
-        # WHEN: process_erst called with invalid data (too many values
-        pjlink.process_erst('11111111')
-
-        # THEN: pjlink.projector_errors should be empty and warning logged
-        self.assertIsNone(pjlink.projector_errors, 'There should be no errors')
-        self.assertTrue(mock_log.warning.called, 'Warning should have been logged')
-        mock_log.warning.assert_called_once_with(log_text)
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_erst_data_invalid_nan(self, mock_log):
-        """
-        Test test_projector_process_erst_data_invalid_nan
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.projector_errors = None
-        log_text = "(127.0.0.1) Invalid error status response '1111Z1'"
-
-        # WHEN: process_erst called with invalid data (too many values
-        pjlink.process_erst('1111Z1')
-
-        # THEN: pjlink.projector_errors should be empty and warning logged
-        self.assertIsNone(pjlink.projector_errors, 'There should be no errors')
-        self.assertTrue(mock_log.warning.called, 'Warning should have been logged')
-        mock_log.warning.assert_called_once_with(log_text)
-
-    def test_projector_process_erst_all_warn(self):
-        """
-        Test test_projector_process_erst_all_warn
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        chk_test = PJLINK_ERST_STATUS[E_WARN]
-        chk_string = ERROR_STRING[E_WARN]
-        chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
-
-        # WHEN: process_erst with status set to WARN
-        pjlink.process_erst(chk_param)
-
-        # THEN: PJLink instance errors should match chk_value
-        for chk in pjlink.projector_errors:
-            self.assertEqual(pjlink.projector_errors[chk], chk_string,
-                             "projector_errors['{chk}'] should have been set to {err}".format(chk=chk,
-                                                                                              err=chk_string))
-
-    def test_projector_process_erst_all_error(self):
-        """
-        Test test_projector_process_erst_all_error
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        chk_test = PJLINK_ERST_STATUS[E_ERROR]
-        chk_string = ERROR_STRING[E_ERROR]
-        chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
-
-        # WHEN: process_erst with status set to WARN
-        pjlink.process_erst(chk_param)
-
-        # THEN: PJLink instance errors should match chk_value
-        for chk in pjlink.projector_errors:
-            self.assertEqual(pjlink.projector_errors[chk], chk_string,
-                             "projector_errors['{chk}'] should have been set to {err}".format(chk=chk,
-                                                                                              err=chk_string))
-
-    def test_projector_process_erst_warn_cover_only(self):
-        """
-        Test test_projector_process_erst_warn_cover_only
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        chk_test = PJLINK_ERST_STATUS[E_WARN]
-        chk_string = ERROR_STRING[E_WARN]
-        pos = PJLINK_ERST_DATA['COVER']
-        build_chk = []
-        for check in range(0, len(PJLINK_ERST_POSITIONS)):
-            if check == pos:
-                build_chk.append(chk_test)
-            else:
-                build_chk.append(PJLINK_ERST_STATUS['OK'])
-        chk_param = ''.join(build_chk)
-
-        # WHEN: process_erst with cover only set to WARN and all others set to OK
-        pjlink.process_erst(chk_param)
-
-        # THEN: Only COVER should have an error
-        self.assertEqual(len(pjlink.projector_errors), 1, 'projector_errors should only have 1 error')
-        self.assertTrue(('Cover' in pjlink.projector_errors), 'projector_errors should have an error for "Cover"')
-        self.assertEqual(pjlink.projector_errors['Cover'],
-                         chk_string,
-                         'projector_errors["Cover"] should have error "{err}"'.format(err=chk_string))
-
-    def test_projector_process_inpt(self):
-        """
-        Test input source status shows current input
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.source = '0'
-
-        # WHEN: Called with input source
-        pjlink.process_inpt('1')
-
-        # THEN: Input selected should reflect current input
-        self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"')
-
-    @patch.object(pjlink_test, 'projectorUpdateIcons')
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_inst(self, mock_log, mock_UpdateIcons):
-        """
-        Test saving video source available information
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.source_available = []
-        test_data = '21 10 30 31 11 20'
-        test_saved = ['10', '11', '20', '21', '30', '31']
-        log_data = '(127.0.0.1) Setting projector sources_available to ' \
-            '"[\'10\', \'11\', \'20\', \'21\', \'30\', \'31\']"'
-        mock_UpdateIcons.reset_mock()
-        mock_log.reset_mock()
-
-        # WHEN: process_inst called with test data
-        pjlink.process_inst(data=test_data)
-
-        # THEN: Data should have been sorted and saved properly
-        self.assertEqual(pjlink.source_available, test_saved, "Sources should have been sorted and saved")
-        mock_log.debug.assert_called_once_with(log_data)
-        self.assertTrue(mock_UpdateIcons.emit.called, 'Update Icons should have been called')
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_lamp_invalid(self, mock_log):
-        """
-        Test status multiple lamp on/off and hours
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.lamp = [{'Hours': 00000, 'On': True},
-                       {'Hours': 11111, 'On': False}]
-        log_data = '(127.0.0.1) process_lamp(): Invalid data "11111 1 22222 0 333A3 1"'
-
-        # WHEN: Call process_command with invalid lamp data
-        pjlink.process_lamp('11111 1 22222 0 333A3 1')
-
-        # THEN: lamps should not have changed
-        self.assertEqual(len(pjlink.lamp), 2,
-                         'Projector should have kept 2 lamps specified')
-        self.assertEqual(pjlink.lamp[0]['On'], True,
-                         'Lamp 1 power status should have been set to TRUE')
-        self.assertEqual(pjlink.lamp[0]['Hours'], 00000,
-                         'Lamp 1 hours should have been left at 00000')
-        self.assertEqual(pjlink.lamp[1]['On'], False,
-                         'Lamp 2 power status should have been set to FALSE')
-        self.assertEqual(pjlink.lamp[1]['Hours'], 11111,
-                         'Lamp 2 hours should have been left at 11111')
-        mock_log.warning.assert_called_once_with(log_data)
-
-    def test_projector_process_lamp_multiple(self):
-        """
-        Test status multiple lamp on/off and hours
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.lamps = []
-
-        # WHEN: Call process_command with lamp data
-        pjlink.process_lamp('11111 1 22222 0 33333 1')
-
-        # THEN: Lamp should have been set with proper lamp status
-        self.assertEqual(len(pjlink.lamp), 3,
-                         'Projector should have 3 lamps specified')
-        self.assertEqual(pjlink.lamp[0]['On'], True,
-                         'Lamp 1 power status should have been set to TRUE')
-        self.assertEqual(pjlink.lamp[0]['Hours'], 11111,
-                         'Lamp 1 hours should have been set to 11111')
-        self.assertEqual(pjlink.lamp[1]['On'], False,
-                         'Lamp 2 power status should have been set to FALSE')
-        self.assertEqual(pjlink.lamp[1]['Hours'], 22222,
-                         'Lamp 2 hours should have been set to 22222')
-        self.assertEqual(pjlink.lamp[2]['On'], True,
-                         'Lamp 3 power status should have been set to TRUE')
-        self.assertEqual(pjlink.lamp[2]['Hours'], 33333,
-                         'Lamp 3 hours should have been set to 33333')
-
-    def test_projector_process_lamp_single(self):
-        """
-        Test status lamp on/off and hours
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.lamps = []
-
-        # WHEN: Call process_command with lamp data
-        pjlink.process_lamp('22222 1')
-
-        # THEN: Lamp should have been set with status=ON and hours=22222
-        self.assertEqual(pjlink.lamp[0]['On'], True,
-                         'Lamp power status should have been set to TRUE')
-        self.assertEqual(pjlink.lamp[0]['Hours'], 22222,
-                         'Lamp hours should have been set to 22222')
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_name(self, mock_log):
-        """
-        Test saving NAME data from projector
-        """
-        # GIVEN: Test data
-        pjlink = pjlink_test
-        test_data = "Some Name the End-User Set IN Projector"
-        test_log = '(127.0.0.1) Setting projector PJLink name to "Some Name the End-User Set IN Projector"'
-        mock_log.reset_mock()
-
-        # WHEN: process_name called with test data
-        pjlink.process_name(data=test_data)
-
-        # THEN: name should be set and logged
-        self.assertEqual(pjlink.pjlink_name, test_data, 'Name test data should have been saved')
-        mock_log.debug.assert_called_once_with(test_log)
-
-    @patch.object(pjlink_test, 'projectorUpdateIcons')
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(pjlink_test, 'change_status')
-    def test_projector_process_powr_on(self,
-                                       mock_change_status,
-                                       mock_send_command,
-                                       mock_UpdateIcons):
-        """
-        Test status power to ON
-        """
-        # GIVEN: Test object and preset
-        pjlink = pjlink_test
-        pjlink.power = S_STANDBY
-        test_data = PJLINK_POWR_STATUS[S_ON]
-
-        # WHEN: Call process_command with turn power on command
-        pjlink.process_command(cmd='POWR', data=test_data)
-
-        # THEN: Power should be set to ON
-        self.assertEqual(pjlink.power, S_ON, 'Power should have been set to ON')
-        mock_send_command.assert_called_once_with('INST')
-        mock_change_status.assert_called_once_with(PJLINK_POWR_STATUS[test_data])
-        self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
-
-    @patch.object(pjlink_test, 'projectorUpdateIcons')
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(pjlink_test, 'change_status')
-    def test_projector_process_powr_invalid(self,
-                                            mock_change_status,
-                                            mock_send_command,
-                                            mock_UpdateIcons):
-        """
-        Test process_powr invalid call
-        """
-        # GIVEN: Test object and preset
-        pjlink = pjlink_test
-        pjlink.power = S_STANDBY
-        test_data = '99'
-
-        # WHEN: Call process_command with turn power on command
-        pjlink.process_command(cmd='POWR', data=test_data)
-
-        # THEN: Power should be set to ON
-        self.assertEqual(pjlink.power, S_STANDBY, 'Power should not have changed')
-        self.assertFalse(mock_change_status.called, 'Change status should not have been called')
-        self.assertFalse(mock_send_command.called, 'send_command("INST") should not have been called')
-        self.assertFalse(mock_UpdateIcons.emit.called, 'projectorUpdateIcons should not have been called')
-
-    @patch.object(pjlink_test, 'projectorUpdateIcons')
-    @patch.object(pjlink_test, 'send_command')
-    @patch.object(pjlink_test, 'change_status')
-    def test_projector_process_powr_off(self,
-                                        mock_change_status,
-                                        mock_send_command,
-                                        mock_UpdateIcons):
-        """
-        Test status power to STANDBY
-        """
-        # GIVEN: Test object and preset
-        pjlink = pjlink_test
-        pjlink.power = S_ON
-        test_data = PJLINK_POWR_STATUS[S_STANDBY]
-
-        # WHEN: Call process_command with turn power on command
-        pjlink.process_command(cmd='POWR', data=test_data)
-
-        # THEN: Power should be set to STANDBY
-        self.assertEqual(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
-        self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
-        mock_change_status.assert_called_once_with(PJLINK_POWR_STATUS[test_data])
-        self.assertFalse(mock_send_command.called, "send_command['INST'] should not have been called")
-
-    def test_projector_process_rfil_save(self):
-        """
-        Test saving filter type
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.model_filter = None
-        filter_model = 'Filter Type Test'
-
-        # WHEN: Filter model is received
-        pjlink.process_rfil(data=filter_model)
-
-        # THEN: Filter model number should be saved
-        self.assertEqual(pjlink.model_filter, filter_model, 'Filter type should have been saved')
-
-    def test_projector_process_rfil_nosave(self):
-        """
-        Test saving filter type previously saved
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.model_filter = 'Old filter type'
-        filter_model = 'Filter Type Test'
-
-        # WHEN: Filter model is received
-        pjlink.process_rfil(data=filter_model)
-
-        # THEN: Filter model number should be saved
-        self.assertNotEquals(pjlink.model_filter, filter_model, 'Filter type should NOT have been saved')
-
-    def test_projector_process_rlmp_save(self):
-        """
-        Test saving lamp type
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.model_lamp = None
-        lamp_model = 'Lamp Type Test'
-
-        # WHEN: Filter model is received
-        pjlink.process_rlmp(data=lamp_model)
-
-        # THEN: Filter model number should be saved
-        self.assertEqual(pjlink.model_lamp, lamp_model, 'Lamp type should have been saved')
-
-    def test_projector_process_rlmp_nosave(self):
-        """
-        Test saving lamp type previously saved
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.model_lamp = 'Old lamp type'
-        lamp_model = 'Filter Type Test'
-
-        # WHEN: Filter model is received
-        pjlink.process_rlmp(data=lamp_model)
-
-        # THEN: Filter model number should be saved
-        self.assertNotEquals(pjlink.model_lamp, lamp_model, 'Lamp type should NOT have been saved')
-
-    def test_projector_process_snum_set(self):
-        """
-        Test saving serial number from projector
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.serial_no = None
-        test_number = 'Test Serial Number'
-
-        # WHEN: No serial number is set and we receive serial number command
-        pjlink.process_snum(data=test_number)
-
-        # THEN: Serial number should be set
-        self.assertEqual(pjlink.serial_no, test_number,
-                         'Projector serial number should have been set')
-
-    def test_projector_process_snum_different(self):
-        """
-        Test projector serial number different than saved serial number
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.serial_no = 'Previous serial number'
-        test_number = 'Test Serial Number'
-
-        # WHEN: No serial number is set and we receive serial number command
-        pjlink.process_snum(data=test_number)
-
-        # THEN: Serial number should be set
-        self.assertNotEquals(pjlink.serial_no, test_number,
-                             'Projector serial number should NOT have been set')
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_sver(self, mock_log):
-        """
-        Test invalid software version information - too long
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.sw_version = None
-        pjlink.sw_version_received = None
-        test_data = 'Test 1 Subtest 1'
-        test_log = "(127.0.0.1) Setting projector software version to 'Test 1 Subtest 1'"
-        mock_log.reset_mock()
-
-        # WHEN: process_sver called with invalid data
-        pjlink.process_sver(data=test_data)
-
-        # THEN: Version information should not change
-        self.assertEqual(pjlink.sw_version, test_data, 'Software version should have been updated')
-        self.assertIsNone(pjlink.sw_version_received, 'Received software version should not have changed')
-        mock_log.debug.assert_called_once_with(test_log)
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_sver_changed(self, mock_log):
-        """
-        Test invalid software version information - Received different than saved
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        test_data_new = 'Test 1 Subtest 2'
-        test_data_old = 'Test 1 Subtest 1'
-        pjlink.sw_version = test_data_old
-        pjlink.sw_version_received = None
-        test_log = '(127.0.0.1) Saving new serial number as sw_version_received'
-        mock_log.reset_mock()
-
-        # WHEN: process_sver called with invalid data
-        pjlink.process_sver(data=test_data_new)
-
-        # THEN: Version information should not change
-        self.assertEqual(pjlink.sw_version, test_data_old, 'Software version should not have been updated')
-        self.assertEqual(pjlink.sw_version_received, test_data_new,
-                         'Received software version should have been changed')
-        self.assertEqual(mock_log.warning.call_count, 4, 'log.warn should have been called 4 times')
-        # There was 4 calls, but only the last one is checked with this method
-        mock_log.warning.assert_called_with(test_log)
-
-    @patch.object(openlp.core.lib.projector.pjlink, 'log')
-    def test_projector_process_sver_invalid(self, mock_log):
-        """
-        Test invalid software version information - too long
-        """
-        # GIVEN: Test object
-        pjlink = pjlink_test
-        pjlink.sw_version = None
-        pjlink.sw_version_received = None
-        test_data = 'This is a test software version line that is too long based on PJLink version 2 specs'
-        test_log = "Invalid software version - too long"
-        mock_log.reset_mock()
-
-        # WHEN: process_sver called with invalid data
-        pjlink.process_sver(data=test_data)
-
-        # THEN: Version information should not change
-        self.assertIsNone(pjlink.sw_version, 'Software version should not have changed')
-        self.assertIsNone(pjlink.sw_version_received, 'Received software version should not have changed')
-        mock_log.warning.assert_called_once_with(test_log)
-
-    def test_projector_reset_information(self):
-        """
-        Test reset_information() resets all information and stops timers
-        """
-        # GIVEN: Test object and test data
-        pjlink = pjlink_test
-        pjlink.power = S_ON
-        pjlink.pjlink_name = 'OPENLPTEST'
-        pjlink.manufacturer = 'PJLINK'
-        pjlink.model = '1'
-        pjlink.shutter = True
-        pjlink.mute = True
-        pjlink.lamp = True
-        pjlink.fan = True
-        pjlink.source_available = True
-        pjlink.other_info = 'ANOTHER TEST'
-        pjlink.send_queue = True
-        pjlink.send_busy = True
-
-        # WHEN: reset_information() is called
-        with patch.object(pjlink, 'timer') as mock_timer:
-            with patch.object(pjlink, 'socket_timer') as mock_socket_timer:
-                pjlink.reset_information()
-
-        # THEN: All information should be reset and timers stopped
-        self.assertEqual(pjlink.power, S_OFF, 'Projector power should be OFF')
-        self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None')
-        self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None')
-        self.assertIsNone(pjlink.model, 'Projector model should be None')
-        self.assertIsNone(pjlink.shutter, 'Projector shutter should be None')
-        self.assertIsNone(pjlink.mute, 'Projector shuttter should be None')
-        self.assertIsNone(pjlink.lamp, 'Projector lamp should be None')
-        self.assertIsNone(pjlink.fan, 'Projector fan should be None')
-        self.assertIsNone(pjlink.source_available, 'Projector source_available should be None')
-        self.assertIsNone(pjlink.source, 'Projector source should be None')
-        self.assertIsNone(pjlink.other_info, 'Projector other_info should be None')
-        self.assertEqual(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
-        self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False')
-        self.assertTrue(mock_timer.stop.called, 'Projector timer.stop()  should have been called')
-        self.assertTrue(mock_socket_timer.stop.called, 'Projector socket_timer.stop() should have been called')

=== added directory 'tests/functional/openlp_core/projectors'
=== added file 'tests/functional/openlp_core/projectors/__init__.py'
--- tests/functional/openlp_core/projectors/__init__.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core/projectors/__init__.py	2017-11-10 12:12:52 +0000
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2017 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+Module-level functions for the functional test suite
+"""

=== added file 'tests/functional/openlp_core/projectors/test_projector_constants.py'
--- tests/functional/openlp_core/projectors/test_projector_constants.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core/projectors/test_projector_constants.py	2017-11-10 12:12:52 +0000
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2015 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+Package to test the openlp.core.projectors.constants module.
+"""
+from unittest import TestCase
+
+
+class TestProjectorConstants(TestCase):
+    """
+    Test specific functions in the projector constants module.
+    """
+    def test_build_pjlink_video_label(self):
+        """
+        Test building PJLINK_DEFAULT_CODES dictionary
+        """
+        # GIVEN: Test data
+        from tests.resources.projector.data import TEST_VIDEO_CODES
+
+        # WHEN: Import projector PJLINK_DEFAULT_CODES
+        from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES
+
+        # THEN: Verify dictionary was build correctly
+        self.assertEqual(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match')

=== added file 'tests/functional/openlp_core/projectors/test_projector_db.py'
--- tests/functional/openlp_core/projectors/test_projector_db.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core/projectors/test_projector_db.py	2017-11-10 12:12:52 +0000
@@ -0,0 +1,462 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2017 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+Package to test the openlp.core.projectors.db module.
+record functions.
+
+PREREQUISITE: add_record() and get_all() functions validated.
+"""
+import os
+import shutil
+from tempfile import mkdtemp
+
+from unittest import TestCase
+from unittest.mock import patch
+
+from openlp.core.lib.db import upgrade_db
+from openlp.core.projectors import upgrade
+from openlp.core.projectors.constants import PJLINK_PORT
+from openlp.core.projectors.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source
+
+from tests.resources.projector.data import TEST_DB_PJLINK1, TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA
+from tests.utils.constants import TEST_RESOURCES_PATH
+
+
+def compare_data(one, two):
+    """
+    Verify two Projector() instances contain the same data
+    """
+    return one is not None and \
+        two is not None and \
+        one.ip == two.ip and \
+        one.port == two.port and \
+        one.name == two.name and \
+        one.location == two.location and \
+        one.notes == two.notes and \
+        one.sw_version == two.sw_version and \
+        one.serial_no == two.serial_no and \
+        one.model_filter == two.model_filter and \
+        one.model_lamp == two.model_lamp
+
+
+def compare_source(one, two):
+    """
+    Verify two ProjectorSource instances contain the same data
+    """
+    return one is not None and \
+        two is not None and \
+        one.projector_id == two.projector_id and \
+        one.code == two.code and \
+        one.text == two.text
+
+
+def add_records(projector_db, test):
+    """
+    Add record if not in database
+    """
+    record_list = projector_db.get_projector_all()
+    if len(record_list) < 1:
+        added = False
+        for record in test:
+            added = projector_db.add_projector(record) or added
+        return added
+
+    for new_record in test:
+        added = None
+        for record in record_list:
+            if compare_data(record, new_record):
+                break
+            added = projector_db.add_projector(new_record)
+    return added
+
+
+class TestProjectorDBUpdate(TestCase):
+    """
+    Test case for upgrading Projector DB.
+    NOTE: Separate class so I don't have to look for upgrade tests.
+    """
+    def setUp(self):
+        """
+        Setup for tests
+        """
+        self.tmp_folder = mkdtemp(prefix='openlp_')
+
+    def tearDown(self):
+        """
+        Clean up after tests
+        """
+        # Ignore errors since windows can have problems with locked files
+        shutil.rmtree(self.tmp_folder, ignore_errors=True)
+
+    def test_upgrade_old_projector_db(self):
+        """
+        Test that we can upgrade an old song db to the current schema
+        """
+        # GIVEN: An old prjector db
+        old_db = os.path.join(TEST_RESOURCES_PATH, "projector", TEST_DB_PJLINK1)
+        tmp_db = os.path.join(self.tmp_folder, TEST_DB)
+        shutil.copyfile(old_db, tmp_db)
+        db_url = 'sqlite:///{db}'.format(db=tmp_db)
+
+        # WHEN: upgrading the db
+        updated_to_version, latest_version = upgrade_db(db_url, upgrade)
+
+        # THEN: the song db should have been upgraded to the latest version
+        self.assertEqual(updated_to_version, latest_version,
+                         'The projector DB should have been upgrade to the latest version')
+
+
+class TestProjectorDB(TestCase):
+    """
+    Test case for ProjectorDB
+    """
+    @patch('openlp.core.projectors.db.init_url')
+    def setUp(self, mocked_init_url):
+        """
+        Set up anything necessary for all tests
+        """
+        self.tmp_folder = mkdtemp(prefix='openlp_')
+        tmpdb_url = 'sqlite:///{db}'.format(db=os.path.join(self.tmp_folder, TEST_DB))
+        mocked_init_url.return_value = tmpdb_url
+        self.projector = ProjectorDB()
+
+    def tearDown(self):
+        """
+        Clean up
+        """
+        self.projector.session.close()
+        self.projector = None
+        # Ignore errors since windows can have problems with locked files
+        shutil.rmtree(self.tmp_folder, ignore_errors=True)
+
+    def test_find_record_by_ip(self):
+        """
+        Test find record by IP
+        """
+        # GIVEN: Record entries in database
+        add_records(self.projector, [Projector(**TEST1_DATA), Projector(**TEST2_DATA)])
+
+        # WHEN: Search for record using IP
+        record = self.projector.get_projector_by_ip(TEST2_DATA['ip'])
+
+        # THEN: Verify proper record returned
+        self.assertTrue(compare_data(Projector(**TEST2_DATA), record),
+                        'Record found should have been test_2 data')
+
+    def test_find_record_by_name(self):
+        """
+        Test find record by name
+        """
+        # GIVEN: Record entries in database
+        add_records(self.projector, [Projector(**TEST1_DATA), Projector(**TEST2_DATA)])
+
+        # WHEN: Search for record using name
+        record = self.projector.get_projector_by_name(TEST2_DATA['name'])
+
+        # THEN: Verify proper record returned
+        self.assertTrue(compare_data(Projector(**TEST2_DATA), record),
+                        'Record found should have been test_2 data')
+
+    def test_record_delete(self):
+        """
+        Test record can be deleted
+        """
+        # GIVEN: Record in database
+        add_records(self.projector, [Projector(**TEST3_DATA), ])
+        record = self.projector.get_projector_by_ip(TEST3_DATA['ip'])
+
+        # WHEN: Record deleted
+        self.projector.delete_projector(record)
+
+        # THEN: Verify record not retrievable
+        found = self.projector.get_projector_by_ip(TEST3_DATA['ip'])
+        self.assertFalse(found, 'test_3 record should have been deleted')
+
+    def test_record_edit(self):
+        """
+        Test edited record returns the same record ID with different data
+        """
+        # GIVEN: Record entries in database
+        add_records(self.projector, [Projector(**TEST1_DATA), Projector(**TEST2_DATA)])
+
+        # WHEN: We retrieve a specific record
+        record = self.projector.get_projector_by_ip(TEST1_DATA['ip'])
+        record_id = record.id
+
+        # WHEN: Data is changed
+        record.ip = TEST3_DATA['ip']
+        record.port = TEST3_DATA['port']
+        record.pin = TEST3_DATA['pin']
+        record.name = TEST3_DATA['name']
+        record.location = TEST3_DATA['location']
+        record.notes = TEST3_DATA['notes']
+        record.sw_version = TEST3_DATA['sw_version']
+        record.serial_no = TEST3_DATA['serial_no']
+        record.model_filter = TEST3_DATA['model_filter']
+        record.model_lamp = TEST3_DATA['model_lamp']
+        updated = self.projector.update_projector(record)
+        self.assertTrue(updated, 'Save updated record should have returned True')
+        record = self.projector.get_projector_by_ip(TEST3_DATA['ip'])
+
+        # THEN: Record ID should remain the same, but data should be changed
+        self.assertEqual(record_id, record.id, 'Edited record should have the same ID')
+        self.assertTrue(compare_data(Projector(**TEST3_DATA), record), 'Edited record should have new data')
+
+    def test_source_add(self):
+        """
+        Test source entry for projector item
+        """
+        # GIVEN: Record entries in database
+        projector1 = Projector(**TEST1_DATA)
+        self.projector.add_projector(projector1)
+        item = self.projector.get_projector_by_id(projector1.id)
+        item_id = item.id
+
+        # WHEN: A source entry is saved for item
+        source = ProjectorSource(projector_id=item_id, code='11', text='First RGB source')
+        self.projector.add_source(source)
+
+        # THEN: Projector should have the same source entry
+        item = self.projector.get_projector_by_id(item_id)
+        self.assertTrue(compare_source(item.source_list[0], source))
+
+    def test_manufacturer_repr(self):
+        """
+        Test Manufacturer.__repr__() text
+        """
+        # GIVEN: Test object
+        manufacturer = Manufacturer()
+
+        # WHEN: Name is set
+        manufacturer.name = 'OpenLP Test'
+
+        # THEN: __repr__ should return a proper string
+        self.assertEqual(str(manufacturer), '<Manufacturer(name="OpenLP Test")>',
+                         'Manufacturer.__repr__() should have returned a proper representation string')
+
+    def test_model_repr(self):
+        """
+        Test Model.__repr__() text
+        """
+        # GIVEN: Test object
+        model = Model()
+
+        # WHEN: Name is set
+        model.name = 'OpenLP Test'
+
+        # THEN: __repr__ should return a proper string
+        self.assertEqual(str(model), '<Model(name='"OpenLP Test"')>',
+                         'Model.__repr__() should have returned a proper representation string')
+
+    def test_source_repr(self):
+        """
+        Test Source.__repr__() text
+        """
+        # GIVEN: Test object
+        source = Source()
+
+        # WHEN: Source() information is set
+        source.pjlink_name = 'Test object'
+        source.pjlink_code = '11'
+        source.text = 'Input text'
+
+        # THEN: __repr__ should return a proper string
+        self.assertEqual(str(source), '<Source(pjlink_name="Test object", pjlink_code="11", text="Input text")>',
+                         'Source.__repr__() should have returned a proper representation string')
+
+    def test_projector_repr(self):
+        """
+        Test Projector.__repr__() text
+        """
+        # GIVEN: Test object
+        projector = Projector()
+
+        # WHEN: projector() is populated
+        # NOTE: projector.[pin, other, sources, sw_version, serial_no, sw_version, model_lamp, model_filter]
+        #           should all return None.
+        #       projector.source_list should return an empty list
+        projector.id = 0
+        projector.ip = '127.0.0.1'
+        projector.port = PJLINK_PORT
+        projector.name = 'Test One'
+        projector.location = 'Somewhere over the rainbow'
+        projector.notes = 'Not again'
+        projector.pjlink_name = 'TEST'
+        projector.manufacturer = 'IN YOUR DREAMS'
+        projector.model = 'OpenLP'
+
+        # THEN: __repr__ should return a proper string
+        self.assertEqual(str(projector),
+                         '< Projector(id="0", ip="127.0.0.1", port="4352", mac_adx="None", pin="None", '
+                         'name="Test One", location="Somewhere over the rainbow", notes="Not again", '
+                         'pjlink_name="TEST", manufacturer="IN YOUR DREAMS", model="OpenLP", serial_no="None", '
+                         'other="None", sources="None", source_list="[]", model_filter="None", model_lamp="None", '
+                         'sw_version="None") >',
+                         'Projector.__repr__() should have returned a proper representation string')
+
+    def test_projectorsource_repr(self):
+        """
+        Test ProjectorSource.__repr__() text
+        """
+        # GIVEN: test setup
+        projector1 = Projector(**TEST1_DATA)
+        self.projector.add_projector(projector1)
+        item = self.projector.get_projector_by_id(projector1.id)
+        item_id = item.id
+
+        # WHEN: A source entry is saved for item
+        source = ProjectorSource(projector_id=item_id, code='11', text='First RGB source')
+        self.projector.add_source(source)
+
+        # THEN: __repr__ should return a proper string
+        self.assertEqual(str(source),
+                         '<ProjectorSource(id="1", code="11", text="First RGB source", projector_id="1")>',
+                         'ProjectorSource.__repr__)_ should have returned a proper representation string')
+
+    def test_get_projector_by_id_none(self):
+        """
+        Test get_projector_by_id() returns None if no db entry
+        """
+        # GIVEN: Test object and data
+        projector = self.projector
+
+        # WHEN: DB search for entry not saved
+        results = projector.get_projector_by_id(dbid=123134556409824506)
+
+        # THEN: Verify return was None
+        self.assertEqual(results, None, 'Returned results should have equaled None')
+
+    def test_get_projector_all_none(self):
+        """
+        Test get_projector_all() with no projectors in db
+        """
+        # GIVEN: Test object with no data
+        projector = self.projector
+
+        # WHEN: We retrieve the database entries
+        results = projector.get_projector_all()
+
+        # THEN: Verify results is None
+        self.assertEqual(results, [], 'Returned results should have returned an empty list')
+
+    def test_get_projector_all_one(self):
+        """
+        Test get_projector_all() with one entry in db
+        """
+        # GIVEN: One entry in database
+        projector = Projector(**TEST1_DATA)
+        self.projector.add_projector(projector)
+
+        # WHEN: We retrieve the database entries
+        results = self.projector.get_projector_all()
+
+        # THEN: We should have a list with one entry
+        self.assertEqual(len(results), 1, 'Returned results should have returned a list with one entry')
+        self.assertTrue((projector in results), 'Result should have been equal to TEST1_DATA')
+
+    def test_get_projector_all_many(self):
+        """
+        Test get_projector_all() with multiple entries in db
+        """
+        # GIVEN: multiple entries in database
+        projector_list = []
+        projector_list.append(Projector(**TEST1_DATA))
+        projector_list.append(Projector(**TEST2_DATA))
+        projector_list.append(Projector(**TEST3_DATA))
+        for projector in projector_list:
+            self.projector.add_projector(projector)
+
+        # WHEN: We retrieve the database entries
+        results = self.projector.get_projector_all()
+
+        # THEN: We should have a list with three entries
+        self.assertEqual(len(results), len(projector_list),
+                         'Returned results should have returned a list with three entries')
+        for projector in results:
+            self.assertTrue((projector in projector_list),
+                            'Projector DB entry should have been in expected list')
+
+    def test_get_projector_by_name_fail(self):
+        """
+        Test get_projector_by_name() fail
+        """
+        # GIVEN: Test entry in database
+        self.projector.add_projector(Projector(**TEST1_DATA))
+
+        # WHEN: We attempt to get a projector that's not in database
+        results = self.projector.get_projector_by_name(name=TEST2_DATA['name'])
+
+        # THEN: We should have None
+        self.assertEqual(results, None, 'projector.get_projector_by_name() should have returned None')
+
+    def test_add_projector_fail(self):
+        """
+        Test add_projector() fail
+        """
+        # GIVEN: Test entry in the database
+        self.projector.add_projector(Projector(**TEST1_DATA))
+
+        # WHEN: Attempt to add same projector entry
+        results = self.projector.add_projector(Projector(**TEST1_DATA))
+
+        # THEN: We should have failed to add new entry
+        self.assertFalse(results, 'add_projector() should have failed')
+
+    def test_update_projector_default_fail(self):
+        """
+        Test update_projector() with no options fails
+        """
+        # GIVEN: projector instance
+        projector = self.projector
+
+        # WHEN: attempt to update a projector entry with no options
+        results = projector.update_projector()
+
+        # THEN: We should have failed
+        self.assertFalse(results, 'update_projector(projector=None) should have returned False')
+
+    def test_update_projector_not_in_db_fail(self):
+        """
+        Test update_projector() when entry not in database
+        """
+        # GIVEN: Projector entry in database
+        self.projector.add_projector(Projector(**TEST1_DATA))
+        projector = Projector(**TEST2_DATA)
+
+        # WHEN: Attempt to update data with a different ID
+        results = self.projector.update_projector(projector)
+
+        # THEN: Results should be False
+        self.assertFalse(results, 'update_projector(projector=projector) should have returned False')
+
+    def test_delete_projector_fail(self):
+        """
+        Test delete_projector(projector) fails to delete record
+        """
+        # GIVEN: Test entry in db
+        self.projector.add_projector(Projector(**TEST1_DATA))
+
+        # wHEN: Attempting to delete an entry not in the databae
+        results = self.projector.delete_projector(Projector(**TEST2_DATA))
+
+        # THEN: Results should be False
+        self.assertFalse(results, 'delete_projector() should have returned False')

=== added file 'tests/functional/openlp_core/projectors/test_projector_pjlink_base.py'
--- tests/functional/openlp_core/projectors/test_projector_pjlink_base.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core/projectors/test_projector_pjlink_base.py	2017-11-10 12:12:52 +0000
@@ -0,0 +1,208 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2015 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+Package to test the openlp.core.projectors.pjlink base package.
+"""
+from unittest import TestCase
+from unittest.mock import call, patch, MagicMock
+
+from openlp.core.projectors import PJLink, Projector
+from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED
+
+from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA
+
+pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
+
+
+class TestPJLinkBase(TestCase):
+    """
+    Tests for the PJLink module
+    """
+    @patch.object(pjlink_test, 'readyRead')
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(pjlink_test, 'waitForReadyRead')
+    @patch('openlp.core.common.qmd5_hash')
+    def test_authenticated_connection_call(self,
+                                           mock_qmd5_hash,
+                                           mock_waitForReadyRead,
+                                           mock_send_command,
+                                           mock_readyRead):
+        """
+        Ticket 92187: Fix for projector connect with PJLink authentication exception.
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+
+        # WHEN: Calling check_login with authentication request:
+        pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
+
+        # THEN: Should have called qmd5_hash
+        self.assertTrue(mock_qmd5_hash.called_with(TEST_SALT,
+                                                   "Connection request should have been called with TEST_SALT"))
+        self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
+                                                   "Connection request should have been called with TEST_PIN"))
+
+    @patch.object(pjlink_test, 'change_status')
+    def test_status_change(self, mock_change_status):
+        """
+        Test process_command call with ERR2 (Parameter) status
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+
+        # WHEN: process_command is called with "ERR2" status from projector
+        pjlink.process_command('POWR', 'ERR2')
+
+        # THEN: change_status should have called change_status with E_UNDEFINED
+        #       as first parameter
+        mock_change_status.called_with(E_PARAMETER,
+                                       'change_status should have been called with "{}"'.format(
+                                           ERROR_STRING[E_PARAMETER]))
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(pjlink_test, 'waitForReadyRead')
+    @patch.object(pjlink_test, 'projectorAuthentication')
+    @patch.object(pjlink_test, 'timer')
+    @patch.object(pjlink_test, 'socket_timer')
+    def test_bug_1593882_no_pin_authenticated_connection(self,
+                                                         mock_socket_timer,
+                                                         mock_timer,
+                                                         mock_authentication,
+                                                         mock_ready_read,
+                                                         mock_send_command):
+        """
+        Test bug 1593882 no pin and authenticated request exception
+        """
+        # GIVEN: Test object and mocks
+        pjlink = pjlink_test
+        pjlink.pin = None
+        mock_ready_read.return_value = True
+
+        # WHEN: call with authentication request and pin not set
+        pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
+
+        # THEN: 'No Authentication' signal should have been sent
+        mock_authentication.emit.assert_called_with(pjlink.ip)
+
+    @patch.object(pjlink_test, 'waitForReadyRead')
+    @patch.object(pjlink_test, 'state')
+    @patch.object(pjlink_test, '_send_command')
+    @patch.object(pjlink_test, 'timer')
+    @patch.object(pjlink_test, 'socket_timer')
+    def test_bug_1593883_pjlink_authentication(self,
+                                               mock_socket_timer,
+                                               mock_timer,
+                                               mock_send_command,
+                                               mock_state,
+                                               mock_waitForReadyRead):
+        """
+        Test bugfix 1593883 pjlink authentication
+        """
+        # GIVEN: Test object and data
+        pjlink = pjlink_test
+        pjlink.pin = TEST_PIN
+        mock_state.return_value = pjlink.ConnectedState
+        mock_waitForReadyRead.return_value = True
+
+        # WHEN: Athenticated connection is called
+        pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
+
+        # THEN: send_command should have the proper authentication
+        self.assertEqual("{test}".format(test=mock_send_command.call_args),
+                         "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
+
+    @patch.object(pjlink_test, 'disconnect_from_host')
+    def test_socket_abort(self, mock_disconnect):
+        """
+        Test PJLink.socket_abort calls disconnect_from_host
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+
+        # WHEN: Calling socket_abort
+        pjlink.socket_abort()
+
+        # THEN: disconnect_from_host should be called
+        self.assertTrue(mock_disconnect.called, 'Should have called disconnect_from_host')
+
+    def test_poll_loop_not_connected(self):
+        """
+        Test PJLink.poll_loop not connected return
+        """
+        # GIVEN: Test object and mocks
+        pjlink = pjlink_test
+        pjlink.state = MagicMock()
+        pjlink.timer = MagicMock()
+        pjlink.state.return_value = False
+        pjlink.ConnectedState = True
+
+        # WHEN: PJLink.poll_loop called
+        pjlink.poll_loop()
+
+        # THEN: poll_loop should exit without calling any other method
+        self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method')
+
+    @patch.object(pjlink_test, 'send_command')
+    def test_poll_loop_start(self, mock_send_command):
+        """
+        Test PJLink.poll_loop makes correct calls
+        """
+        # GIVEN: test object and test data
+        pjlink = pjlink_test
+        pjlink.state = MagicMock()
+        pjlink.timer = MagicMock()
+        pjlink.timer.interval = MagicMock()
+        pjlink.timer.setInterval = MagicMock()
+        pjlink.timer.start = MagicMock()
+        pjlink.poll_time = 20
+        pjlink.power = S_ON
+        pjlink.source_available = None
+        pjlink.other_info = None
+        pjlink.manufacturer = None
+        pjlink.model = None
+        pjlink.pjlink_name = None
+        pjlink.ConnectedState = S_CONNECTED
+        pjlink.timer.interval.return_value = 10
+        pjlink.state.return_value = S_CONNECTED
+        call_list = [
+            call('POWR', queue=True),
+            call('ERST', queue=True),
+            call('LAMP', queue=True),
+            call('AVMT', queue=True),
+            call('INPT', queue=True),
+            call('INST', queue=True),
+            call('INFO', queue=True),
+            call('INF1', queue=True),
+            call('INF2', queue=True),
+            call('NAME', queue=True),
+        ]
+
+        # WHEN: PJLink.poll_loop is called
+        pjlink.poll_loop()
+
+        # THEN: proper calls were made to retrieve projector data
+        # First, call to update the timer with the next interval
+        self.assertTrue(pjlink.timer.setInterval.called, 'Should have updated the timer')
+        # Next, should have called the timer to start
+        self.assertTrue(pjlink.timer.start.called, 'Should have started the timer')
+        # Finally, should have called send_command with a list of projetctor status checks
+        mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries')

=== added file 'tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py'
--- tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py	2017-11-10 12:12:52 +0000
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2015 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+Package to test the openlp.core.projectors.pjlink command routing.
+"""
+
+from unittest import TestCase
+from unittest.mock import patch, MagicMock
+
+import openlp.core.projectors.pjlink
+from openlp.core.projectors import PJLink, Projector
+from openlp.core.projectors.constants import PJLINK_ERRORS, \
+    E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED
+
+'''
+from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
+    PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
+'''
+from tests.resources.projector.data import TEST_PIN, TEST1_DATA
+
+pjlink_test = PJLink(Projector(**TEST1_DATA), pin=TEST_PIN, no_poll=True)
+pjlink_test.ip = '127.0.0.1'
+
+
+class TestPJLinkRouting(TestCase):
+    """
+    Tests for the PJLink module command routing
+    """
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_process_command_call_clss(self, mock_log):
+        """
+        Test process_command calls proper function
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        log_text = '(127.0.0.1) Calling function for CLSS'
+        mock_log.reset_mock()
+        mock_process_clss = MagicMock()
+        pjlink.pjlink_functions['CLSS'] = mock_process_clss
+
+        # WHEN: process_command is called with valid function and data
+        pjlink.process_command(cmd='CLSS', data='1')
+
+        # THEN: Process method should have been called properly
+        mock_log.debug.assert_called_with(log_text)
+        mock_process_clss.assert_called_with('1')
+
+    @patch.object(pjlink_test, 'change_status')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_process_command_err1(self, mock_log, mock_change_status):
+        """
+        Test ERR1 - Undefined projector function
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        log_text = '(127.0.0.1) Projector returned error "ERR1"'
+        mock_change_status.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: process_command called with ERR1
+        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNDEFINED])
+
+        # THEN: Error should be logged and status_change should be called
+        mock_change_status.assert_called_once_with(E_UNDEFINED, 'Undefined Command: "CLSS"')
+        mock_log.error.assert_called_with(log_text)
+
+    @patch.object(pjlink_test, 'change_status')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_process_command_err2(self, mock_log, mock_change_status):
+        """
+        Test ERR2 - Parameter Error
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        log_text = '(127.0.0.1) Projector returned error "ERR2"'
+        mock_change_status.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: process_command called with ERR2
+        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PARAMETER])
+
+        # THEN: Error should be logged and status_change should be called
+        mock_change_status.assert_called_once_with(E_PARAMETER)
+        mock_log.error.assert_called_with(log_text)
+
+    @patch.object(pjlink_test, 'change_status')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_process_command_err3(self, mock_log, mock_change_status):
+        """
+        Test ERR3 - Unavailable error
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        log_text = '(127.0.0.1) Projector returned error "ERR3"'
+        mock_change_status.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: process_command called with ERR3
+        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNAVAILABLE])
+
+        # THEN: Error should be logged and status_change should be called
+        mock_change_status.assert_called_once_with(E_UNAVAILABLE)
+        mock_log.error.assert_called_with(log_text)
+
+    @patch.object(pjlink_test, 'change_status')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_process_command_err4(self, mock_log, mock_change_status):
+        """
+        Test ERR3 - Unavailable error
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        log_text = '(127.0.0.1) Projector returned error "ERR4"'
+        mock_change_status.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: process_command called with ERR3
+        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PROJECTOR])
+
+        # THEN: Error should be logged and status_change should be called
+        mock_change_status.assert_called_once_with(E_PROJECTOR)
+        mock_log.error.assert_called_with(log_text)
+
+    @patch.object(pjlink_test, 'projectorAuthentication')
+    @patch.object(pjlink_test, 'change_status')
+    @patch.object(pjlink_test, 'disconnect_from_host')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_process_command_erra(self, mock_log, mock_disconnect, mock_change_status, mock_err_authenticate):
+        """
+        Test ERRA - Authentication Error
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        log_text = '(127.0.0.1) Projector returned error "ERRA"'
+        mock_change_status.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: process_command called with ERRA
+        pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_AUTHENTICATION])
+
+        # THEN: Error should be logged and several methods should be called
+        self.assertTrue(mock_disconnect.called, 'disconnect_from_host should have been called')
+        mock_change_status.assert_called_once_with(E_AUTHENTICATION)
+        mock_log.error.assert_called_with(log_text)
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_process_command_future(self, mock_log):
+        """
+        Test command valid but no method to process yet
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        log_text = "(127.0.0.1) Unable to process command='CLSS' (Future option)"
+        mock_log.reset_mock()
+        # Remove a valid command so we can test valid command but not available yet
+        pjlink.pjlink_functions.pop('CLSS')
+
+        # WHEN: process_command called with an unknown command
+        with patch.object(pjlink, 'pjlink_functions') as mock_functions:
+            pjlink.process_command(cmd='CLSS', data='DONT CARE')
+
+        # THEN: Error should be logged and no command called
+        self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
+        mock_log.warning.assert_called_once_with(log_text)
+
+    @patch.object(pjlink_test, 'pjlink_functions')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_process_command_invalid(self, mock_log, mock_functions):
+        """
+        Test not a valid command
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_functions.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: process_command called with an unknown command
+        pjlink.process_command(cmd='Unknown', data='Dont Care')
+        log_text = "(127.0.0.1) Ignoring command='Unknown' (Invalid/Unknown)"
+
+        # THEN: Error should be logged and no command called
+        self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
+        mock_log.error.assert_called_once_with(log_text)
+
+    @patch.object(pjlink_test, 'pjlink_functions')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_process_command_ok(self, mock_log, mock_functions):
+        """
+        Test command returned success
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_functions.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: process_command called with an unknown command
+        pjlink.process_command(cmd='CLSS', data='OK')
+        log_text = '(127.0.0.1) Command "CLSS" returned OK'
+
+        # THEN: Error should be logged and no command called
+        self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
+        self.assertEqual(mock_log.debug.call_count, 2, 'log.debug() should have been called twice')
+        # Although we called it twice, only the last log entry is saved
+        mock_log.debug.assert_called_with(log_text)

=== added file 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py'
--- tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py	2017-11-10 12:12:52 +0000
@@ -0,0 +1,1148 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2015 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+Package to test the openlp.core.projectors.pjlink commands package.
+"""
+from unittest import TestCase
+from unittest.mock import patch
+
+import openlp.core.projectors.pjlink
+from openlp.core.projectors import PJLink, Projector
+from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
+    PJLINK_POWR_STATUS, \
+    E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \
+    S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY
+
+from tests.resources.projector.data import TEST_PIN, TEST1_DATA
+
+pjlink_test = PJLink(Projector(**TEST1_DATA), pin=TEST_PIN, no_poll=True)
+pjlink_test.ip = '127.0.0.1'
+
+# Create a list of ERST positional data so we don't have to redo the same buildup multiple times
+PJLINK_ERST_POSITIONS = []
+for pos in range(0, len(PJLINK_ERST_DATA)):
+    if pos in PJLINK_ERST_DATA:
+        PJLINK_ERST_POSITIONS.append(PJLINK_ERST_DATA[pos])
+
+
+class TestPJLinkCommands(TestCase):
+    """
+    Tests for the PJLink module
+    """
+    @patch.object(pjlink_test, 'changeStatus')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_change_status_connection_error(self, mock_log, mock_change_status):
+        """
+        Test change_status with connection error
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.projector_status = 0
+        pjlink.status_connect = 0
+        test_code = E_UNKNOWN_SOCKET_ERROR
+        mock_change_status.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: change_status called with unknown socket error
+        pjlink.change_status(status=test_code, msg=None)
+
+        # THEN: Proper settings should change and signals sent
+        self.assertEqual(pjlink.projector_status, E_NOT_CONNECTED, 'Projector status should be NOT CONNECTED')
+        self.assertEqual(pjlink.status_connect, E_NOT_CONNECTED, 'Status connect should be NOT CONNECTED')
+        mock_change_status.emit.assert_called_once_with(pjlink.ip, E_UNKNOWN_SOCKET_ERROR,
+                                                        'An unidentified error occurred')
+        self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
+
+    @patch.object(pjlink_test, 'changeStatus')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_change_status_connection_status_connecting(self, mock_log, mock_change_status):
+        """
+        Test change_status with connection status
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.projector_status = 0
+        pjlink.status_connect = 0
+        test_code = S_CONNECTING
+        mock_change_status.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: change_status called with unknown socket error
+        pjlink.change_status(status=test_code, msg=None)
+
+        # THEN: Proper settings should change and signals sent
+        self.assertEqual(pjlink.projector_status, S_NOT_CONNECTED, 'Projector status should be NOT CONNECTED')
+        self.assertEqual(pjlink.status_connect, S_CONNECTING, 'Status connect should be CONNECTING')
+        mock_change_status.emit.assert_called_once_with(pjlink.ip, S_CONNECTING, 'Connecting')
+        self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
+
+    @patch.object(pjlink_test, 'changeStatus')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_change_status_connection_status_connected(self, mock_log, mock_change_status):
+        """
+        Test change_status with connection status
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.projector_status = 0
+        pjlink.status_connect = 0
+        test_code = S_ON
+        mock_change_status.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: change_status called with unknown socket error
+        pjlink.change_status(status=test_code, msg=None)
+
+        # THEN: Proper settings should change and signals sent
+        self.assertEqual(pjlink.projector_status, S_ON, 'Projector status should be ON')
+        self.assertEqual(pjlink.status_connect, S_CONNECTED, 'Status connect should be CONNECTED')
+        mock_change_status.emit.assert_called_once_with(pjlink.ip, S_ON, 'Power is on')
+        self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
+
+    @patch.object(pjlink_test, 'changeStatus')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_change_status_connection_status_with_message(self, mock_log, mock_change_status):
+        """
+        Test change_status with connection status
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.projector_status = 0
+        pjlink.status_connect = 0
+        test_message = 'Different Status Message than default'
+        test_code = S_ON
+        mock_change_status.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: change_status called with unknown socket error
+        pjlink.change_status(status=test_code, msg=test_message)
+
+        # THEN: Proper settings should change and signals sent
+        self.assertEqual(pjlink.projector_status, S_ON, 'Projector status should be ON')
+        self.assertEqual(pjlink.status_connect, S_CONNECTED, 'Status connect should be CONNECTED')
+        mock_change_status.emit.assert_called_once_with(pjlink.ip, S_ON, test_message)
+        self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_av_mute_status(self, mock_log, mock_send_command):
+        """
+        Test sending command to retrieve shutter/audio state
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'AVMT'
+        test_log = '(127.0.0.1) Sending AVMT command'
+
+        # WHEN: get_av_mute_status is called
+        pjlink.get_av_mute_status()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_available_inputs(self, mock_log, mock_send_command):
+        """
+        Test sending command to retrieve avaliable inputs
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'INST'
+        test_log = '(127.0.0.1) Sending INST command'
+
+        # WHEN: get_available_inputs is called
+        pjlink.get_available_inputs()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_error_status(self, mock_log, mock_send_command):
+        """
+        Test sending command to retrieve projector error status
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'ERST'
+        test_log = '(127.0.0.1) Sending ERST command'
+
+        # WHEN: get_error_status is called
+        pjlink.get_error_status()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_input_source(self, mock_log, mock_send_command):
+        """
+        Test sending command to retrieve current input
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'INPT'
+        test_log = '(127.0.0.1) Sending INPT command'
+
+        # WHEN: get_input_source is called
+        pjlink.get_input_source()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_lamp_status(self, mock_log, mock_send_command):
+        """
+        Test sending command to retrieve lamp(s) status
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'LAMP'
+        test_log = '(127.0.0.1) Sending LAMP command'
+
+        # WHEN: get_lamp_status is called
+        pjlink.get_lamp_status()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_manufacturer(self, mock_log, mock_send_command):
+        """
+        Test sending command to retrieve manufacturer name
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'INF1'
+        test_log = '(127.0.0.1) Sending INF1 command'
+
+        # WHEN: get_manufacturer is called
+        pjlink.get_manufacturer()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_model(self, mock_log, mock_send_command):
+        """
+        Test sending command to get model information
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'INF2'
+        test_log = '(127.0.0.1) Sending INF2 command'
+
+        # WHEN: get_model is called
+        pjlink.get_model()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_name(self, mock_log, mock_send_command):
+        """
+        Test sending command to get user-assigned name
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'NAME'
+        test_log = '(127.0.0.1) Sending NAME command'
+
+        # WHEN: get_name is called
+        pjlink.get_name()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_other_info(self, mock_log, mock_send_command):
+        """
+        Test sending command to retrieve other information
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'INFO'
+        test_log = '(127.0.0.1) Sending INFO command'
+
+        # WHEN: get_other_info is called
+        pjlink.get_other_info()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_get_power_status(self, mock_log, mock_send_command):
+        """
+        Test sending command to retrieve current power state
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        mock_log.reset_mock()
+        mock_send_command.reset_mock()
+        test_data = 'POWR'
+        test_log = '(127.0.0.1) Sending POWR command'
+
+        # WHEN: get_power_status called
+        pjlink.get_power_status()
+
+        # THEN: log data and send_command should have been called
+        mock_log.debug.assert_called_once_with(test_log)
+        mock_send_command.assert_called_once_with(cmd=test_data)
+
+    def test_projector_get_status_error(self):
+        """
+        Test to check returned information for error code
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        test_string = 'E_SOCKET_ADDRESS_NOT_AVAILABLE'
+        test_message = 'The address specified to socket.bind() does not belong to the host'
+
+        # WHEN: get_status called
+        string, message = pjlink._get_status(status=E_SOCKET_ADDRESS_NOT_AVAILABLE)
+
+        # THEN: Proper strings should have been returned
+        self.assertEqual(string, test_string, 'Code as string should have been returned')
+        self.assertEqual(message, test_message, 'Description of code should have been returned')
+
+    def test_projector_get_status_invalid(self):
+        """
+        Test to check returned information for error code
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        test_string = 'Test string since get_status will only work with int'
+        test_message = 'Invalid status code'
+
+        # WHEN: get_status called
+        string, message = pjlink._get_status(status=test_string)
+
+        # THEN: Proper strings should have been returned
+        self.assertEqual(string, -1, 'Should have returned -1 as a bad status check')
+        self.assertEqual(message, test_message, 'Error message should have been returned')
+
+    def test_projector_get_status_status(self):
+        """
+        Test to check returned information for status codes
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        test_string = 'S_NOT_CONNECTED'
+        test_message = 'Not connected'
+
+        # WHEN: get_status called
+        string, message = pjlink._get_status(status=S_NOT_CONNECTED)
+
+        # THEN: Proper strings should have been returned
+        self.assertEqual(string, test_string, 'Code as string should have been returned')
+        self.assertEqual(message, test_message, 'Description of code should have been returned')
+
+    def test_projector_get_status_unknown(self):
+        """
+        Test to check returned information for unknown code
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        test_string = 999999
+        test_message = 'Unknown status'
+
+        # WHEN: get_status called
+        string, message = pjlink._get_status(status=test_string)
+
+        # THEN: Proper strings should have been returned
+        self.assertEqual(string, test_string, 'Received code should have been returned')
+        self.assertEqual(message, test_message, 'Unknown status string should have been returned')
+
+    def test_projector_process_inf1(self):
+        """
+        Test saving INF1 data (manufacturer)
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.manufacturer = None
+        test_data = 'TEst INformation MultiCase'
+
+        # WHEN: process_inf called with test data
+        pjlink.process_inf1(data=test_data)
+
+        # THEN: Data should be saved
+        self.assertEqual(pjlink.manufacturer, test_data, 'Test data should have been saved')
+
+    def test_projector_process_inf2(self):
+        """
+        Test saving INF2 data (model)
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.model = None
+        test_data = 'TEst moDEl MultiCase'
+
+        # WHEN: process_inf called with test data
+        pjlink.process_inf2(data=test_data)
+
+        # THEN: Data should be saved
+        self.assertEqual(pjlink.model, test_data, 'Test data should have been saved')
+
+    def test_projector_process_info(self):
+        """
+        Test saving INFO data (other information)
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.other_info = None
+        test_data = 'TEst ExtrANEous MultiCase INformatoin that MFGR might Set'
+
+        # WHEN: process_inf called with test data
+        pjlink.process_info(data=test_data)
+
+        # THEN: Data should be saved
+        self.assertEqual(pjlink.other_info, test_data, 'Test data should have been saved')
+
+    @patch.object(pjlink_test, 'projectorUpdateIcons')
+    def test_projector_process_avmt_bad_data(self, mock_UpdateIcons):
+        """
+        Test avmt bad data fail
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.shutter = True
+        pjlink.mute = True
+
+        # WHEN: Called with an invalid setting
+        pjlink.process_avmt('36')
+
+        # THEN: Shutter should be closed and mute should be True
+        self.assertTrue(pjlink.shutter, 'Shutter should changed')
+        self.assertTrue(pjlink.mute, 'Audio should not have changed')
+        self.assertFalse(mock_UpdateIcons.emit.called, 'Update icons should NOT have been called')
+
+    @patch.object(pjlink_test, 'projectorUpdateIcons')
+    def test_projector_process_avmt_closed_muted(self, mock_UpdateIcons):
+        """
+        Test avmt status shutter closed and mute off
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.shutter = False
+        pjlink.mute = False
+
+        # WHEN: Called with setting shutter to closed and mute on
+        pjlink.process_avmt('31')
+
+        # THEN: Shutter should be closed and mute should be True
+        self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
+        self.assertTrue(pjlink.mute, 'Audio should be muted')
+        self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
+
+    @patch.object(pjlink_test, 'projectorUpdateIcons')
+    def test_projector_process_avmt_shutter_closed(self, mock_UpdateIcons):
+        """
+        Test avmt status shutter closed and audio unchanged
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.shutter = False
+        pjlink.mute = True
+
+        # WHEN: Called with setting shutter closed and mute off
+        pjlink.process_avmt('11')
+
+        # THEN: Shutter should be True and mute should be False
+        self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
+        self.assertTrue(pjlink.mute, 'Audio should not have changed')
+        self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
+
+    @patch.object(pjlink_test, 'projectorUpdateIcons')
+    def test_projector_process_avmt_audio_muted(self, mock_UpdateIcons):
+        """
+        Test avmt status shutter unchanged and mute on
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.shutter = True
+        pjlink.mute = False
+
+        # WHEN: Called with setting shutter closed and mute on
+        pjlink.process_avmt('21')
+
+        # THEN: Shutter should be closed and mute should be True
+        self.assertTrue(pjlink.shutter, 'Shutter should not have changed')
+        self.assertTrue(pjlink.mute, 'Audio should be off')
+        self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
+
+    @patch.object(pjlink_test, 'projectorUpdateIcons')
+    def test_projector_process_avmt_open_unmuted(self, mock_UpdateIcons):
+        """
+        Test avmt status shutter open and mute off
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.shutter = True
+        pjlink.mute = True
+
+        # WHEN: Called with setting shutter to closed and mute on
+        pjlink.process_avmt('30')
+
+        # THEN: Shutter should be closed and mute should be True
+        self.assertFalse(pjlink.shutter, 'Shutter should have been set to open')
+        self.assertFalse(pjlink.mute, 'Audio should be on')
+        self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
+
+    def test_projector_process_clss_one(self):
+        """
+        Test class 1 sent from projector
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+
+        # WHEN: Process class response
+        pjlink.process_clss('1')
+
+        # THEN: Projector class should be set to 1
+        self.assertEqual(pjlink.pjlink_class, '1',
+                         'Projector should have set class=1')
+
+    def test_projector_process_clss_two(self):
+        """
+        Test class 2 sent from projector
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+
+        # WHEN: Process class response
+        pjlink.process_clss('2')
+
+        # THEN: Projector class should be set to 1
+        self.assertEqual(pjlink.pjlink_class, '2',
+                         'Projector should have set class=2')
+
+    def test_projector_process_clss_nonstandard_reply_optoma(self):
+        """
+        Bugfix 1550891: CLSS request returns non-standard reply with Optoma projector
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+
+        # WHEN: Process non-standard reply
+        pjlink.process_clss('Class 1')
+
+        # THEN: Projector class should be set with proper value
+        self.assertEqual(pjlink.pjlink_class, '1',
+                         'Non-standard class reply should have set class=1')
+
+    def test_projector_process_clss_nonstandard_reply_benq(self):
+        """
+        Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+
+        # WHEN: Process non-standard reply
+        pjlink.process_clss('Version2')
+
+        # THEN: Projector class should be set with proper value
+        # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify
+        self.assertEqual(pjlink.pjlink_class, '2',
+                         'Non-standard class reply should have set class=2')
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_clss_invalid_nan(self, mock_log):
+        """
+        Test CLSS reply has no class number
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+
+        # WHEN: Process invalid reply
+        pjlink.process_clss('Z')
+        log_text = "(127.0.0.1) NAN clss version reply 'Z' - defaulting to class '1'"
+
+        # THEN: Projector class should be set with default value
+        self.assertEqual(pjlink.pjlink_class, '1',
+                         'Non-standard class reply should have set class=1')
+        mock_log.error.assert_called_once_with(log_text)
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_clss_invalid_no_version(self, mock_log):
+        """
+        Test CLSS reply has no class number
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+
+        # WHEN: Process invalid reply
+        pjlink.process_clss('Invalid')
+        log_text = "(127.0.0.1) No numbers found in class version reply 'Invalid' - defaulting to class '1'"
+
+        # THEN: Projector class should be set with default value
+        self.assertEqual(pjlink.pjlink_class, '1',
+                         'Non-standard class reply should have set class=1')
+        mock_log.error.assert_called_once_with(log_text)
+
+    def test_projector_process_erst_all_ok(self):
+        """
+        Test test_projector_process_erst_all_ok
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        chk_test = PJLINK_ERST_STATUS['OK']
+        chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
+
+        # WHEN: process_erst with no errors
+        pjlink.process_erst(chk_param)
+
+        # THEN: PJLink instance errors should be None
+        self.assertIsNone(pjlink.projector_errors, 'projector_errors should have been set to None')
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_erst_data_invalid_length(self, mock_log):
+        """
+        Test test_projector_process_erst_data_invalid_length
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.projector_errors = None
+        log_text = "127.0.0.1) Invalid error status response '11111111': length != 6"
+
+        # WHEN: process_erst called with invalid data (too many values
+        pjlink.process_erst('11111111')
+
+        # THEN: pjlink.projector_errors should be empty and warning logged
+        self.assertIsNone(pjlink.projector_errors, 'There should be no errors')
+        self.assertTrue(mock_log.warning.called, 'Warning should have been logged')
+        mock_log.warning.assert_called_once_with(log_text)
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_erst_data_invalid_nan(self, mock_log):
+        """
+        Test test_projector_process_erst_data_invalid_nan
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.projector_errors = None
+        log_text = "(127.0.0.1) Invalid error status response '1111Z1'"
+
+        # WHEN: process_erst called with invalid data (too many values
+        pjlink.process_erst('1111Z1')
+
+        # THEN: pjlink.projector_errors should be empty and warning logged
+        self.assertIsNone(pjlink.projector_errors, 'There should be no errors')
+        self.assertTrue(mock_log.warning.called, 'Warning should have been logged')
+        mock_log.warning.assert_called_once_with(log_text)
+
+    def test_projector_process_erst_all_warn(self):
+        """
+        Test test_projector_process_erst_all_warn
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        chk_test = PJLINK_ERST_STATUS[E_WARN]
+        chk_string = ERROR_STRING[E_WARN]
+        chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
+
+        # WHEN: process_erst with status set to WARN
+        pjlink.process_erst(chk_param)
+
+        # THEN: PJLink instance errors should match chk_value
+        for chk in pjlink.projector_errors:
+            self.assertEqual(pjlink.projector_errors[chk], chk_string,
+                             "projector_errors['{chk}'] should have been set to {err}".format(chk=chk,
+                                                                                              err=chk_string))
+
+    def test_projector_process_erst_all_error(self):
+        """
+        Test test_projector_process_erst_all_error
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        chk_test = PJLINK_ERST_STATUS[E_ERROR]
+        chk_string = ERROR_STRING[E_ERROR]
+        chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
+
+        # WHEN: process_erst with status set to WARN
+        pjlink.process_erst(chk_param)
+
+        # THEN: PJLink instance errors should match chk_value
+        for chk in pjlink.projector_errors:
+            self.assertEqual(pjlink.projector_errors[chk], chk_string,
+                             "projector_errors['{chk}'] should have been set to {err}".format(chk=chk,
+                                                                                              err=chk_string))
+
+    def test_projector_process_erst_warn_cover_only(self):
+        """
+        Test test_projector_process_erst_warn_cover_only
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        chk_test = PJLINK_ERST_STATUS[E_WARN]
+        chk_string = ERROR_STRING[E_WARN]
+        pos = PJLINK_ERST_DATA['COVER']
+        build_chk = []
+        for check in range(0, len(PJLINK_ERST_POSITIONS)):
+            if check == pos:
+                build_chk.append(chk_test)
+            else:
+                build_chk.append(PJLINK_ERST_STATUS['OK'])
+        chk_param = ''.join(build_chk)
+
+        # WHEN: process_erst with cover only set to WARN and all others set to OK
+        pjlink.process_erst(chk_param)
+
+        # THEN: Only COVER should have an error
+        self.assertEqual(len(pjlink.projector_errors), 1, 'projector_errors should only have 1 error')
+        self.assertTrue(('Cover' in pjlink.projector_errors), 'projector_errors should have an error for "Cover"')
+        self.assertEqual(pjlink.projector_errors['Cover'],
+                         chk_string,
+                         'projector_errors["Cover"] should have error "{err}"'.format(err=chk_string))
+
+    def test_projector_process_inpt(self):
+        """
+        Test input source status shows current input
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.source = '0'
+
+        # WHEN: Called with input source
+        pjlink.process_inpt('1')
+
+        # THEN: Input selected should reflect current input
+        self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"')
+
+    @patch.object(pjlink_test, 'projectorUpdateIcons')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_inst(self, mock_log, mock_UpdateIcons):
+        """
+        Test saving video source available information
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.source_available = []
+        test_data = '21 10 30 31 11 20'
+        test_saved = ['10', '11', '20', '21', '30', '31']
+        log_data = '(127.0.0.1) Setting projector sources_available to ' \
+            '"[\'10\', \'11\', \'20\', \'21\', \'30\', \'31\']"'
+        mock_UpdateIcons.reset_mock()
+        mock_log.reset_mock()
+
+        # WHEN: process_inst called with test data
+        pjlink.process_inst(data=test_data)
+
+        # THEN: Data should have been sorted and saved properly
+        self.assertEqual(pjlink.source_available, test_saved, "Sources should have been sorted and saved")
+        mock_log.debug.assert_called_once_with(log_data)
+        self.assertTrue(mock_UpdateIcons.emit.called, 'Update Icons should have been called')
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_lamp_invalid(self, mock_log):
+        """
+        Test status multiple lamp on/off and hours
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.lamp = [{'Hours': 00000, 'On': True},
+                       {'Hours': 11111, 'On': False}]
+        log_data = '(127.0.0.1) process_lamp(): Invalid data "11111 1 22222 0 333A3 1"'
+
+        # WHEN: Call process_command with invalid lamp data
+        pjlink.process_lamp('11111 1 22222 0 333A3 1')
+
+        # THEN: lamps should not have changed
+        self.assertEqual(len(pjlink.lamp), 2,
+                         'Projector should have kept 2 lamps specified')
+        self.assertEqual(pjlink.lamp[0]['On'], True,
+                         'Lamp 1 power status should have been set to TRUE')
+        self.assertEqual(pjlink.lamp[0]['Hours'], 00000,
+                         'Lamp 1 hours should have been left at 00000')
+        self.assertEqual(pjlink.lamp[1]['On'], False,
+                         'Lamp 2 power status should have been set to FALSE')
+        self.assertEqual(pjlink.lamp[1]['Hours'], 11111,
+                         'Lamp 2 hours should have been left at 11111')
+        mock_log.warning.assert_called_once_with(log_data)
+
+    def test_projector_process_lamp_multiple(self):
+        """
+        Test status multiple lamp on/off and hours
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.lamps = []
+
+        # WHEN: Call process_command with lamp data
+        pjlink.process_lamp('11111 1 22222 0 33333 1')
+
+        # THEN: Lamp should have been set with proper lamp status
+        self.assertEqual(len(pjlink.lamp), 3,
+                         'Projector should have 3 lamps specified')
+        self.assertEqual(pjlink.lamp[0]['On'], True,
+                         'Lamp 1 power status should have been set to TRUE')
+        self.assertEqual(pjlink.lamp[0]['Hours'], 11111,
+                         'Lamp 1 hours should have been set to 11111')
+        self.assertEqual(pjlink.lamp[1]['On'], False,
+                         'Lamp 2 power status should have been set to FALSE')
+        self.assertEqual(pjlink.lamp[1]['Hours'], 22222,
+                         'Lamp 2 hours should have been set to 22222')
+        self.assertEqual(pjlink.lamp[2]['On'], True,
+                         'Lamp 3 power status should have been set to TRUE')
+        self.assertEqual(pjlink.lamp[2]['Hours'], 33333,
+                         'Lamp 3 hours should have been set to 33333')
+
+    def test_projector_process_lamp_single(self):
+        """
+        Test status lamp on/off and hours
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.lamps = []
+
+        # WHEN: Call process_command with lamp data
+        pjlink.process_lamp('22222 1')
+
+        # THEN: Lamp should have been set with status=ON and hours=22222
+        self.assertEqual(pjlink.lamp[0]['On'], True,
+                         'Lamp power status should have been set to TRUE')
+        self.assertEqual(pjlink.lamp[0]['Hours'], 22222,
+                         'Lamp hours should have been set to 22222')
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_name(self, mock_log):
+        """
+        Test saving NAME data from projector
+        """
+        # GIVEN: Test data
+        pjlink = pjlink_test
+        test_data = "Some Name the End-User Set IN Projector"
+        test_log = '(127.0.0.1) Setting projector PJLink name to "Some Name the End-User Set IN Projector"'
+        mock_log.reset_mock()
+
+        # WHEN: process_name called with test data
+        pjlink.process_name(data=test_data)
+
+        # THEN: name should be set and logged
+        self.assertEqual(pjlink.pjlink_name, test_data, 'Name test data should have been saved')
+        mock_log.debug.assert_called_once_with(test_log)
+
+    @patch.object(pjlink_test, 'projectorUpdateIcons')
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(pjlink_test, 'change_status')
+    def test_projector_process_powr_on(self,
+                                       mock_change_status,
+                                       mock_send_command,
+                                       mock_UpdateIcons):
+        """
+        Test status power to ON
+        """
+        # GIVEN: Test object and preset
+        pjlink = pjlink_test
+        pjlink.power = S_STANDBY
+        test_data = PJLINK_POWR_STATUS[S_ON]
+
+        # WHEN: Call process_command with turn power on command
+        pjlink.process_command(cmd='POWR', data=test_data)
+
+        # THEN: Power should be set to ON
+        self.assertEqual(pjlink.power, S_ON, 'Power should have been set to ON')
+        mock_send_command.assert_called_once_with('INST')
+        mock_change_status.assert_called_once_with(PJLINK_POWR_STATUS[test_data])
+        self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
+
+    @patch.object(pjlink_test, 'projectorUpdateIcons')
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(pjlink_test, 'change_status')
+    def test_projector_process_powr_invalid(self,
+                                            mock_change_status,
+                                            mock_send_command,
+                                            mock_UpdateIcons):
+        """
+        Test process_powr invalid call
+        """
+        # GIVEN: Test object and preset
+        pjlink = pjlink_test
+        pjlink.power = S_STANDBY
+        test_data = '99'
+
+        # WHEN: Call process_command with turn power on command
+        pjlink.process_command(cmd='POWR', data=test_data)
+
+        # THEN: Power should be set to ON
+        self.assertEqual(pjlink.power, S_STANDBY, 'Power should not have changed')
+        self.assertFalse(mock_change_status.called, 'Change status should not have been called')
+        self.assertFalse(mock_send_command.called, 'send_command("INST") should not have been called')
+        self.assertFalse(mock_UpdateIcons.emit.called, 'projectorUpdateIcons should not have been called')
+
+    @patch.object(pjlink_test, 'projectorUpdateIcons')
+    @patch.object(pjlink_test, 'send_command')
+    @patch.object(pjlink_test, 'change_status')
+    def test_projector_process_powr_off(self,
+                                        mock_change_status,
+                                        mock_send_command,
+                                        mock_UpdateIcons):
+        """
+        Test status power to STANDBY
+        """
+        # GIVEN: Test object and preset
+        pjlink = pjlink_test
+        pjlink.power = S_ON
+        test_data = PJLINK_POWR_STATUS[S_STANDBY]
+
+        # WHEN: Call process_command with turn power on command
+        pjlink.process_command(cmd='POWR', data=test_data)
+
+        # THEN: Power should be set to STANDBY
+        self.assertEqual(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
+        self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
+        mock_change_status.assert_called_once_with(PJLINK_POWR_STATUS[test_data])
+        self.assertFalse(mock_send_command.called, "send_command['INST'] should not have been called")
+
+    def test_projector_process_rfil_save(self):
+        """
+        Test saving filter type
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.model_filter = None
+        filter_model = 'Filter Type Test'
+
+        # WHEN: Filter model is received
+        pjlink.process_rfil(data=filter_model)
+
+        # THEN: Filter model number should be saved
+        self.assertEqual(pjlink.model_filter, filter_model, 'Filter type should have been saved')
+
+    def test_projector_process_rfil_nosave(self):
+        """
+        Test saving filter type previously saved
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.model_filter = 'Old filter type'
+        filter_model = 'Filter Type Test'
+
+        # WHEN: Filter model is received
+        pjlink.process_rfil(data=filter_model)
+
+        # THEN: Filter model number should be saved
+        self.assertNotEquals(pjlink.model_filter, filter_model, 'Filter type should NOT have been saved')
+
+    def test_projector_process_rlmp_save(self):
+        """
+        Test saving lamp type
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.model_lamp = None
+        lamp_model = 'Lamp Type Test'
+
+        # WHEN: Filter model is received
+        pjlink.process_rlmp(data=lamp_model)
+
+        # THEN: Filter model number should be saved
+        self.assertEqual(pjlink.model_lamp, lamp_model, 'Lamp type should have been saved')
+
+    def test_projector_process_rlmp_nosave(self):
+        """
+        Test saving lamp type previously saved
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.model_lamp = 'Old lamp type'
+        lamp_model = 'Filter Type Test'
+
+        # WHEN: Filter model is received
+        pjlink.process_rlmp(data=lamp_model)
+
+        # THEN: Filter model number should be saved
+        self.assertNotEquals(pjlink.model_lamp, lamp_model, 'Lamp type should NOT have been saved')
+
+    def test_projector_process_snum_set(self):
+        """
+        Test saving serial number from projector
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.serial_no = None
+        test_number = 'Test Serial Number'
+
+        # WHEN: No serial number is set and we receive serial number command
+        pjlink.process_snum(data=test_number)
+
+        # THEN: Serial number should be set
+        self.assertEqual(pjlink.serial_no, test_number,
+                         'Projector serial number should have been set')
+
+    def test_projector_process_snum_different(self):
+        """
+        Test projector serial number different than saved serial number
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.serial_no = 'Previous serial number'
+        test_number = 'Test Serial Number'
+
+        # WHEN: No serial number is set and we receive serial number command
+        pjlink.process_snum(data=test_number)
+
+        # THEN: Serial number should be set
+        self.assertNotEquals(pjlink.serial_no, test_number,
+                             'Projector serial number should NOT have been set')
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_sver(self, mock_log):
+        """
+        Test invalid software version information - too long
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.sw_version = None
+        pjlink.sw_version_received = None
+        test_data = 'Test 1 Subtest 1'
+        test_log = "(127.0.0.1) Setting projector software version to 'Test 1 Subtest 1'"
+        mock_log.reset_mock()
+
+        # WHEN: process_sver called with invalid data
+        pjlink.process_sver(data=test_data)
+
+        # THEN: Version information should not change
+        self.assertEqual(pjlink.sw_version, test_data, 'Software version should have been updated')
+        self.assertIsNone(pjlink.sw_version_received, 'Received software version should not have changed')
+        mock_log.debug.assert_called_once_with(test_log)
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_sver_changed(self, mock_log):
+        """
+        Test invalid software version information - Received different than saved
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        test_data_new = 'Test 1 Subtest 2'
+        test_data_old = 'Test 1 Subtest 1'
+        pjlink.sw_version = test_data_old
+        pjlink.sw_version_received = None
+        test_log = '(127.0.0.1) Saving new serial number as sw_version_received'
+        mock_log.reset_mock()
+
+        # WHEN: process_sver called with invalid data
+        pjlink.process_sver(data=test_data_new)
+
+        # THEN: Version information should not change
+        self.assertEqual(pjlink.sw_version, test_data_old, 'Software version should not have been updated')
+        self.assertEqual(pjlink.sw_version_received, test_data_new,
+                         'Received software version should have been changed')
+        self.assertEqual(mock_log.warning.call_count, 4, 'log.warn should have been called 4 times')
+        # There was 4 calls, but only the last one is checked with this method
+        mock_log.warning.assert_called_with(test_log)
+
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_projector_process_sver_invalid(self, mock_log):
+        """
+        Test invalid software version information - too long
+        """
+        # GIVEN: Test object
+        pjlink = pjlink_test
+        pjlink.sw_version = None
+        pjlink.sw_version_received = None
+        test_data = 'This is a test software version line that is too long based on PJLink version 2 specs'
+        test_log = "Invalid software version - too long"
+        mock_log.reset_mock()
+
+        # WHEN: process_sver called with invalid data
+        pjlink.process_sver(data=test_data)
+
+        # THEN: Version information should not change
+        self.assertIsNone(pjlink.sw_version, 'Software version should not have changed')
+        self.assertIsNone(pjlink.sw_version_received, 'Received software version should not have changed')
+        mock_log.warning.assert_called_once_with(test_log)
+
+    def test_projector_reset_information(self):
+        """
+        Test reset_information() resets all information and stops timers
+        """
+        # GIVEN: Test object and test data
+        pjlink = pjlink_test
+        pjlink.power = S_ON
+        pjlink.pjlink_name = 'OPENLPTEST'
+        pjlink.manufacturer = 'PJLINK'
+        pjlink.model = '1'
+        pjlink.shutter = True
+        pjlink.mute = True
+        pjlink.lamp = True
+        pjlink.fan = True
+        pjlink.source_available = True
+        pjlink.other_info = 'ANOTHER TEST'
+        pjlink.send_queue = True
+        pjlink.send_busy = True
+
+        # WHEN: reset_information() is called
+        with patch.object(pjlink, 'timer') as mock_timer:
+            with patch.object(pjlink, 'socket_timer') as mock_socket_timer:
+                pjlink.reset_information()
+
+        # THEN: All information should be reset and timers stopped
+        self.assertEqual(pjlink.power, S_OFF, 'Projector power should be OFF')
+        self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None')
+        self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None')
+        self.assertIsNone(pjlink.model, 'Projector model should be None')
+        self.assertIsNone(pjlink.shutter, 'Projector shutter should be None')
+        self.assertIsNone(pjlink.mute, 'Projector shuttter should be None')
+        self.assertIsNone(pjlink.lamp, 'Projector lamp should be None')
+        self.assertIsNone(pjlink.fan, 'Projector fan should be None')
+        self.assertIsNone(pjlink.source_available, 'Projector source_available should be None')
+        self.assertIsNone(pjlink.source, 'Projector source should be None')
+        self.assertIsNone(pjlink.other_info, 'Projector other_info should be None')
+        self.assertEqual(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
+        self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False')
+        self.assertTrue(mock_timer.stop.called, 'Projector timer.stop()  should have been called')
+        self.assertTrue(mock_socket_timer.stop.called, 'Projector socket_timer.stop() should have been called')

=== modified file 'tests/interfaces/openlp_core/ui/test_projectoreditform.py'
--- tests/interfaces/openlp_core/ui/test_projectoreditform.py	2017-10-07 07:05:07 +0000
+++ tests/interfaces/openlp_core/ui/test_projectoreditform.py	2017-11-10 12:12:52 +0000
@@ -20,7 +20,7 @@
 # Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
 ###############################################################################
 """
-Interface tests to test the openlp.core.ui.projector.editform.ProjectorEditForm()
+Interface tests to test the openlp.core.projectors.editform.ProjectorEditForm()
 class and methods.
 """
 import os
@@ -28,8 +28,7 @@
 from unittest.mock import patch
 
 from openlp.core.common.registry import Registry
-from openlp.core.lib.projector.db import Projector, ProjectorDB
-from openlp.core.ui import ProjectorEditForm
+from openlp.core.projectors import Projector, ProjectorDB, ProjectorEditForm, ProjectorManager
 
 from tests.helpers.testmixin import TestMixin
 from tests.resources.projector.data import TEST_DB, TEST1_DATA, TEST2_DATA
@@ -48,7 +47,7 @@
         self.setup_application()
         self.build_settings()
         Registry.create()
-        with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
+        with patch('openlp.core.projectors.db.init_url') as mocked_init_url:
             if os.path.exists(TEST_DB):
                 os.unlink(TEST_DB)
             mocked_init_url.return_value = 'sqlite:///' + TEST_DB
@@ -66,7 +65,7 @@
         del self.projector_form
         self.destroy_settings()
 
-    @patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec')
+    @patch('openlp.core.projectors.editform.QtWidgets.QDialog.exec')
     def test_edit_form_add_projector(self, mocked_exec):
         """
         Test projector edit form with no parameters creates a new entry.
@@ -84,7 +83,7 @@
         self.assertTrue((item.ip is None and item.name is None),
                         'Projector edit form should have a new Projector() instance to edit')
 
-    @patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec')
+    @patch('openlp.core.projectors.editform.QtWidgets.QDialog.exec')
     def test_edit_form_edit_projector(self, mocked_exec):
         """
         Test projector edit form with existing projector entry

=== modified file 'tests/interfaces/openlp_core/ui/test_projectormanager.py'
--- tests/interfaces/openlp_core/ui/test_projectormanager.py	2017-10-07 07:05:07 +0000
+++ tests/interfaces/openlp_core/ui/test_projectormanager.py	2017-11-10 12:12:52 +0000
@@ -27,8 +27,7 @@
 from unittest.mock import patch, MagicMock
 
 from openlp.core.common.registry import Registry
-from openlp.core.ui import ProjectorManager, ProjectorEditForm
-from openlp.core.lib.projector.db import ProjectorDB
+from openlp.core.projectors import ProjectorDB, ProjectorEditForm, ProjectorManager
 
 from tests.helpers.testmixin import TestMixin
 from tests.resources.projector.data import TEST_DB
@@ -45,7 +44,7 @@
         self.build_settings()
         self.setup_application()
         Registry.create()
-        with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
+        with patch('openlp.core.projectors.db.init_url') as mocked_init_url:
             if os.path.exists(TEST_DB):
                 os.unlink(TEST_DB)
             mocked_init_url.return_value = 'sqlite:///%s' % TEST_DB

=== modified file 'tests/interfaces/openlp_core/ui/test_projectorsourceform.py'
--- tests/interfaces/openlp_core/ui/test_projectorsourceform.py	2017-10-07 07:05:07 +0000
+++ tests/interfaces/openlp_core/ui/test_projectorsourceform.py	2017-11-10 12:12:52 +0000
@@ -32,9 +32,9 @@
 from PyQt5.QtWidgets import QDialog
 
 from openlp.core.common.registry import Registry
-from openlp.core.lib.projector.db import ProjectorDB, Projector
-from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES
-from openlp.core.ui.projector.sourceselectform import source_group, SourceSelectSingle
+from openlp.core.projectors.db import ProjectorDB, Projector
+from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES
+from openlp.core.projectors.sourceselectform import source_group, SourceSelectSingle
 
 from tests.helpers.testmixin import TestMixin
 from tests.resources.projector.data import TEST_DB, TEST1_DATA
@@ -58,7 +58,7 @@
     """
     Test class for the Projector Source Select form module
     """
-    @patch('openlp.core.lib.projector.db.init_url')
+    @patch('openlp.core.projectors.db.init_url')
     def setUp(self, mocked_init_url):
         """
         Set up anything necessary for all tests


Follow ups