← Back to team overview

openlp-core team mailing list archive

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

 

Ken Roberts has proposed merging lp:~alisonken1/openlp/pjlink2-a into lp:openlp with lp:~alisonken1/openlp/pjlink2-resource-data as a prerequisite.

Commit message:
Initial PJLink class 2 updates

Requested reviews:
  Tomas Groth (tomasgroth)

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

Initial PJLink class 2 updates

- Converted PJLINK_DEFAULT_CODES from a static dictionary to dynamically-built dictionary
- Added _not_implemented method to be able to list future methods while updating
- Added class list to hold future method functionality
- Added class list for UDP commands
- Added test for building PJLINK_DEFAULT_CODES dictionary
- Added test for _not_implemented method
- Removed extraneous TODO

--------------------------------
lp:~alisonken1/openlp/pjlink2-a (revision 2735)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2015/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1925/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1861/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1241/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1099/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/228/
[FAILURE] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/76/

-- 
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/lib/projector/constants.py'
--- openlp/core/lib/projector/constants.py	2016-12-31 11:01:36 +0000
+++ openlp/core/lib/projector/constants.py	2017-05-15 16:41:11 +0000
@@ -48,7 +48,8 @@
            'S_INFO', 'S_NETWORK_SENDING', 'S_NETWORK_RECEIVED',
            'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
            'PJLINK_PORT', 'PJLINK_MAX_PACKET', 'TIMEOUT', 'ERROR_MSG', 'PJLINK_ERRORS',
-           'STATUS_STRING', 'PJLINK_VALID_CMD', 'CONNECTION_ERRORS']
+           'STATUS_STRING', 'PJLINK_VALID_CMD', 'CONNECTION_ERRORS',
+           'PJLINK_DEFAULT_SOURCES', 'PJLINK_DEFAULT_CODES', 'PJLINK_DEFAULT_ITEMS']
 
 # Set common constants.
 CR = chr(0x0D)  # \r
@@ -321,53 +322,54 @@
     '2': translate('OpenLP.DB', 'Video'),
     '3': translate('OpenLP.DB', 'Digital'),
     '4': translate('OpenLP.DB', 'Storage'),
