← Back to team overview

openlp-core team mailing list archive

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

 

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

Commit message:
PJLink2 Update V03

Requested reviews:
  OpenLP Core (openlp-core)

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

NOTE: Part 3 of a multi-part merge.
      v[1..n] merges are to fix tests

WARNING: Requires merge of lp:~alisonken1/openlp/pjlink2-v02

- Update tests for test_projector_pjlink_base_02.py
- Added setUp() and tearDown() to have a common pjlink instance
- Rename test_send_command_no_data() to test_send_command_not_connected() to reflect actual test
- Add tests for PJLink.send_command and PJLink._underscore_send_command
- Fix test_projector_pjlink_cmd_routing for logging change to PJLink._underscore_send_command()

- In openlp.core.projectors.pjlink.PJLink class:
    - Added copy import for PJLink reset information so PJLINK_CLASS default is not changed
    - Fix send_command() socket state check
    - Change some log entries from debug to warning
    - Add call to pjlink._underscore_send_command() for invalid command check if
      send_queue or priority_queue has data to send
    - Fix _underscore_send_command no data check to include setting send_busy to False

--------------------------------------------------------------------------------
lp:~alisonken1/openlp/pjlink2-v03 (revision 2861)
https://ci.openlp.io/job/Branch-01-Pull/2722/                          [SUCCESS]
https://ci.openlp.io/job/Branch-02a-Linux-Tests/2616/                  [SUCCESS]
https://ci.openlp.io/job/Branch-02b-macOS-Tests/391/                   [SUCCESS]
https://ci.openlp.io/job/Branch-03a-Build-Source/214/                  [SUCCESS]
https://ci.openlp.io/job/Branch-03b-Build-macOS/193/                   [SUCCESS]
https://ci.openlp.io/job/Branch-04a-Code-Lint/1676/                    [SUCCESS]
https://ci.openlp.io/job/Branch-04b-Test-Coverage/1489/                [SUCCESS]
https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/371/                 [SUCCESS]

All builds passed
-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~alisonken1/openlp/pjlink2-v03 into lp:openlp.
=== modified file 'openlp/core/projectors/pjlink.py'
--- openlp/core/projectors/pjlink.py	2019-04-28 07:10:59 +0000
+++ openlp/core/projectors/pjlink.py	2019-04-28 07:10:59 +0000
@@ -48,6 +48,7 @@
 """
 import logging
 from codecs import decode
+from copy import copy
 
 from PyQt5 import QtCore, QtNetwork
 
@@ -276,6 +277,7 @@
         self.model_lamp = None  # RLMP
         self.mute = None  # AVMT
         self.other_info = None  # INFO
+        self.pjlink_class = copy(PJLINK_CLASS)
         self.pjlink_name = None  # NAME
         self.power = S_OFF  # POWR
         self.serial_no = None  # SNUM
@@ -629,11 +631,14 @@
         :param salt: Optional  salt for md5 hash initial authentication
         :param priority: Option to send packet now rather than queue it up
         """
-        if QSOCKET_STATE[self.state()] != S_CONNECTED:
+        if QSOCKET_STATE[self.state()] != QSOCKET_STATE[S_CONNECTED]:
             log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.entry.name))
             return self.reset_information()
         if cmd not in PJLINK_VALID_CMD:
             log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.entry.name))
+            if self.priority_queue or self.send_queue:
+                # Just in case there's already something to send
+                return self._send_command()
             return
         log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.entry.name,
                                                                                                command=cmd,
@@ -649,9 +654,9 @@
                                                                  options=opts,
                                                                  suffix=PJLINK_SUFFIX)
         if out in self.priority_queue:
