diff --git a/openlp/core/common/actions.py b/openlp/core/common/actions.py
index f4fbdd9db..c620695a2 100644
--- a/openlp/core/common/actions.py
+++ b/openlp/core/common/actions.py
@@ -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):
diff --git a/openlp/core/projectors/manager.py b/openlp/core/projectors/manager.py
index 0bb2d7022..c2575c60a 100644
--- a/openlp/core/projectors/manager.py
+++ b/openlp/core/projectors/manager.py
@@ -37,8 +37,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
@@ -50,6 +50,26 @@ log = logging.getLogger(__name__)
log.debug('projectormanager loaded')
+# Dict for matching projector status to display icon
+STATUS_ICONS = {
+ S_NOT_CONNECTED: ':/projector/projector_item_disconnect.png',
+ S_CONNECTING: ':/projector/projector_item_connect.png',
+ S_CONNECTED: ':/projector/projector_off.png',
+ S_OFF: ':/projector/projector_off.png',
+ S_INITIALIZE: ':/projector/projector_off.png',
+ S_STANDBY: ':/projector/projector_off.png',
+ S_WARMUP: ':/projector/projector_warmup.png',
+ S_ON: ':/projector/projector_on.png',
+ 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'
+}
+
+
class UiProjectorManager(object):
"""
UI part of the Projector Manager
@@ -292,6 +312,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
S_COOLDOWN: UiIcons().projector_cooldown,
E_ERROR: UiIcons().projector_error,
E_NETWORK: UiIcons().error,
+ E_SOCKET_TIMEOUT: UiIcons().authentication,
E_AUTHENTICATION: UiIcons().authentication,
E_UNKNOWN_SOCKET_ERROR: UiIcons().error,
E_NOT_CONNECTED: UiIcons().projector_disconnect
@@ -359,22 +380,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))
@@ -515,11 +528,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 = []
@@ -649,6 +661,21 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
data=projector.link.manufacturer)
message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager', 'Model'),
data=projector.link.model)
+ message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager', 'PJLink Class'),
+ data=projector.link.pjlink_class)
+ if projector.link.pjlink_class != 1:
+ message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager',
+ 'Software Version'),
+ data=projector.link.sw_version)
+ message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager',
+ 'Serial Number'),
+ data=projector.link.serial_no)
+ message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager',
+ 'Lamp Model Number'),
+ data=projector.link.model_lamp)
+ message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager',
+ 'Filter Model Number'),
+ data=projector.link.model_filter)
message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager',
'Other info'),
data=projector.link.other_info)
@@ -662,20 +689,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 += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager',
- 'Serial Number'),
- data=projector.serial_no)
- message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager',
- 'Software Version'),
- data=projector.sw_version)
- message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager',
- 'Lamp type'),
- data=projector.model_lamp)
- message += '{title}: {data}
'.format(title=translate('OpenLP.ProjectorManager',
- 'Filter type'),
- data=projector.model_filter)
count = 1
for item in projector.link.lamp:
if item['On'] is None:
@@ -956,6 +969,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__()
diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py
index beeaabe1d..b2b23a188 100644
--- a/openlp/core/projectors/pjlink.py
+++ b/openlp/core/projectors/pjlink.py
@@ -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):
"""
diff --git a/scripts/lp-merge.py b/scripts/lp-merge.py
index d017821e4..9bb8ac17a 100755
--- a/scripts/lp-merge.py
+++ b/scripts/lp-merge.py
@@ -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
#
- 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. #