-    '5': translate('OpenLP.DB', 'Network')
-}
-
-PJLINK_DEFAULT_CODES = {
-    '11': translate('OpenLP.DB', 'RGB 1'),
-    '12': translate('OpenLP.DB', 'RGB 2'),
-    '13': translate('OpenLP.DB', 'RGB 3'),
-    '14': translate('OpenLP.DB', 'RGB 4'),
-    '15': translate('OpenLP.DB', 'RGB 5'),
-    '16': translate('OpenLP.DB', 'RGB 6'),
-    '17': translate('OpenLP.DB', 'RGB 7'),
-    '18': translate('OpenLP.DB', 'RGB 8'),
-    '19': translate('OpenLP.DB', 'RGB 9'),
-    '21': translate('OpenLP.DB', 'Video 1'),
-    '22': translate('OpenLP.DB', 'Video 2'),
-    '23': translate('OpenLP.DB', 'Video 3'),
-    '24': translate('OpenLP.DB', 'Video 4'),
-    '25': translate('OpenLP.DB', 'Video 5'),
-    '26': translate('OpenLP.DB', 'Video 6'),
-    '27': translate('OpenLP.DB', 'Video 7'),
-    '28': translate('OpenLP.DB', 'Video 8'),
-    '29': translate('OpenLP.DB', 'Video 9'),
-    '31': translate('OpenLP.DB', 'Digital 1'),
-    '32': translate('OpenLP.DB', 'Digital 2'),
-    '33': translate('OpenLP.DB', 'Digital 3'),
-    '34': translate('OpenLP.DB', 'Digital 4'),
-    '35': translate('OpenLP.DB', 'Digital 5'),
-    '36': translate('OpenLP.DB', 'Digital 6'),
-    '37': translate('OpenLP.DB', 'Digital 7'),
-    '38': translate('OpenLP.DB', 'Digital 8'),
-    '39': translate('OpenLP.DB', 'Digital 9'),
-    '41': translate('OpenLP.DB', 'Storage 1'),
-    '42': translate('OpenLP.DB', 'Storage 2'),
-    '43': translate('OpenLP.DB', 'Storage 3'),
-    '44': translate('OpenLP.DB', 'Storage 4'),
-    '45': translate('OpenLP.DB', 'Storage 5'),
-    '46': translate('OpenLP.DB', 'Storage 6'),
-    '47': translate('OpenLP.DB', 'Storage 7'),
-    '48': translate('OpenLP.DB', 'Storage 8'),
-    '49': translate('OpenLP.DB', 'Storage 9'),
-    '51': translate('OpenLP.DB', 'Network 1'),
-    '52': translate('OpenLP.DB', 'Network 2'),
-    '53': translate('OpenLP.DB', 'Network 3'),
-    '54': translate('OpenLP.DB', 'Network 4'),
-    '55': translate('OpenLP.DB', 'Network 5'),
-    '56': translate('OpenLP.DB', 'Network 6'),
-    '57': translate('OpenLP.DB', 'Network 7'),
-    '58': translate('OpenLP.DB', 'Network 8'),
-    '59': translate('OpenLP.DB', 'Network 9')
-}
+    '5': translate('OpenLP.DB', 'Network'),
+    '6': translate('OpenLP.DB', 'Internal')
+}
+
+PJLINK_DEFAULT_ITEMS = {
+    '1': translate('OpenLP.DB', '1'),
+    '2': translate('OpenLP.DB', '2'),
+    '3': translate('OpenLP.DB', '3'),
+    '4': translate('OpenLP.DB', '4'),
+    '5': translate('OpenLP.DB', '5'),
+    '6': translate('OpenLP.DB', '6'),
+    '7': translate('OpenLP.DB', '7'),
+    '8': translate('OpenLP.DB', '8'),
+    '9': translate('OpenLP.DB', '9'),
+    'A': translate('OpenLP.DB', 'A'),
+    'B': translate('OpenLP.DB', 'B'),
+    'C': translate('OpenLP.DB', 'C'),
+    'D': translate('OpenLP.DB', 'D'),
+    'E': translate('OpenLP.DB', 'E'),
+    'F': translate('OpenLP.DB', 'F'),
+    'G': translate('OpenLP.DB', 'G'),
+    'H': translate('OpenLP.DB', 'H'),
+    'I': translate('OpenLP.DB', 'I'),
+    'J': translate('OpenLP.DB', 'J'),
+    'K': translate('OpenLP.DB', 'K'),
+    'L': translate('OpenLP.DB', 'L'),
+    'M': translate('OpenLP.DB', 'M'),
+    'N': translate('OpenLP.DB', 'N'),
+    'O': translate('OpenLP.DB', 'O'),
+    'P': translate('OpenLP.DB', 'P'),
+    'Q': translate('OpenLP.DB', 'Q'),
+    'R': translate('OpenLP.DB', 'R'),
+    'S': translate('OpenLP.DB', 'S'),
+    'T': translate('OpenLP.DB', 'T'),
+    'U': translate('OpenLP.DB', 'U'),
+    'V': translate('OpenLP.DB', 'V'),
+    'W': translate('OpenLP.DB', 'W'),
+    'X': translate('OpenLP.DB', 'X'),
+    'Y': translate('OpenLP.DB', 'Y'),
+    'Z': translate('OpenLP.DB', 'Z')
+}
+
+# Due to the expanded nature of PJLink class 2 video sources,
+# translate the individual types then build the video source
+# dictionary from the translations.
+PJLINK_DEFAULT_CODES = dict()
+for source in PJLINK_DEFAULT_SOURCES:
+    for item in PJLINK_DEFAULT_ITEMS:
+        label = "{source}{item}".format(source=source, item=item)
+        PJLINK_DEFAULT_CODES[label] = "{source} {item}".format(source=PJLINK_DEFAULT_SOURCES[source],
+                                                               item=PJLINK_DEFAULT_ITEMS[item])

=== modified file 'openlp/core/lib/projector/pjlink1.py'
--- openlp/core/lib/projector/pjlink1.py	2016-12-31 11:01:36 +0000
+++ openlp/core/lib/projector/pjlink1.py	2017-05-15 16:41:11 +0000
@@ -78,6 +78,33 @@
     projectorNoAuthentication = QtCore.pyqtSignal(str)  # PIN set and no authentication needed
     projectorReceivedData = QtCore.pyqtSignal()  # Notify when received data finished processing
     projectorUpdateIcons = QtCore.pyqtSignal()  # Update the status icons on toolbar
