Initial PJLink class 2 updates

This commit is contained in:
Ken Roberts 2017-05-12 02:51:56 -07:00
parent d74c9c0c81
commit a5335adbc4
4 changed files with 159 additions and 55 deletions

View File

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

View File

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

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2015 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.lib.projector.constants package.
"""
from unittest import TestCase, skip
class TestProjectorConstants(TestCase):
"""
Test specific functions in the projector constants module.
"""
@skip('Waiting for merge of ~alisonken1/openlp/pjlink2-resource-data')
def build_pjlink_video_label_test(self):
"""
Test building PJLINK_DEFAULT_CODES dictionary
"""
# GIVEN: Test data
from tests.resources.projector.data import TEST_VIDEO_CODES
# WHEN: Import projector PJLINK_DEFAULT_CODES
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
# THEN: Verify dictionary was build correctly
self.assertEquals(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match')

View File

@ -366,3 +366,18 @@ class TestPJLink(TestCase):
# THEN: send_command should have the proper authentication
self.assertEquals("{test}".format(test=mock_send_command.call_args),
"call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
@patch.object(pjlink_test, '_not_implemented')
def not_implemented_test(self, mock_not_implemented):
"""
Test pjlink1._not_implemented method being called
"""
# GIVEN: test object
pjlink = pjlink_test
test_cmd = 'TESTMEONLY'
# WHEN: A future command is called that is not implemented yet
pjlink.process_command(test_cmd, "Garbage data for test only")
# THEN: pjlink1.__not_implemented should have been called with test_cmd
mock_not_implemented.assert_called_with(test_cmd)