Ken Roberts has proposed merging lp:~alisonken1/openlp/pjlink2-s into lp:openlp.
Commit message: PJLink2 update S Requested reviews: OpenLP Core (openlp-core) For more details, see: https://code.launchpad.net/~alisonken1/openlp/pjlink2-s/+merge/348698 - Convert action.remove raise ValueError on missing action to log.warning - Added 2-second status timer to update icons during projector state change - Update set power methods to use status timer - Update set shutter methods to use status timer - Fix process_avmt to include missing valid status from projector - Update projector info menu to include version 2 information - Add E_SOCKET_TIMEOUT to STATUS_ICONS dictionary - Change 'Action "{action}" does not exist' from raise ValueError to log.warning - Update projector to include PJLink2 extras - Fix version check exception if server returns status code != 200 - Fix version tests for status code - Fix actions tests for remove action not in list - Fix projector tests for changed calls - Update setup.cfg to inlude pycodestyle (pep8 deprecaded) - PEP8 fix for scripts/lp-merge.txt -------------------------------------------------------------------------------- lp:~alisonken1/openlp/pjlink2-s (revision 2821) https://ci.openlp.io/job/Branch-01-Pull/2537/ [SUCCESS] https://ci.openlp.io/job/Branch-02a-Linux-Tests/2435/ [SUCCESS] https://ci.openlp.io/job/Branch-02b-macOS-Tests/224/ [FAILURE] https://ci.openlp.io/job/Branch-03a-Build-Source/126/ [SUCCESS] https://ci.openlp.io/job/Branch-03b-Build-macOS/119/ [SUCCESS] https://ci.openlp.io/job/Branch-04a-Code-Analysis/1588/ [SUCCESS] https://ci.openlp.io/job/Branch-04b-Test-Coverage/1401/ [SUCCESS] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/322/ [FAILURE] -- Your team OpenLP Core is requested to review the proposed merge of lp:~alisonken1/openlp/pjlink2-s into lp:openlp.
=== modified file 'openlp/core/common/actions.py' --- openlp/core/common/actions.py 2017-12-29 09:15:48 +0000 +++ openlp/core/common/actions.py 2018-06-28 15:48:46 +0000 @@ -113,7 +113,7 @@ if item[1] == action: self.actions.remove(item) return - raise ValueError('Action "{action}" does not exist.'.format(action=action)) + log.warning('Action "{action}" does not exist.'.format(action=action)) class CategoryList(object): === modified file 'openlp/core/projectors/manager.py' --- openlp/core/projectors/manager.py 2018-05-19 00:48:33 +0000 +++ openlp/core/projectors/manager.py 2018-06-28 15:48:46 +0000 @@ -36,8 +36,8 @@ from openlp.core.lib.ui import create_widget_action from openlp.core.projectors import DialogSourceStyle from openlp.core.projectors.constants import E_AUTHENTICATION, E_ERROR, E_NETWORK, E_NOT_CONNECTED, \ - E_UNKNOWN_SOCKET_ERROR, S_CONNECTED, S_CONNECTING, S_COOLDOWN, S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, \ - S_STANDBY, S_WARMUP, PJLINK_PORT, STATUS_CODE, STATUS_MSG, QSOCKET_STATE + E_SOCKET_TIMEOUT, E_UNKNOWN_SOCKET_ERROR, S_CONNECTED, S_CONNECTING, S_COOLDOWN, S_INITIALIZE, \ + S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP, PJLINK_PORT, STATUS_CODE, STATUS_MSG, QSOCKET_STATE from openlp.core.projectors.db import ProjectorDB from openlp.core.projectors.editform import ProjectorEditForm @@ -62,6 +62,7 @@ S_COOLDOWN: ':/projector/projector_cooldown.png', E_ERROR: ':/projector/projector_error.png', E_NETWORK: ':/projector/projector_not_connected_error.png', + E_SOCKET_TIMEOUT: ':/projector/projector_not_connected_error.png', E_AUTHENTICATION: ':/projector/projector_not_connected_error.png', E_UNKNOWN_SOCKET_ERROR: ':/projector/projector_not_connected_error.png', E_NOT_CONNECTED: ':/projector/projector_not_connected_error.png' @@ -360,22 +361,14 @@ self.connect_action.setVisible(not visible) self.disconnect_action.setVisible(visible) self.status_action.setVisible(visible) - if visible: - self.select_input_action.setVisible(real_projector.link.power == S_ON) - self.edit_input_action.setVisible(real_projector.link.power == S_ON) - self.poweron_action.setVisible(real_projector.link.power == S_STANDBY) - self.poweroff_action.setVisible(real_projector.link.power == S_ON) - self.blank_action.setVisible(real_projector.link.power == S_ON and - not real_projector.link.shutter) - self.show_action.setVisible(real_projector.link.power == S_ON and - real_projector.link.shutter) - else: - self.select_input_action.setVisible(False) - self.edit_input_action.setVisible(False) - self.poweron_action.setVisible(False) - self.poweroff_action.setVisible(False) - self.blank_action.setVisible(False) - self.show_action.setVisible(False) + self.select_input_action.setVisible(visible and real_projector.link.power == S_ON) + self.edit_input_action.setVisible(visible and real_projector.link.power == S_ON) + self.poweron_action.setVisible(visible and real_projector.link.power == S_STANDBY) + self.poweroff_action.setVisible(visible and real_projector.link.power == S_ON) + self.blank_action.setVisible(visible and real_projector.link.power == S_ON and + not real_projector.link.shutter) + self.show_action.setVisible(visible and real_projector.link.power == S_ON and + real_projector.link.shutter) self.menu.projector = real_projector self.menu.exec(self.projector_list_widget.mapToGlobal(point)) @@ -516,11 +509,10 @@ except (AttributeError, TypeError): pass # Disconnect signals from projector being deleted - if self.pjlink_udp[projector.port]: - try: - self.pjlink_udp[projector.port].data_received.disconnect(projector.get_buffer) - except (AttributeError, TypeError): - pass + try: + self.pjlink_udp[projector.link.port].data_received.disconnect(projector.link.get_buffer) + except (AttributeError, TypeError): + pass # Rebuild projector list new_list = [] @@ -650,6 +642,21 @@ data=projector.link.manufacturer) message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Model'), data=projector.link.model) + message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'PJLink Class'), + data=projector.link.pjlink_class) + if projector.link.pjlink_class != 1: + message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', + 'Software Version'), + data=projector.link.sw_version) + message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', + 'Serial Number'), + data=projector.link.serial_no) + message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', + 'Lamp Model Number'), + data=projector.link.model_lamp) + message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', + 'Filter Model Number'), + data=projector.link.model_filter) message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager', 'Other info'), data=projector.link.other_info) @@ -663,20 +670,6 @@ source=translate('OpenLP.ProjectorManager', 'Current source input is'), selected=projector.link.source) - if projector.link.pjlink_class == '2': - # Information only available for PJLink Class 2 projectors - message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager', - 'Serial Number'), - data=projector.serial_no) - message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager', - 'Software Version'), - data=projector.sw_version) - message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager', - 'Lamp type'), - data=projector.model_lamp) - message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager', - 'Filter type'), - data=projector.model_filter) count = 1 for item in projector.link.lamp: if item['On'] is None: @@ -957,6 +950,10 @@ self.poll_time = None self.socket_timeout = None self.status = S_NOT_CONNECTED + self.serial_no = None + self.sw_version = None + self.model_filter = None + self.model_lamp = None super().__init__() === modified file 'openlp/core/projectors/pjlink.py' --- openlp/core/projectors/pjlink.py 2018-05-19 00:48:33 +0000 +++ openlp/core/projectors/pjlink.py 2018-06-28 15:48:46 +0000 @@ -59,7 +59,7 @@ PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PREFIX, PJLINK_PORT, PJLINK_POWR_STATUS, \ PJLINK_SUFFIX, PJLINK_VALID_CMD, PROJECTOR_STATE, STATUS_CODE, STATUS_MSG, QSOCKET_STATE, \ E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_NETWORK, E_NOT_CONNECTED, E_SOCKET_TIMEOUT, \ - S_CONNECTED, S_CONNECTING, S_NOT_CONNECTED, S_OFF, S_OK, S_ON + S_CONNECTED, S_CONNECTING, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STANDBY log = logging.getLogger(__name__) log.debug('pjlink loaded') @@ -233,6 +233,10 @@ if hasattr(self, 'socket_timer'): log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.entry.name)) self.socket_timer.stop() + if hasattr(self, 'status_timer'): + log.debug('({ip}): Calling status_timer.stop()'.format(ip=self.entry.name)) + self.status_timer.stop() + self.status_timer_checks = {} self.send_busy = False self.send_queue = [] self.priority_queue = [] @@ -287,14 +291,18 @@ """ Process shutter and speaker status. See PJLink specification for format. Update self.mute (audio) and self.shutter (video shutter). + 10 = Shutter open, audio unchanged 11 = Shutter closed, audio unchanged + 20 = Shutter unchanged, Audio normal 21 = Shutter unchanged, Audio muted - 30 = Shutter closed, audio muted - 31 = Shutter open, audio normal + 30 = Shutter open, audio muted + 31 = Shutter closed, audio normal :param data: Shutter and audio status """ - settings = {'11': {'shutter': True, 'mute': self.mute}, + settings = {'10': {'shutter': False, 'mute': self.mute}, + '11': {'shutter': True, 'mute': self.mute}, + '20': {'shutter': self.shutter, 'mute': False}, '21': {'shutter': self.shutter, 'mute': True}, '30': {'shutter': False, 'mute': False}, '31': {'shutter': True, 'mute': True} @@ -309,6 +317,8 @@ self.shutter = shutter self.mute = mute if update_icons: + if 'AVMT' in self.status_timer_checks: + self.status_timer_delete('AVMT') self.projectorUpdateIcons.emit() return @@ -592,6 +602,8 @@ else: # Log unknown status response log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.entry.name, data=data)) + if self.power in [S_ON, S_STANDBY, S_OFF] and 'POWR' in self.status_timer_checks: + self.status_timer_delete(cmd='POWR') return def process_rfil(self, data): @@ -734,6 +746,11 @@ self.socket_timer = QtCore.QTimer(self) self.socket_timer.setInterval(self.socket_timeout) self.socket_timer.timeout.connect(self.socket_abort) + # Timer for doing status updates for commands that change state and should update faster + self.status_timer_checks = {} # Keep track of events for the status timer + self.status_timer = QtCore.QTimer(self) + self.status_timer.setInterval(2000) # 2 second interval should be fast enough + self.status_timer.timeout.connect(self.status_timer_update) # Socket status signals self.connected.connect(self.check_login) self.disconnected.connect(self.disconnect_from_host) @@ -1191,12 +1208,12 @@ self.change_status(S_NOT_CONNECTED) self.reset_information() - def get_av_mute_status(self): + def get_av_mute_status(self, priority=False): """ Send command to retrieve shutter status. """ log.debug('({ip}) Sending AVMT command'.format(ip=self.entry.name)) - return self.send_command(cmd='AVMT') + return self.send_command(cmd='AVMT', priority=priority) def get_available_inputs(self): """ @@ -1254,12 +1271,14 @@ log.debug('({ip}) Sending INFO command'.format(ip=self.entry.name)) return self.send_command(cmd='INFO') - def get_power_status(self): + def get_power_status(self, priority=False): """ Send command to retrieve power status. + + :param priority: (OPTIONAL) Send in priority queue """ log.debug('({ip}) Sending POWR command'.format(ip=self.entry.name)) - return self.send_command(cmd='POWR') + return self.send_command(cmd='POWR', priority=priority) def set_input_source(self, src=None): """ @@ -1283,6 +1302,7 @@ """ log.debug('({ip}) Setting POWR to 1 (on)'.format(ip=self.entry.name)) self.send_command(cmd='POWR', opts='1', priority=True) + self.status_timer_add(cmd='POWR', callback=self.get_power_status) self.poll_loop() def set_power_off(self): @@ -1291,6 +1311,7 @@ """ log.debug('({ip}) Setting POWR to 0 (standby)'.format(ip=self.entry.name)) self.send_command(cmd='POWR', opts='0', priority=True) + self.status_timer_add(cmd='POWR', callback=self.get_power_status) self.poll_loop() def set_shutter_closed(self): @@ -1299,6 +1320,7 @@ """ log.debug('({ip}) Setting AVMT to 11 (shutter closed)'.format(ip=self.entry.name)) self.send_command(cmd='AVMT', opts='11', priority=True) + self.status_timer_add('AVMT', self.get_av_mute_status) self.poll_loop() def set_shutter_open(self): @@ -1307,8 +1329,51 @@ """ log.debug('({ip}) Setting AVMT to "10" (shutter open)'.format(ip=self.entry.name)) self.send_command(cmd='AVMT', opts='10', priority=True) + self.status_timer_add('AVMT', self.get_av_mute_status) self.poll_loop() - self.projectorUpdateIcons.emit() + + def status_timer_add(self, cmd, callback): + """ + Add a callback to the status timer. + + :param cmd: PJLink command associated with callback + :param callback: Method to call + """ + if cmd in self.status_timer_checks: + log.warning('({ip}) "{cmd}" already in checks - returning'.format(ip=self.entry.name, cmd=cmd)) + return + log.debug('({ip}) Adding "{cmd}" callback for status timer'.format(ip=self.entry.name, cmd=cmd)) + if not self.status_timer.isActive(): + self.status_timer.start() + self.status_timer_checks[cmd] = callback + + def status_timer_delete(self, cmd): + """ + Delete a callback from the status timer. + + :param cmd: PJLink command associated with callback + :param callback: Method to call + """ + if cmd not in self.status_timer_checks: + log.warning('({ip}) "{cmd}" not listed in status timer - returning'.format(ip=self.entry.name, cmd=cmd)) + return + log.debug('({ip}) Removing "{cmd}" from status timer'.format(ip=self.entry.name, cmd=cmd)) + self.status_timer_checks.pop(cmd) + if not self.status_timer_checks: + self.status_timer.stop() + + def status_timer_update(self): + """ + Call methods defined in status_timer_checks for updates + """ + if not self.status_timer_checks: + log.warning('({ip}) status_timer_update() called when no callbacks - ' + 'Race condition?'.format(ip=self.entry.name)) + self.status_timer.stop() + return + for cmd, callback in self.status_timer_checks.items(): + log.debug('({ip}) Status update call for {cmd}'.format(ip=self.entry.name, cmd=cmd)) + callback(priority=True) def receive_data_signal(self): """ === modified file 'openlp/core/version.py' --- openlp/core/version.py 2018-03-29 15:54:55 +0000 +++ openlp/core/version.py 2018-06-28 15:48:46 +0000 @@ -89,6 +89,10 @@ while retries < 3: try: response = requests.get(download_url, headers=headers) + if response.status_code != 200: + log.warn('Server returned status code {code}: ' + 'Version update check not available.'.format(code=response.status_code)) + break remote_version = response.text.strip() log.debug('New version found: %s', remote_version) break === modified file 'scripts/lp-merge.py' --- scripts/lp-merge.py 2018-05-04 21:14:04 +0000 +++ scripts/lp-merge.py 2018-06-28 15:48:46 +0000 @@ -103,8 +103,8 @@ merge_info['branch_url'] = span_branch_url.contents[0] # Find the p tag that contains the commit message # <div id="commit-message">...<div id="edit-commit_message">...<div class="yui3-editable_text-text"><p> - commit_message = soup.find('div', id='commit-message').find('div', id='edit-commit_message')\ - .find('div', 'yui3-editable_text-text').p + commit_message = soup.find('div', id='commit-message').find('div', id='edit-commit_message') \ + .find('div', 'yui3-editable_text-text').p merge_info['commit_message'] = commit_message.string # Find all tr-tags with this class. Makes it possible to get bug numbers. # <tr class="bug-branch-summary" === modified file 'setup.cfg' --- setup.cfg 2017-07-07 23:43:50 +0000 +++ setup.cfg 2018-06-28 15:48:46 +0000 @@ -12,3 +12,8 @@ max-line-length = 120 ignore = E402 +[pycodestyle] +exclude = resources.py,vlc.py +max-line-length = 120 +ignore = E402 + === modified file 'tests/functional/openlp_core/common/test_actions.py' --- tests/functional/openlp_core/common/test_actions.py 2017-12-29 09:15:48 +0000 +++ tests/functional/openlp_core/common/test_actions.py 2018-06-28 15:48:46 +0000 @@ -23,10 +23,11 @@ Package to test the openlp.core.common.actions package. """ from unittest import TestCase -from unittest.mock import MagicMock +from unittest.mock import MagicMock, call, patch from PyQt5 import QtGui, QtCore, QtWidgets +import openlp.core.common.actions from openlp.core.common.actions import CategoryActionList, ActionList from openlp.core.common.settings import Settings from tests.helpers.testmixin import TestMixin @@ -139,8 +140,21 @@ # THEN: Now the element should not be in the list anymore. assert self.action1 not in self.list - # THEN: Check if an exception is raised when trying to remove a not present action. - self.assertRaises(ValueError, self.list.remove, self.action2) + def test_remove_not_in_list(self): + """ + Test the remove() method when action not in list + """ + with patch.object(openlp.core.common.actions, 'log') as mock_log: + log_warn_calls = [call('Action "" does not exist.')] + + # GIVEN: The list + self.list.append(self.action1) + + # WHEN: Delete an item not in the list. + self.list.remove('') + + # THEN: Warning should be logged + mock_log.warning.assert_has_calls(log_warn_calls) class TestActionList(TestCase, TestMixin): === modified file 'tests/functional/openlp_core/test_version.py' --- tests/functional/openlp_core/test_version.py 2018-01-02 21:00:54 +0000 +++ tests/functional/openlp_core/test_version.py 2018-06-28 15:48:46 +0000 @@ -54,7 +54,7 @@ current_version = {'full': '2.0', 'version': '2.0', 'build': None} mock_platform.system.return_value = 'Linux' mock_platform.release.return_value = '4.12.0-1-amd64' - mock_requests.get.return_value = MagicMock(text='2.4.6') + mock_requests.get.return_value = MagicMock(text='2.4.6', status_code=200) worker = VersionWorker(last_check_date, current_version) # WHEN: The worker is run @@ -79,7 +79,7 @@ current_version = {'full': '2.1.3', 'version': '2.1.3', 'build': None} mock_platform.system.return_value = 'Linux' mock_platform.release.return_value = '4.12.0-1-amd64' - mock_requests.get.return_value = MagicMock(text='2.4.6') + mock_requests.get.return_value = MagicMock(text='2.4.6', status_code=200) worker = VersionWorker(last_check_date, current_version) # WHEN: The worker is run @@ -104,7 +104,7 @@ current_version = {'full': '2.1-bzr2345', 'version': '2.1', 'build': '2345'} mock_platform.system.return_value = 'Linux' mock_platform.release.return_value = '4.12.0-1-amd64' - mock_requests.get.return_value = MagicMock(text='2.4.6') + mock_requests.get.return_value = MagicMock(text='2.4.6', status_code=200) worker = VersionWorker(last_check_date, current_version) # WHEN: The worker is run === modified file 'tests/openlp_core/projectors/test_projector_pjlink_base_01.py' --- tests/openlp_core/projectors/test_projector_pjlink_base_01.py 2018-05-03 14:58:50 +0000 +++ tests/openlp_core/projectors/test_projector_pjlink_base_01.py 2018-06-28 15:48:46 +0000 @@ -290,7 +290,7 @@ # THEN: log data and send_command should have been called mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) + mock_send_command.assert_called_once_with(cmd=test_data, priority=False) def test_projector_get_available_inputs(self): """ @@ -470,7 +470,7 @@ # THEN: log data and send_command should have been called mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) + mock_send_command.assert_called_once_with(cmd=test_data, priority=False) def test_projector_get_status_invalid(self): """
_______________________________________________ Mailing list: https://launchpad.net/~openlp-core Post to : openlp-core@lists.launchpad.net Unsubscribe : https://launchpad.net/~openlp-core More help : https://help.launchpad.net/ListHelp