+    # New commands available in PJLink Class 2
+    pjlink_future = [
+        'ACKN',  # UDP Reply to 'SRCH'
+        'FILT',  # Get current filter usage time
+        'FREZ',  # Set freeze/unfreeze picture being projected
+        'INNM',  # Get Video source input terminal name
+        'IRES',  # Get Video source resolution
+        'LKUP',  # UPD Linkup status notification
+        'MVOL',  # Set microphone volume
+        'RFIL',  # Get replacement air filter model number
+        'RLMP',  # Get lamp replacement model number
+        'RRES',  # Get projector recommended video resolution
+        'SNUM',  # Get projector serial number
+        'SRCH',  # UDP broadcast search for available projectors on local network
+        'SVER',  # Get projector software version
+        'SVOL',  # Set speaker volume
+        'TESTMEONLY'  # For testing when other commands have been implemented
+    ]
+
+    pjlink_udp_commands = [
+        'ACKN',
+        'ERST',  # Class 1 or 2
+        'INPT',  # Class 1 or 2
+        'LKUP',
+        'POWR',  # Class 1 or 2
+        'SRCH'
+    ]
 
     def __init__(self, name=None, ip=None, port=PJLINK_PORT, pin=None, *args, **kwargs):
         """
@@ -403,7 +430,8 @@
             return
         self.socket_timer.stop()
         self.projectorNetwork.emit(S_NETWORK_RECEIVED)
-        data_in = decode(read, 'ascii')
+        # NOTE: Class2 has changed to some values being UTF-8
+        data_in = decode(read, 'utf-8')
         data = data_in.strip()
         if len(data) < 7:
             # Not enough data for a packet
@@ -510,11 +538,12 @@
             self._send_command()
 
     @QtCore.pyqtSlot()
-    def _send_command(self, data=None):
+    def _send_command(self, data=None, utf8=False):
         """
         Socket interface to send data. If data=None, then check queue.
 
         :param data: Immediate data to send
+        :param utf8: Send as UTF-8 string otherwise send as ASCII string
         """
         log.debug('({ip}) _send_string()'.format(ip=self.ip))
         log.debug('({ip}) _send_string(): Connection status: {data}'.format(ip=self.ip, data=self.state()))
@@ -542,7 +571,7 @@
         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('ascii'))
+        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:
             # Network error?
@@ -556,7 +585,13 @@
         :param cmd: Command to process
         :param data: Data being processed
         """
-        log.debug('({ip}) Processing command "{data}"'.format(ip=self.ip, data=cmd))
+        log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip,
+                                                                                cmd=cmd,
+                                                                                data=data))
+        # Check if we have a future command not available yet
+        if cmd in self.pjlink_future:
+            self._not_implemented(cmd)
+            return
         if data in PJLINK_ERRORS:
             # Oops - projector error
             log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data))
@@ -568,9 +603,8 @@
                 self.projectorAuthentication.emit(self.name)
             elif data.upper() == 'ERR1':
                 # Undefined command
-                self.change_status(E_UNDEFINED, '{error} "{data}"'.format(error=translate('OpenLP.PJLink1',
-                                                                                          'Undefined command:'),
-                                                                          data=cmd))
+                self.change_status(E_UNDEFINED, '{error}: "{data}"'.format(error=ERROR_MSG[E_UNDEFINED],
+                                                                           data=cmd))
             elif data.upper() == 'ERR2':
                 # Invalid parameter
                 self.change_status(E_PARAMETER)
@@ -962,3 +996,11 @@
         log.debug('({ip}) Setting AVMT to "10" (shutter open)'.format(ip=self.ip))
         self.send_command(cmd='AVMT', opts='10')
         self.poll_loop()
+
+    def _not_implemented(self, cmd):
+        """
+        Log when a future PJLink command has not been implemented yet.
+        """
+        log.warn("({ip}) Future command '{cmd}' has not been implemented yet".format(ip=self.ip,
+                                                                                     cmd=cmd))
+        return

=== added file 'tests/functional/openlp_core_lib/test_projector_constants.py'
--- tests/functional/openlp_core_lib/test_projector_constants.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_lib/test_projector_constants.py	2017-05-15 16:41:11 +0000
@@ -0,0 +1,44 @@
+# -*- 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, skip
+
+
+class TestProjectorConstants(TestCase):
+    """
+    Test specific functions in the projector constants module.
+    """
+    @skip('Waiting for merge of ~alisonken1/openlp/pjlink2-resource-data')
+    def build_pjlink_video_label_test(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.assertEquals(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match')

=== modified file 'tests/functional/openlp_core_lib/test_projector_pjlink1.py'
--- tests/functional/openlp_core_lib/test_projector_pjlink1.py	2017-04-24 05:17:55 +0000
+++ tests/functional/openlp_core_lib/test_projector_pjlink1.py	2017-05-15 16:41:11 +0000
@@ -366,3 +366,18 @@
         # THEN: send_command should have the proper authentication
         self.assertEquals("{test}".format(test=mock_send_command.call_args),
                           "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
+
+    @patch.object(pjlink_test, '_not_implemented')
+    def not_implemented_test(self, mock_not_implemented):
+        """
+        Test pjlink1._not_implemented method being called
+        """
+        # GIVEN: test object
+        pjlink = pjlink_test
+        test_cmd = 'TESTMEONLY'
+
+        # WHEN: A future command is called that is not implemented yet
+        pjlink.process_command(test_cmd, "Garbage data for test only")
+
+        # THEN: pjlink1.__not_implemented should have been called with test_cmd
+        mock_not_implemented.assert_called_with(test_cmd)


Follow ups