-            log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.entry.name))
+            log.warning('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.entry.name))
         elif out in self.send_queue:
-            log.debug('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.entry.name))
+            log.warning('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.entry.name))
         else:
             if priority:
                 log.debug('({ip}) send_command(): Adding to priority queue'.format(ip=self.entry.name))
@@ -672,7 +677,8 @@
         :param utf8: Send as UTF-8 string otherwise send as ASCII string
         """
         if not data and not self.priority_queue and not self.send_queue:
-            log.debug('({ip}) _send_command(): Nothing to send - returning'.format(ip=self.entry.name))
+            log.warning('({ip}) _send_command(): Nothing to send - returning'.format(ip=self.entry.name))
+            self.send_busy = False
             return
         log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.entry.name,
                                                                data=data.strip() if data else data))
@@ -684,7 +690,7 @@
         log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.entry.name,
                                                                              data=conn_state))
         if QSOCKET_STATE[self.state()] != S_CONNECTED:
-            log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.entry.name))
+            log.warning('({ip}) _send_command() Not connected - abort'.format(ip=self.entry.name))
             self.send_busy = False
             return self.disconnect_from_host()
         if data and data not in self.priority_queue:
@@ -707,7 +713,7 @@
             log.debug('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.entry.name))
         else:
             # No data to send
-            log.debug('({ip}) _send_command(): No data to send'.format(ip=self.entry.name))
+            log.warning('({ip}) _send_command(): No data to send'.format(ip=self.entry.name))
             self.send_busy = False
             return
         self.send_busy = True

=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_base_02.py'
--- tests/openlp_core/projectors/test_projector_pjlink_base_02.py	2019-04-28 07:10:59 +0000
+++ tests/openlp_core/projectors/test_projector_pjlink_base_02.py	2019-04-28 07:10:59 +0000
@@ -22,11 +22,12 @@
 """
 Package to test the openlp.core.projectors.pjlink base package.
 """
-from unittest import TestCase, skip
+from unittest import TestCase
 from unittest.mock import call, patch
 
 import openlp.core.projectors.pjlink
-from openlp.core.projectors.constants import S_NOT_CONNECTED
+from openlp.core.projectors.constants import E_NETWORK, PJLINK_PREFIX, PJLINK_SUFFIX, QSOCKET_STATE, \
+    S_CONNECTED, S_NOT_CONNECTED
 from openlp.core.projectors.db import Projector
 from openlp.core.projectors.pjlink import PJLink
 from tests.resources.projector.data import TEST1_DATA
@@ -36,67 +37,658 @@
     """
     Tests for the PJLink module
     """
-    @skip('Needs update to new setup')
-    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
-    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
-    @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
-    @patch.object(openlp.core.projectors.pjlink, 'log')
-    def test_send_command_no_data(self, mock_log, mock_send_command, mock_reset, mock_state):
-        """
-        Test _send_command with no data to send
-        """
-        # GIVEN: Test object
-        log_warning_calls = [call('({ip}) send_command(): Not connected - returning'.format(ip=TEST1_DATA['name']))]
-
-        log_debug_calls = [call('PJlink(projector="< Projector(id="None", ip="111.111.111.111", port="1111", '
-                                'mac_adx="11:11:11:11:11:11", pin="1111", name="___TEST_ONE___", '
-                                'location="location one", notes="notes one", pjlink_name="None", '
-                                'pjlink_class="None", manufacturer="None", model="None", '
-                                'serial_no="Serial Number 1", other="None", sources="None", source_list="[]", '
-                                'model_filter="Filter type 1", model_lamp="Lamp type 1", '
-                                'sw_version="Version 1") >", args="()" kwargs="{\'no_poll\': True}")'),
-                           call('PJlinkCommands(args=() kwargs={})')]
+    def setUp(self):
+        """
+        Initialize test state(s)
+        """
+        # Default PJLink instance for tests
+        self.pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
+
+    def tearDown(self):
+        """
+        Cleanup test state(s)
+        """
+        del(self.pjlink)
+
+    # ------------ Test PJLink._underscore_send_command ----------
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'change_status')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'write')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_local_send_command_network_error(self, mock_log, mock_reset, mock_state, mock_disconnect, mock_write,
+                                              mock_change_status):
+        """
+        Test _underscore_send_command when possible network error occured
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = [call('({ip}) _send_command(): -1 received - '
+                                  'disconnecting from host'.format(ip=self.pjlink.name))]
+        log_debug_calls = [call('({ip}) _send_command(data="None")'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): priority_queue: []'.format(ip=self.pjlink.name)),
+                           call("({ip}) _send_command(): send_queue: ['{data}\\r']".format(ip=self.pjlink.name,
+                                                                                           data=test_command.strip())),
+                           call('({ip}) _send_command(): Connection status: S_CONNECTED'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Sending "{data}"'.format(ip=self.pjlink.name,
+                                                                                  data=test_command.strip()))
+                           ]
+
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
+        mock_write.return_value = -1
+        self.pjlink.send_queue = [test_command]
+        self.pjlink.priority_queue = []
+
+        # WHEN: _send_command called with no data and queue's emtpy
+        # Patch some attributes here since they are not available until after instantiation
+        with patch.object(self.pjlink, 'socket_timer') as mock_timer, \
+                patch.object(self.pjlink, 'waitForBytesWritten') as mock_waitBytes:
+            mock_waitBytes.return_value = True
+            self.pjlink._send_command()
+
+            # THEN:
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            mock_change_status.called_with(E_NETWORK, 'Error while sending data to projector')
+            assert (not self.pjlink.send_queue), 'Send queue should be empty'
+            assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+            assert mock_timer.start.called, 'Timer should have been called'
+            assert (not mock_reset.called), 'reset_information() should not should have been called'
+            assert mock_disconnect.called, 'disconnect_from_host() should have been called'
+            assert self.pjlink.send_busy, 'send_busy should be True'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_local_send_command_no_data(self, mock_log, mock_reset, mock_state):
+        """
+        Test _underscore_send_command with no data to send
+        """
+        # GIVEN: Test object
+        log_error_calls = []
+        log_warning_calls = [call('({ip}) _send_command(): Nothing to send - returning'.format(ip=self.pjlink.name))]
+        log_debug_calls = []
+        mock_state.return_value = S_CONNECTED
+        self.pjlink.send_queue = []
+        self.pjlink.priority_queue = []
+
+        # WHEN: _send_command called with no data and queue's emtpy
+        # Patch some attributes here since they are not available until after instantiation
+        with patch.object(self.pjlink, 'socket_timer') as mock_timer:
+            self.pjlink._send_command()
+
+            # THEN:
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            assert (not self.pjlink.send_queue), 'Send queue should be empty'
+            assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+            assert (not mock_timer.called), 'Timer should not have been called'
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_local_send_command_no_data_queue_check(self, mock_log, mock_reset, mock_state):
+        """
+        Test _underscore_send_command last queue length check
+        """
+        # GIVEN: Test object
+        log_error_calls = []
+        log_warning_calls = [call('({ip}) _send_command(): No data to send'.format(ip=self.pjlink.name))]
+        log_debug_calls = []
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
+        self.pjlink.priority_queue = []
+
+        # WHEN: _send_command called with no data and queue's emtpy
+        # Patch some attributes here since they are not available until after instantiation
+        with patch.object(self.pjlink, 'socket_timer') as mock_timer, \
+                patch.object(self.pjlink, 'send_queue') as mock_queue:
+            # Unlikely case of send_queue not really empty, but len(send_queue) returns 0
+            mock_queue.return_value = ['test']
+            mock_queue.__len__.return_value = 0
+            self.pjlink._send_command(data=None)
+
+            # THEN:
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+            assert (not mock_timer.called), 'Timer should not have been called'
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'write')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_local_send_command_normal_send(self, mock_log, mock_reset, mock_state, mock_disconnect, mock_write):
+        """
+        Test _underscore_send_command using normal queue
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = []
+        log_debug_calls = [call('({ip}) _send_command(data="None")'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): priority_queue: []'.format(ip=self.pjlink.name)),
+                           call("({ip}) _send_command(): send_queue: ['{data}\\r']".format(ip=self.pjlink.name,
+                                                                                           data=test_command.strip())),
+                           call('({ip}) _send_command(): Connection status: S_CONNECTED'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Sending "{data}"'.format(ip=self.pjlink.name,
+                                                                                  data=test_command.strip()))
+                           ]
+
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
+        mock_write.return_value = len(test_command)
+        self.pjlink.send_queue = [test_command]
+        self.pjlink.priority_queue = []
+
+        # WHEN: _send_command called with no data and queue's emtpy
+        # Patch some attributes here since they are not available until after instantiation
+        with patch.object(self.pjlink, 'socket_timer') as mock_timer, \
+                patch.object(self.pjlink, 'waitForBytesWritten') as mock_waitBytes:
+            mock_waitBytes.return_value = True
+            self.pjlink._send_command()
+
+            # THEN:
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            assert (not self.pjlink.send_queue), 'Send queue should be empty'
+            assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+            assert mock_timer.start.called, 'Timer should have been called'
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+            assert (not mock_disconnect.called), 'disconnect_from_host() should not have been called'
+            assert self.pjlink.send_busy, 'send_busy flag should be True'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_local_send_command_not_connected(self, mock_log, mock_reset, mock_state, mock_disconnect):
+        """
+        Test _underscore_send_command when not connected
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = [call('({ip}) _send_command() Not connected - abort'.format(ip=self.pjlink.name))]
+        log_debug_calls = [call('({ip}) _send_command(data="None")'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): priority_queue: []'.format(ip=self.pjlink.name)),
+                           call("({ip}) _send_command(): send_queue: ['%1CLSS ?\\r']".format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Connection status: S_OK'.format(ip=self.pjlink.name))]
         mock_state.return_value = S_NOT_CONNECTED
-        pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
-        pjlink.send_queue = []
-        pjlink.priority_queue = []
-
-        # WHEN: _send_command called with no data and queue's empty
-        pjlink.send_command(cmd='DONTCARE')
-
-        # THEN:
-        mock_log.debug.assert_has_calls(log_debug_calls)
-        mock_log.warning.assert_has_calls(log_warning_calls)
-        assert mock_reset.called is True
-        assert mock_reset.called is True
-
-    @skip('Needs update to new setup')
-    @patch.object(openlp.core.projectors.pjlink, 'log')
-    def test_local_send_command_no_data(self, mock_log):
-        """
-        Test _send_command with no data to send
-        """
-        # GIVEN: Test object
-        log_debug_calls = [call('PJlink(projector="< Projector(id="None", ip="111.111.111.111", port="1111", '
-                                'mac_adx="11:11:11:11:11:11", pin="1111", name="___TEST_ONE___", '
-                                'location="location one", notes="notes one", pjlink_name="None", '
-                                'pjlink_class="None", manufacturer="None", model="None", '
-                                'serial_no="Serial Number 1", other="None", sources="None", source_list="[]", '
-                                'model_filter="Filter type 1", model_lamp="Lamp type 1", '
-                                'sw_version="Version 1") >", args="()" kwargs="{\'no_poll\': True}")'),
-                           call('PJlinkCommands(args=() kwargs={})'),
-                           call('(___TEST_ONE___) reset_information() connect status is S_NOT_CONNECTED'),
-                           call('(___TEST_ONE___) _send_command(): Nothing to send - returning')]
-
-        pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
-        pjlink.send_queue = []
-        pjlink.priority_queue = []
+        self.pjlink.send_queue = [test_command]
+        self.pjlink.priority_queue = []
 
         # WHEN: _send_command called with no data and queue's emtpy
         # Patch here since pjlink does not have socket_timer until after instantiation
-        with patch.object(pjlink, 'socket_timer') as mock_timer:
-            pjlink._send_command(data=None, utf8=False)
-
-            # THEN:
-            mock_log.debug.assert_has_calls(log_debug_calls)
-            assert mock_timer.called is False
+        with patch.object(self.pjlink, 'socket_timer') as mock_timer:
+            self.pjlink._send_command()
+
+            # THEN:
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            assert (self.pjlink.send_queue == [test_command]), 'Send queue should have one entry'
+            assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+            assert (not mock_timer.called), 'Timer should not have been called'
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+            assert mock_disconnect.called, 'disconnect_from_host() should have been called'
+            assert (not self.pjlink.send_busy), 'send_busy flag should be False'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'write')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_local_send_command_priority_send(self, mock_log, mock_reset, mock_state, mock_disconnect, mock_write):
+        """
+        Test _underscore_send_command with priority queue
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = []
+        log_debug_calls = [call('({ip}) _send_command(data="{data}")'.format(ip=self.pjlink.name,
+                                                                             data=test_command.strip())),
+                           call('({ip}) _send_command(): priority_queue: []'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): send_queue: []'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Connection status: S_CONNECTED'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Priority packet - '
+                                'adding to priority queue'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Sending "{data}"'.format(ip=self.pjlink.name,
+                                                                                  data=test_command.strip()))
+                           ]
+
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
+        mock_write.return_value = len(test_command)
+        self.pjlink.send_queue = []
+        self.pjlink.priority_queue = []
+
+        # WHEN: _send_command called with no data and queue's emtpy
+        # Patch some attributes here since they are not available until after instantiation
+        with patch.object(self.pjlink, 'socket_timer') as mock_timer, \
+                patch.object(self.pjlink, 'waitForBytesWritten') as mock_waitBytes:
+            mock_waitBytes.return_value = True
+            self.pjlink._send_command(data=test_command)
+
+            # THEN:
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            assert (not self.pjlink.send_queue), 'Send queue should be empty'
+            assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+            assert mock_timer.start.called, 'Timer should have been called'
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+            assert (not mock_disconnect.called), 'disconnect_from_host() should not have been called'
+            assert self.pjlink.send_busy, 'send_busy flag should be True'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'write')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_local_send_command_priority_send_with_normal_queue(self, mock_log, mock_reset, mock_state,
+                                                                mock_disconnect, mock_write):
+        """
+        Test _underscore_send_command with priority queue when normal queue active
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = []
+        log_debug_calls = [call('({ip}) _send_command(data="{data}")'.format(ip=self.pjlink.name,
+                                                                             data=test_command.strip())),
+                           call('({ip}) _send_command(): priority_queue: []'.format(ip=self.pjlink.name)),
+                           call("({ip}) _send_command(): send_queue: ['{data}\\r']".format(ip=self.pjlink.name,
+                                                                                           data=test_command.strip())),
+                           call('({ip}) _send_command(): Connection status: S_CONNECTED'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Priority packet - '
+                                'adding to priority queue'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Sending "{data}"'.format(ip=self.pjlink.name,
+                                                                                  data=test_command.strip()))
+                           ]
+
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
+        mock_write.return_value = len(test_command)
+        self.pjlink.send_queue = [test_command]
+        self.pjlink.priority_queue = []
+
+        # WHEN: _send_command called with no data and queue's emtpy
+        # Patch some attributes here since they are not available until after instantiation
+        with patch.object(self.pjlink, 'socket_timer') as mock_timer, \
+                patch.object(self.pjlink, 'waitForBytesWritten') as mock_waitBytes:
+            mock_waitBytes.return_value = True
+            self.pjlink._send_command(data=test_command)
+
+            # THEN:
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            assert self.pjlink.send_queue, 'Send queue should have one entry'
+            assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+            assert mock_timer.start.called, 'Timer should have been called'
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+            assert (not mock_disconnect.called), 'disconnect_from_host() should not have been called'
+            assert self.pjlink.send_busy, 'send_busy flag should be True'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_local_send_command_send_busy_normal_queue(self, mock_log, mock_reset, mock_state):
+        """
+        Test _underscore_send_command send_busy flag with normal queue
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = []
+        log_debug_calls = [call('({ip}) _send_command(data="None")'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): priority_queue: []'.format(ip=self.pjlink.name)),
+                           call("({ip}) _send_command(): send_queue: ['{data}\\r']".format(ip=self.pjlink.name,
+                                                                                           data=test_command.strip())),
+                           call('({ip}) _send_command(): Connection status: S_CONNECTED'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Still busy, returning'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Priority queue = []'.format(ip=self.pjlink.name)),
+                           call("({ip}) _send_command(): Normal queue = "
+                                "['{data}\\r']".format(ip=self.pjlink.name, data=test_command.strip()))]
+
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
+        self.pjlink.send_busy = True
+        self.pjlink.send_queue = [test_command]
+        self.pjlink.priority_queue = []
+
+        # WHEN: _send_command called with no data and queue's emtpy
+        # Patch some attributes here since they are not available until after instantiation
+        with patch.object(self.pjlink, 'socket_timer') as mock_timer:
+            self.pjlink._send_command()
+
+            # THEN:
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            assert self.pjlink.send_queue, 'Send queue should have one entry'
+            assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+            assert (not mock_timer.start.called), 'Timer should not have been called'
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+            assert self.pjlink.send_busy, 'send_busy flag should be True'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_local_send_command_send_busy_priority_queue(self, mock_log, mock_reset, mock_state):
+        """
+        Test _underscore_send_command send_busy flag with priority queue
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = []
+        log_debug_calls = [call('({ip}) _send_command(data="None")'.format(ip=self.pjlink.name)),
+                           call("({ip}) _send_command(): priority_queue: "
+                                "['{data}\\r']".format(ip=self.pjlink.name,
+                                                       data=test_command.strip())),
+                           call('({ip}) _send_command(): send_queue: []'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Connection status: S_CONNECTED'.format(ip=self.pjlink.name)),
+                           call('({ip}) _send_command(): Still busy, returning'.format(ip=self.pjlink.name)),
+                           call("({ip}) _send_command(): Priority queue = "
+                                "['{data}\\r']".format(ip=self.pjlink.name, data=test_command.strip())),
+                           call('({ip}) _send_command(): Normal queue = []'.format(ip=self.pjlink.name))
+                           ]
+
+        mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
+        self.pjlink.send_busy = True
+        self.pjlink.send_queue = []
+        self.pjlink.priority_queue = [test_command]
+
+        # WHEN: _send_command called with no data and queue's emtpy
+        # Patch some attributes here since they are not available until after instantiation
+        with patch.object(self.pjlink, 'socket_timer') as mock_timer:
+            self.pjlink._send_command()
+
+            # THEN:
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            assert (not self.pjlink.send_queue), 'Send queue should be empty'
+            assert self.pjlink.priority_queue, 'Priority queue should have one entry'
+            assert (not mock_timer.start.called), 'Timer should not have been called'
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+            assert self.pjlink.send_busy, 'send_busy flag should be True'
+
+    # ------------ Test PJLink.send_command ----------
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_send_command_add_normal_command(self, mock_log, mock_send_command, mock_reset, mock_state):
+        """
+        Test send_command adding normal queue item
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = []
+        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name)),
+                           call('({ip}) send_command(): Adding to normal queue'.format(ip=self.pjlink.name))]
+        mock_state.return_value = S_CONNECTED
+
+        # Patch here since pjlink does not have priority or send queue's until instantiated
+        with patch.object(self.pjlink, 'send_queue') as mock_send, \
+                patch.object(self.pjlink, 'priority_queue') as mock_priority:
+
+            # WHEN: send_command called with valid normal command
+            self.pjlink.send_command(cmd='CLSS')
+
+            # THEN:
+            mock_send.append.called_with(test_command)
+            mock_priority.append.called is False
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.error.assert_has_calls(log_error_calls)
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+            assert mock_send_command.called, '_underscore_send_command() should have been called'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_send_command_add_priority_command(self, mock_log, mock_send_command, mock_reset, mock_state):
+        """
+        Test _send_command adding priority queue item
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = []
+        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name)),
+                           call('({ip}) send_command(): Adding to priority queue'.format(ip=self.pjlink.name))]
+        mock_state.return_value = S_CONNECTED
+
+        # Patch here since pjlink does not have priority or send queue's until instantiated
+        with patch.object(self.pjlink, 'send_queue') as mock_send, \
+                patch.object(self.pjlink, 'priority_queue') as mock_priority:
+
+            # WHEN: send_command called with valid priority command
+            self.pjlink.send_command(cmd='CLSS', priority=True)
+
+            # THEN:
+            mock_log.debug.assert_has_calls(log_debug_calls)
+            mock_log.warning.assert_has_calls(log_warning_calls)
+            mock_log.error.assert_has_calls(log_error_calls)
+            mock_priority.append.assert_called_with(test_command)
+            assert (not mock_send.append.called), 'send_queue should not have changed'
+            assert (not mock_reset.called), 'reset_information() should not have been called'
+            assert mock_send_command.called, '_underscore_send_command() should have been called'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_send_command_duplicate_normal_command(self, mock_log, mock_send_command, mock_reset, mock_state):
+        """
+        Test send_command with duplicate item for normal queue
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = [call('({ip}) send_command(): Already in normal queue - '
+                                  'skipping'.format(ip=self.pjlink.name))]
+        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name))]
+        mock_state.return_value = S_CONNECTED
+        self.pjlink.send_queue = [test_command]
+        self.pjlink.priority_queue = []
+
+        # WHEN: send_command called with same command in normal queue
+        self.pjlink.send_command(cmd='CLSS')
+
+        # THEN:
+        mock_log.debug.assert_has_calls(log_debug_calls)
+        mock_log.warning.assert_has_calls(log_warning_calls)
+        mock_log.error.assert_has_calls(log_error_calls)
+        assert (self.pjlink.send_queue == [test_command]), 'Send queue should have one entry'
+        assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+        assert (not mock_reset.called), 'reset_information() should not have been called'
+        assert mock_send_command.called, '_underscore_send_command() should have been called'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_send_command_duplicate_priority_command(self, mock_log, mock_send_command, mock_reset, mock_state):
+        """
+        Test send_command with duplicate item for priority queue
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = []
+        log_warning_calls = [call('({ip}) send_command(): Already in priority queue - '
+                                  'skipping'.format(ip=self.pjlink.name))]
+        log_debug_calls = [call('({ip}) send_command(): Building cmd="CLSS" opts="?"'.format(ip=self.pjlink.name))]
+        mock_state.return_value = S_CONNECTED
+        self.pjlink.send_queue = []
+        self.pjlink.priority_queue = [test_command]
+
+        # WHEN: send_command called with same command in priority queue
+        self.pjlink.send_command(cmd='CLSS', priority=True)
+
+        # THEN:
+        mock_log.debug.assert_has_calls(log_debug_calls)
+        mock_log.warning.assert_has_calls(log_warning_calls)
+        mock_log.error.assert_has_calls(log_error_calls)
+        assert (not self.pjlink.send_queue), 'Send queue should be empty'
+        assert (self.pjlink.priority_queue == [test_command]), 'Priority queue should have one entry'
+        assert (not mock_reset.called), 'reset_information() should not have been called'
+        assert mock_send_command.called, '_underscore_send_command() should have been called'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_send_command_invalid_command_empty_queues(self, mock_log, mock_send_command, mock_reset, mock_state):
+        """
+        Test send_command with invalid command
+        """
+        # GIVEN: Test object
+        log_error_calls = [call('({ip}) send_command(): Invalid command requested - '
+                                'ignoring.'.format(ip=self.pjlink.name))]
+        log_warning_calls = []
+        log_debug_calls = []
+        mock_state.return_value = S_CONNECTED
+        self.pjlink.send_queue = []
+        self.pjlink.priority_queue = []
+
+        # WHEN: send_command with invalid command
+        self.pjlink.send_command(cmd='DONTCARE')
+
+        # THEN:
+        mock_log.debug.assert_has_calls(log_debug_calls)
+        mock_log.warning.assert_has_calls(log_warning_calls)
+        mock_log.error.assert_has_calls(log_error_calls)
+        assert (not self.pjlink.send_queue), 'Send queue should be empty'
+        assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+        assert (not mock_reset.called), 'reset_information() should not have been called'
+        assert (not mock_send_command.called), '_underscore_send_command() should not have been called'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_send_command_invalid_command_normal_queue(self, mock_log, mock_send_command, mock_reset, mock_state):
+        """
+        Test _send_command with invalid command for normal queue
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = [call('({ip}) send_command(): Invalid command requested - '
+                                'ignoring.'.format(ip=self.pjlink.name))]
+        log_warning_calls = []
+        log_debug_calls = []
+        mock_state.return_value = S_CONNECTED
+        self.pjlink.send_queue = [test_command]
+        self.pjlink.priority_queue = []
+
+        # WHEN: send_command with invalid command
+        self.pjlink.send_command(cmd='DONTCARE')
+
+        # THEN:
+        mock_log.debug.assert_has_calls(log_debug_calls)
+        mock_log.warning.assert_has_calls(log_warning_calls)
+        mock_log.error.assert_has_calls(log_error_calls)
+        assert self.pjlink.send_queue, 'Send queue should have one entry'
+        assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+        assert (not mock_reset.called), 'reset_information() should not have been called'
+        assert mock_send_command.called, '_underscore_send_command() should have been called'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_send_command_invalid_command_priority_queue(self, mock_log, mock_send_command, mock_reset, mock_state):
+        """
+        Test _send_command with invalid command for priority queue
+        """
+        # GIVEN: Test object
+        test_command = '{prefix}{clss}CLSS ?{suff}'.format(prefix=PJLINK_PREFIX,
+                                                           clss=self.pjlink.pjlink_class,
+                                                           suff=PJLINK_SUFFIX)
+        log_error_calls = [call('({ip}) send_command(): Invalid command requested - '
+                                'ignoring.'.format(ip=self.pjlink.name))]
+        log_warning_calls = []
+        log_debug_calls = []
+        mock_state.return_value = S_CONNECTED
+        self.pjlink.send_queue = []
+        self.pjlink.priority_queue = [test_command]
+
+        # WHEN: send_command with invalid command
+        self.pjlink.send_command(cmd='DONTCARE', priority=True)
+
+        # THEN:
+        mock_log.debug.assert_has_calls(log_debug_calls)
+        mock_log.warning.assert_has_calls(log_warning_calls)
+        mock_log.error.assert_has_calls(log_error_calls)
+        assert (not self.pjlink.send_queue), 'Send queue should be empty'
+        assert self.pjlink.priority_queue, 'Priority queue should have one entry'
+        assert (not mock_reset.called), 'reset_information() should not have been called'
+        assert mock_send_command.called, '_underscore_send_command() should have been called'
+
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
+    @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
+    @patch.object(openlp.core.projectors.pjlink, 'log')
+    def test_send_command_not_connected(self, mock_log, mock_send_command, mock_reset, mock_state):
+        """
+        Test send_command when not connected
+        """
+        # GIVEN: Test object
+        log_error_calls = []
+        log_warning_calls = [call('({ip}) send_command(): Not connected - returning'.format(ip=self.pjlink.name))]
+        log_debug_calls = []
+        mock_state.return_value = S_NOT_CONNECTED
+        self.pjlink.send_queue = []
+        self.pjlink.priority_queue = []
+
+        # WHEN: send_command called when not connected
+        self.pjlink.send_command(cmd=None)
+
+        # THEN:
+        mock_log.debug.assert_has_calls(log_debug_calls)
+        mock_log.warning.assert_has_calls(log_warning_calls)
+        mock_log.error.assert_has_calls(log_error_calls)
+        assert (not self.pjlink.send_queue), 'Send queue should be empty'
+        assert (not self.pjlink.priority_queue), 'Priority queue should be empty'
+        assert mock_reset.called, 'reset_information() should have been called'
+        assert (not mock_send_command.called), '_underscore_send_command() should not have been called'

=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py'
--- tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py	2019-04-28 07:10:59 +0000
+++ tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py	2019-04-28 07:10:59 +0000
@@ -59,15 +59,16 @@
         # GIVEN: Test object
         self.pjlink.pjlink_functions = MagicMock()
         log_warning_text = [call('({ip}) get_data(): Invalid packet - '
-                                 'unknown command "UNKN"'.format(ip=self.pjlink.name))]
+                                 'unknown command "UNKN"'.format(ip=self.pjlink.name)),
+                            call('({ip}) _send_command(): Nothing to send - '
+                                 'returning'.format(ip=self.pjlink.name))]
         log_debug_text = [call('(___TEST_ONE___) get_data(buffer="%1UNKN=Huh?"'),
                           call('(___TEST_ONE___) get_data(): Checking new data "%1UNKN=Huh?"'),
                           call('(___TEST_ONE___) get_data() header="%1UNKN" data="Huh?"'),
                           call('(___TEST_ONE___) get_data() version="1" cmd="UNKN"'),
                           call('(___TEST_ONE___) Cleaning buffer - msg = "get_data(): '
                                'Invalid packet - unknown command "UNKN""'),
-                          call('(___TEST_ONE___) Finished cleaning buffer - 0 bytes dropped'),
-                          call('(___TEST_ONE___) _send_command(): Nothing to send - returning')]
+                          call('(___TEST_ONE___) Finished cleaning buffer - 0 bytes dropped')]
         # WHEN: get_data called with an unknown command
         self.pjlink.get_data(buff='{prefix}1UNKN=Huh?'.format(prefix=PJLINK_PREFIX))
 


Follow ups