← Back to team overview

openlp-core team mailing list archive

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

 

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

Commit message:
PJLink class 2 update F

Requested reviews:
  OpenLP Core (openlp-core)

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


-- Added '# noqa' for flake8 to ignore specific line errors
-- Renamed test_projector_clssto clss_one
-- Added test_projector_clss_two
-- Updated  PJLink non-standard class check
-- Added process_snum
-- Added tests for process_snum
-- Renamed tests/functional/openlp_core_lib/test_projectordb.py to test_projector_db.py
-- Added filter model command
-- Added lamp model command
-- Added tests for filter/model commands

--------------------------------
lp:~alisonken1/openlp/pjlink2-f (revision 2752)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2096/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2006/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1919/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1296/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1142/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/271/
[SUCCESS] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/116/

-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~alisonken1/openlp/pjlink2-f into lp:openlp.
=== modified file 'openlp/core/lib/projector/db.py'
--- openlp/core/lib/projector/db.py	2017-06-01 22:35:57 +0000
+++ openlp/core/lib/projector/db.py	2017-06-25 02:30:08 +0000
@@ -303,7 +303,7 @@
         :param ip: Host IP/Name
         :returns: Projector() instance
         """
-        log.debug('get_projector_by_ip(ip="%s")' % ip)
+        log.debug('get_projector_by_ip(ip="{ip}")'.format(ip=ip))
         projector = self.get_object_filtered(Projector, Projector.ip == ip)
         if projector is None:
             # Not found

=== modified file 'openlp/core/lib/projector/pjlink1.py'
--- openlp/core/lib/projector/pjlink1.py	2017-06-17 00:25:06 +0000
+++ openlp/core/lib/projector/pjlink1.py	2017-06-25 02:30:08 +0000
@@ -44,6 +44,8 @@
 
 __all__ = ['PJLink']
 
+import re
+
 from codecs import decode
 
 from PyQt5 import QtCore, QtNetwork
@@ -53,9 +55,12 @@
     E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \
     E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, \
     PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
-    PJLINK_DEFAULT_CODES, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \
+    STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \
     S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS
 
+# Possible future imports
+# from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
+
 # Shortcuts
 SocketError = QtNetwork.QAbstractSocket.SocketError
 SocketSTate = QtNetwork.QAbstractSocket.SocketState
@@ -113,8 +118,13 @@
         self.port = port
         self.pin = pin
         super().__init__()
+        self.model_lamp = None
+        self.model_filter = None
         self.mac_adx = kwargs.get('mac_adx')
+        self.serial_no = None
+        self.serial_no_received = None  # Used only if saved serial number is different than received serial number
         self.dbid = None
+        self.db_update = False  # Use to check if db needs to be updated prior to exiting
         self.location = None
         self.notes = None
         self.dbid = kwargs.get('dbid')
@@ -158,7 +168,9 @@
             'LAMP': self.process_lamp,
             'NAME': self.process_name,
             'PJLINK': self.check_login,
-            'POWR': self.process_powr
+            'POWR': self.process_powr,
+            'SNUM': self.process_snum,
+            'SVER': self.process_sver
         }
 
     def reset_information(self):
@@ -166,12 +178,16 @@
         Reset projector-specific information to default
         """
         log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state()))
+        self.send_queue = []
         self.power = S_OFF
         self.pjlink_name = None
         self.manufacturer = None
         self.model = None
         self.serial_no = None
+        self.serial_no_received = None
         self.sw_version = None
+        self.sw_version_received = None
+        self.mac_adx = None
         self.shutter = None
         self.mute = None
         self.lamp = None
@@ -188,7 +204,6 @@
         if hasattr(self, 'socket_timer'):
             log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.ip))
             self.socket_timer.stop()
-        self.send_queue = []
         self.send_busy = False
 
     def thread_started(self):
@@ -249,7 +264,10 @@
         # Restart timer
         self.timer.start()
         # These commands may change during connetion
-        for command in ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']:
+        check_list = ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']
+        if self.pjlink_class == '2':
+            check_list.extend(['FILT', 'FREZ'])
+        for command in check_list:
             self.send_command(command, queue=True)
         # The following commands do not change, so only check them once
         if self.power == S_ON and self.source_available is None:
@@ -262,6 +280,38 @@
             self.send_command('INF2', queue=True)
         if self.pjlink_name is None:
             self.send_command('NAME', queue=True)
