PJLink2 update S

This commit is contained in:
Ken Roberts 2018-06-28 08:37:37 -07:00
parent b0c31b00f2
commit bc832c7c72
9 changed files with 142 additions and 57 deletions

View File

@ -113,7 +113,7 @@ class CategoryActionList(object):
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):

View File

@ -36,8 +36,8 @@ from openlp.core.common.settings import Settings
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 @@ STATUS_ICONS = {
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 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
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 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
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 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
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 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
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 @@ class ProjectorItem(QtCore.QObject):
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__()

View File

@ -59,7 +59,7 @@ from openlp.core.projectors.constants import CONNECTION_ERRORS, PJLINK_CLASS, PJ
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 @@ class PJLinkCommands(object):
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 @@ class PJLinkCommands(object):
"""
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 @@ class PJLinkCommands(object):
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 @@ class PJLinkCommands(object):
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 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
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 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
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 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
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 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
"""
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 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
"""
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 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
"""
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 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
"""
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):
"""

View File

@ -89,6 +89,10 @@ class VersionWorker(ThreadWorker):
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

View File

@ -103,8 +103,8 @@ def get_merge_info(url):
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"

View File

@ -12,3 +12,8 @@ exclude=resources.py,vlc.py
max-line-length = 120
ignore = E402
[pycodestyle]
exclude = resources.py,vlc.py
max-line-length = 120
ignore = E402

View File

@ -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 @@ class TestCategoryActionList(TestCase):
# 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):

View File

@ -54,7 +54,7 @@ def test_worker_start(mock_requests, mock_platform):
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 @@ def test_worker_start_dev_version(mock_requests, mock_platform):
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 @@ def test_worker_start_nightly_version(mock_requests, mock_platform):
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

View File

@ -290,7 +290,7 @@ class TestPJLinkBase(TestCase):
# 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 @@ class TestPJLinkBase(TestCase):
# 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):
"""