+        if self.pjlink_class == '2':
+            # Class 2 specific checks
+            if self.serial_no is None:
+                self.send_command('SNUM', queue=True)
+            if self.sw_version is None:
+                self.send_command('SVER', queue=True)
+            if self.model_filter is None:
+                self.send_command('RFIL', queue=True)
+            if self.model_lamp is None:
+                self.send_command('RLMP', queue=True)
+
+    def process_rfil(self, data):
+        """
+        Process replacement filter type
+        """
+        if self.model_filter is None:
+            self.model_filter = data
+        else:
+            log.warn("({ip}) Filter model already set".format(ip=self.ip))
+            log.warn("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter))
+            log.warn("({ip}) New model: '{new}'".format(ip=self.ip, new=data))
+
+    def process_rlmp(self, data):
+        """
+        Process replacement lamp type
+        """
+        if self.model_lamp is None:
+            self.model_lamp = data
+        else:
+            log.warn("({ip}) Lamp model already set".format(ip=self.ip))
+            log.warn("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp))
+            log.warn("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data))
 
     def _get_status(self, status):
         """
@@ -341,7 +391,7 @@
             data = decode(read, 'utf-8')
             # Possibility of extraneous data on input when reading.
             # Clean out extraneous characters in buffer.
-            dontcare = self.readLine(self.max_size)
+            dontcare = self.readLine(self.max_size)  # noqa: F841
             log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip()))
         # At this point, we should only have the initial login prompt with
         # possible authentication
@@ -440,7 +490,8 @@
             return
         data_split = data.split('=')
         try:
-            (prefix, version, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1])
+            (prefix, version, cmd, data) = (data_split[0][0], data_split[0][1],  # noqa: F841
+                                            data_split[0][2:], data_split[1])
         except ValueError as e:
             log.warning('({ip}) get_data(): Invalid packet - expected header + command + data'.format(ip=self.ip))
             log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip()))
@@ -729,11 +780,22 @@
         :param data: Class that projector supports.
         """
         # bug 1550891: Projector returns non-standard class response:
-        #            : Expected: %1CLSS=1
-        #            : Received: %1CLSS=Class 1
+        #            : Expected: '%1CLSS=1'
+        #            : Received: '%1CLSS=Class 1'  (Optoma)
+        #            : Received: '%1CLSS=Version1'  (BenQ)
         if len(data) > 1:
-            # Split non-standard information from response
-            clss = data.split()[-1]
+            log.warn("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data))
+            # Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
+            # AND the different responses that can be received, the semi-permanent way to
+            # fix the class reply is to just remove all non-digit characters.
+            try:
+                clss = re.findall('\d', data)[0]  # Should only be the first match
+            except IndexError:
+                log.error("({ip}) No numbers found in class version reply - defaulting to class '1'".format(ip=self.ip))
+                clss = '1'
+        elif not data.isdigit():
+            log.error("({ip}) NAN class version reply - defaulting to class '1'".format(ip=self.ip))
+            clss = '1'
         else:
             clss = data
         self.pjlink_class = clss
@@ -845,6 +907,42 @@
                     PJLINK_ERST_STATUS[data[5]]
         return
 
+    def process_snum(self, data):
+        """
+        Serial number of projector.
+
+        :param data: Serial number from projector.
+        """
+        if self.serial_no is None:
+            log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data))
+            self.serial_no = data
+            self.db_update = False
+        else:
+            # Compare serial numbers and see if we got the same projector
+            if self.serial_no != data:
+                log.warn("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip))
+                log.warn("({ip}) Saved:    '{old}'".format(ip=self.ip, old=self.serial_no))
+                log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
+                log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
+                self.serial_no_received = data
+
+    def process_sver(self, data):
+        """
+        Software version of projector
+        """
+        if self.sw_version is None:
+            log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data))
+            self.sw_version = data
+            self.db_update = True
+        else:
+            # Compare software version and see if we got the same projector
+            if self.serial_no != data:
+                log.warn("({ip}) Projector software version does not match saved software version".format(ip=self.ip))
+                log.warn("({ip}) Saved:    '{old}'".format(ip=self.ip, old=self.sw_version))
+                log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
+                log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
+                self.sw_version_received = data
+
     def connect_to_host(self):
         """
         Initiate connection to projector.

=== modified file 'openlp/core/lib/projector/upgrade.py'
--- openlp/core/lib/projector/upgrade.py	2017-06-09 12:12:39 +0000
+++ openlp/core/lib/projector/upgrade.py	2017-06-25 02:30:08 +0000
@@ -25,16 +25,18 @@
 """
 import logging
 
-# Not all imports used at this time, but keep for future upgrades
-from sqlalchemy import Table, Column, types, inspect
-from sqlalchemy.exc import NoSuchTableError
+from sqlalchemy import Table, Column, types
 from sqlalchemy.sql.expression import null
 
-from openlp.core.common.db import drop_columns
 from openlp.core.lib.db import get_upgrade_op
 
 log = logging.getLogger(__name__)
 
+# Possible future imports
+# from sqlalchemy.exc import NoSuchTableError
+# from sqlalchemy import inspect
+# from openlp.core.common.db import drop_columns
+
 # Initial projector DB was unversioned
 __version__ = 2
 

=== modified file 'setup.cfg'
--- setup.cfg	2017-02-26 21:14:49 +0000
+++ setup.cfg	2017-06-25 02:30:08 +0000
@@ -1,4 +1,14 @@
+# E402 module level import not at top of file
+# E722 do not use bare except, specify exception instead
+# F841 local variable '<variable>' is assigned to but never used
+
 [pep8]
 exclude=resources.py,vlc.py
 max-line-length = 120
 ignore = E402,E722
+
+[flake8]
+exclude=resources.py,vlc.py
+max-line-length = 120
+ignore = E402,E722
+

=== modified file 'tests/functional/openlp_core_lib/test_projector_constants.py'
--- tests/functional/openlp_core_lib/test_projector_constants.py	2017-06-01 22:35:57 +0000
+++ tests/functional/openlp_core_lib/test_projector_constants.py	2017-06-25 02:30:08 +0000
@@ -22,7 +22,7 @@
 """
 Package to test the openlp.core.lib.projector.constants package.
 """
-from unittest import TestCase, skip
+from unittest import TestCase, skip  # noqa: F401
 
 
 class TestProjectorConstants(TestCase):

=== renamed file 'tests/functional/openlp_core_lib/test_projectordb.py' => 'tests/functional/openlp_core_lib/test_projector_db.py'
--- tests/functional/openlp_core_lib/test_projectordb.py	2017-06-01 22:35:57 +0000
+++ tests/functional/openlp_core_lib/test_projector_db.py	2017-06-25 02:30:08 +0000
@@ -29,8 +29,8 @@
 import shutil
 from tempfile import mkdtemp
 
-from unittest import TestCase, skip
-from unittest.mock import MagicMock, patch
+from unittest import TestCase, skip  # noqa: F401
+from unittest.mock import MagicMock, patch  # noqa: F401
 
 from openlp.core.lib.projector import upgrade
 from openlp.core.lib.db import upgrade_db
@@ -413,7 +413,7 @@
         Test add_projector() fail
         """
         # GIVEN: Test entry in the database
-        ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))
+        ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))  # noqa: F841
 
         # WHEN: Attempt to add same projector entry
         results = self.projector.add_projector(Projector(**TEST1_DATA))
@@ -439,7 +439,7 @@
         Test update_projector() when entry not in database
         """
         # GIVEN: Projector entry in database
-        ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))
+        ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))  # noqa: F841
         projector = Projector(**TEST2_DATA)
 
         # WHEN: Attempt to update data with a different ID

=== modified file 'tests/functional/openlp_core_lib/test_projector_pjlink1.py'
--- tests/functional/openlp_core_lib/test_projector_pjlink1.py	2017-05-27 18:21:24 +0000
+++ tests/functional/openlp_core_lib/test_projector_pjlink1.py	2017-06-25 02:30:08 +0000
@@ -59,9 +59,101 @@
         self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
                                                    "Connection request should have been called with TEST_PIN"))
 
-    def test_projector_class(self):
-        """
-        Test class version from projector
+    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.assertEquals(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.assertEquals(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.assertEquals(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')
+
+    def test_projector_clss_one(self):
+        """
+        Test class 1 sent from projector
         """
         # GIVEN: Test object
         pjlink = pjlink_test
@@ -73,9 +165,23 @@
         self.assertEquals(pjlink.pjlink_class, '1',
                           'Projector should have returned class=1')
 
-    def test_non_standard_class_reply(self):
-        """
-        Bugfix 1550891: CLSS request returns non-standard 'Class N' reply
+    def test_projector_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.assertEquals(pjlink.pjlink_class, '2',
+                          'Projector should have returned class=2')
+
+    def test_bug_1550891_non_standard_class_reply(self):
+        """
+        Bugfix 1550891: CLSS request returns non-standard reply
         """
         # GIVEN: Test object
         pjlink = pjlink_test
@@ -85,7 +191,7 @@
 
         # THEN: Projector class should be set with proper value
         self.assertEquals(pjlink.pjlink_class, '1',
-                          'Non-standard class reply should have set proper class')
+                          'Non-standard class reply should have set class=1')
 
     @patch.object(pjlink_test, 'change_status')
     def test_status_change(self, mock_change_status):


Follow ups