-- Renamed test_projector_clss to clss_one

-- Added test_projector_clss_two
-- Updated  PJLink non-standard class check
-- Added process_snum
-- Added tests for process_snum
-- Renamed tests/functional/openlp_core_lib/test_projectordb.py to test_projector_db.py
-- Added filter model command (rfil) -- Added lamp model command (rlmp)
-- Added tests for filter/model commands
-- Fix typo in projector status window
-- Cleanups from commit notes
-- Added invalid data buffer cleanups
-- Added calls ...

bzr-revno: 2753
This commit is contained in:
Ken Roberts 2017-07-31 22:03:10 -07:00 committed by Raoul Snyman
commit b60a8b5a3e
9 changed files with 316 additions and 121 deletions

View File

@ -303,7 +303,7 @@ class ProjectorDB(Manager):
:param ip: Host IP/Name
:returns: Projector() instance
"""
log.debug('get_projector_by_ip(ip="%s")' % ip)
log.debug('get_projector_by_ip(ip="{ip}")'.format(ip=ip))
projector = self.get_object_filtered(Projector, Projector.ip == ip)
if projector is None:
# Not found

View File

@ -44,6 +44,7 @@ log.debug('pjlink1 loaded')
__all__ = ['PJLink']
import re
from codecs import decode
from PyQt5 import QtCore, QtNetwork
@ -53,7 +54,7 @@ from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG
E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \
E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, \
PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
PJLINK_DEFAULT_CODES, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \
STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \
S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS
# Shortcuts
@ -113,8 +114,13 @@ class PJLink(QtNetwork.QTcpSocket):
self.port = port
self.pin = pin
super().__init__()
self.model_lamp = None
self.model_filter = None
self.mac_adx = kwargs.get('mac_adx')
self.serial_no = None
self.serial_no_received = None # Used only if saved serial number is different than received serial number
self.dbid = None
self.db_update = False # Use to check if db needs to be updated prior to exiting
self.location = None
self.notes = None
self.dbid = kwargs.get('dbid')
@ -158,7 +164,11 @@ class PJLink(QtNetwork.QTcpSocket):
'LAMP': self.process_lamp,
'NAME': self.process_name,
'PJLINK': self.check_login,
'POWR': self.process_powr
'POWR': self.process_powr,
'SNUM': self.process_snum,
'SVER': self.process_sver,
'RFIL': self.process_rfil,
'RLMP': self.process_rlmp
}
def reset_information(self):
@ -166,12 +176,16 @@ class PJLink(QtNetwork.QTcpSocket):
Reset projector-specific information to default
"""
log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state()))
self.send_queue = []
self.power = S_OFF
self.pjlink_name = None
self.manufacturer = None
self.model = None
self.serial_no = None
self.serial_no_received = None
self.sw_version = None
self.sw_version_received = None
self.mac_adx = None
self.shutter = None
self.mute = None
self.lamp = None
@ -188,7 +202,6 @@ class PJLink(QtNetwork.QTcpSocket):
if hasattr(self, 'socket_timer'):
log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.ip))
self.socket_timer.stop()
self.send_queue = []
self.send_busy = False
def thread_started(self):
@ -240,6 +253,7 @@ class PJLink(QtNetwork.QTcpSocket):
Normally called by timer().
"""
if self.state() != self.ConnectedState:
log.warn("({ip}) poll_loop(): Not connected - returning".format(ip=self.ip))
return
log.debug('({ip}) Updating projector status'.format(ip=self.ip))
# Reset timer in case we were called from a set command
@ -248,8 +262,11 @@ class PJLink(QtNetwork.QTcpSocket):
self.timer.setInterval(self.poll_time)
# Restart timer
self.timer.start()
# These commands may change during connetion
for command in ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']:
# These commands may change during connection
check_list = ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']
if self.pjlink_class == '2':
check_list.extend(['FILT', 'FREZ'])
for command in check_list:
self.send_command(command, queue=True)
# The following commands do not change, so only check them once
if self.power == S_ON and self.source_available is None:
@ -262,6 +279,38 @@ class PJLink(QtNetwork.QTcpSocket):
self.send_command('INF2', queue=True)
if self.pjlink_name is None:
self.send_command('NAME', queue=True)
if self.pjlink_class == '2':
# Class 2 specific checks
if self.serial_no is None:
self.send_command('SNUM', queue=True)
if self.sw_version is None:
self.send_command('SVER', queue=True)
if self.model_filter is None:
self.send_command('RFIL', queue=True)
if self.model_lamp is None:
self.send_command('RLMP', queue=True)
def process_rfil(self, data):
"""
Process replacement filter type
"""
if self.model_filter is None:
self.model_filter = data
else:
log.warn("({ip}) Filter model already set".format(ip=self.ip))
log.warn("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter))
log.warn("({ip}) New model: '{new}'".format(ip=self.ip, new=data))
def process_rlmp(self, data):
"""
Process replacement lamp type
"""
if self.model_lamp is None:
self.model_lamp = data
else:
log.warn("({ip}) Lamp model already set".format(ip=self.ip))
log.warn("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp))
log.warn("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data))
def _get_status(self, status):
"""
@ -331,7 +380,7 @@ class PJLink(QtNetwork.QTcpSocket):
self.change_status(E_SOCKET_TIMEOUT)
return
read = self.readLine(self.max_size)
dontcare = self.readLine(self.max_size) # Clean out the trailing \r\n
self.readLine(self.max_size) # Clean out the trailing \r\n
if read is None:
log.warning('({ip}) read is None - socket error?'.format(ip=self.ip))
return
@ -341,7 +390,7 @@ class PJLink(QtNetwork.QTcpSocket):
data = decode(read, 'utf-8')
# Possibility of extraneous data on input when reading.
# Clean out extraneous characters in buffer.
dontcare = self.readLine(self.max_size)
self.readLine(self.max_size)
log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip()))
# At this point, we should only have the initial login prompt with
# possible authentication
@ -363,24 +412,24 @@ class PJLink(QtNetwork.QTcpSocket):
# Authentication error
self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.name))
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
return
elif data_check[1] == '0' and self.pin is not None:
# Pin set and no authentication needed
log.warning('({ip}) Regular connection but PIN set'.format(ip=self.name))
self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('({ip}) Emitting projectorNoAuthentication() signal'.format(ip=self.name))
log.debug('({ip}) Emitting projectorNoAuthentication() signal'.format(ip=self.ip))
self.projectorNoAuthentication.emit(self.name)
return
elif data_check[1] == '1':
# Authenticated login with salt
if self.pin is None:
log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.name))
log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.ip))
self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('({ip}) Emitting projectorAuthentication() signal'.format(ip=self.name))
self.projectorAuthentication.emit(self.name)
log.debug('({ip}) Emitting projectorAuthentication() signal'.format(ip=self.ip))
self.projectorAuthentication.emit(self.ip)
return
else:
log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2]))
@ -400,6 +449,20 @@ class PJLink(QtNetwork.QTcpSocket):
self.timer.setInterval(2000) # Set 2 seconds for initial information
self.timer.start()
def _trash_buffer(self, msg=None):
"""
Clean out extraneous stuff in the buffer.
"""
log.warning("({ip}) {message}".format(ip=self.ip, message='Invalid packet' if msg is None else msg))
self.send_busy = False
trash_count = 0
while self.bytesAvailable() > 0:
trash = self.read(self.max_size)
trash_count += len(trash)
log.debug("({ip}) Finished cleaning buffer - {count} bytes dropped".format(ip=self.ip,
count=trash_count))
return
@QtCore.pyqtSlot()
def get_data(self):
"""
@ -414,43 +477,27 @@ class PJLink(QtNetwork.QTcpSocket):
if read == -1:
# No data available
log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip))
self.send_busy = False
self.projectorReceivedData.emit()
return
return self.receive_data_signal()
self.socket_timer.stop()
self.projectorNetwork.emit(S_NETWORK_RECEIVED)
# 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
log.debug('({ip}) get_data(): Packet length < 7: "{data}"'.format(ip=self.ip, data=data))
self.receive_data_signal()
return
if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)):
return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix')
elif '=' not in data:
log.warning('({ip}) get_data(): Invalid packet received'.format(ip=self.ip))
self.receive_data_signal()
return
return self._trash_buffer(msg='get_data(): Invalid packet does not have equal')
log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data))
# At this point, we should have something to work with
if data.upper().startswith('PJLINK'):
# Reconnected from remote host disconnect ?
self.check_login(data)
self.receive_data_signal()
return
data_split = data.split('=')
header, data = data.split('=')
try:
(prefix, version, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1])
version, cmd = header[1], header[2:]
except ValueError as e:
log.warning('({ip}) get_data(): Invalid packet - expected header + command + data'.format(ip=self.ip))
log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip()))
self.change_status(E_INVALID_DATA)
self.receive_data_signal()
return
log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip()))
return self._trash_buffer('get_data(): Expected header + command + data')
if cmd not in PJLINK_VALID_CMD:
log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd))
self.receive_data_signal()
return
return self._trash_buffer(msg='get_data(): Unknown command "{data}"'.format(data=cmd))
if int(self.pjlink_class) < int(version):
log.warn('({ip}) get_data(): Projector returned class reply higher '
'than projector stated class'.format(ip=self.ip))
@ -506,7 +553,7 @@ class PJLink(QtNetwork.QTcpSocket):
salt='' if salt is None
else ' with hash'))
cmd_ver = PJLINK_VALID_CMD[cmd]['version']
if self.pjlink_class in cmd_ver:
if self.pjlink_class in PJLINK_VALID_CMD[cmd]['version']:
header = PJLINK_HEADER.format(linkclass=self.pjlink_class)
elif len(cmd_ver) == 1 and (int(cmd_ver[0]) < int(self.pjlink_class)):
# Typically a class 1 only command
@ -575,6 +622,7 @@ class PJLink(QtNetwork.QTcpSocket):
self.waitForBytesWritten(2000) # 2 seconds should be enough
if sent == -1:
# Network error?
log.warning("({ip}) _send_command(): -1 received".format(ip=self.ip))
self.change_status(E_NETWORK,
translate('OpenLP.PJLink', 'Error while sending data to projector'))
@ -617,20 +665,17 @@ class PJLink(QtNetwork.QTcpSocket):
elif data.upper() == 'ERR4':
# Projector/display error
self.change_status(E_PROJECTOR)
self.send_busy = False
self.projectorReceivedData.emit()
self.receive_data_signal()
return
# Command succeeded - no extra information
elif data.upper() == 'OK':
log.debug('({ip}) Command returned OK'.format(ip=self.ip))
# A command returned successfully, recheck data
self.send_busy = False
self.projectorReceivedData.emit()
# A command returned successfully
self.receive_data_signal()
return
# Command checks already passed
log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
self.send_busy = False
self.projectorReceivedData.emit()
self.receive_data_signal()
self.pjlink_functions[cmd](data)
def process_lamp(self, data):
@ -729,11 +774,22 @@ class PJLink(QtNetwork.QTcpSocket):
:param data: Class that projector supports.
"""
# bug 1550891: Projector returns non-standard class response:
# : Expected: %1CLSS=1
# : Received: %1CLSS=Class 1
# : Expected: '%1CLSS=1'
# : Received: '%1CLSS=Class 1' (Optoma)
# : Received: '%1CLSS=Version1' (BenQ)
if len(data) > 1:
# Split non-standard information from response
clss = data.split()[-1]
log.warn("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data))
# Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
# AND the different responses that can be received, the semi-permanent way to
# fix the class reply is to just remove all non-digit characters.
try:
clss = re.findall('\d', data)[0] # Should only be the first match
except IndexError:
log.error("({ip}) No numbers found in class version reply - defaulting to class '1'".format(ip=self.ip))
clss = '1'
elif not data.isdigit():
log.error("({ip}) NAN class version reply - defaulting to class '1'".format(ip=self.ip))
clss = '1'
else:
clss = data
self.pjlink_class = clss
@ -845,6 +901,42 @@ class PJLink(QtNetwork.QTcpSocket):
PJLINK_ERST_STATUS[data[5]]
return
def process_snum(self, data):
"""
Serial number of projector.
:param data: Serial number from projector.
"""
if self.serial_no is None:
log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data))
self.serial_no = data
self.db_update = False
else:
# Compare serial numbers and see if we got the same projector
if self.serial_no != data:
log.warn("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip))
log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no))
log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
self.serial_no_received = data
def process_sver(self, data):
"""
Software version of projector
"""
if self.sw_version is None:
log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data))
self.sw_version = data
self.db_update = True
else:
# Compare software version and see if we got the same projector
if self.serial_no != data:
log.warn("({ip}) Projector software version does not match saved software version".format(ip=self.ip))
log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version))
log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
self.sw_version_received = data
def connect_to_host(self):
"""
Initiate connection to projector.

View File

@ -71,10 +71,10 @@ log = logging.getLogger(__name__)
log.debug('pjlink2 loaded')
from PyQt5 import QtCore, QtNetwork
from PyQt5 import QtNetwork
class PJLinkUDP(QtNetwork.QTcpSocket):
class PJLinkUDP(QtNetwork.QUdpSocket):
"""
Socket service for handling datagram (UDP) sockets.
"""

View File

@ -25,12 +25,9 @@ backend for the projector setup.
"""
import logging
# Not all imports used at this time, but keep for future upgrades
from sqlalchemy import Table, Column, types, inspect
from sqlalchemy.exc import NoSuchTableError
from sqlalchemy import Table, Column, types
from sqlalchemy.sql.expression import null
from openlp.core.common.db import drop_columns
from openlp.core.lib.db import get_upgrade_op
log = logging.getLogger(__name__)

View File

@ -420,9 +420,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_shutter_closed()
opt.link.set_shutter_closed()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -455,9 +453,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.connect_to_host()
opt.link.connect_to_host()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -527,7 +523,8 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
self.projector_list = new_list
list_item = self.projector_list_widget.takeItem(self.projector_list_widget.currentRow())
list_item = None
deleted = self.projectordb.delete_projector(projector.db_item)
if not self.projectordb.delete_projector(projector.db_item):
log.warning('Delete projector {item} failed'.format(item=projector.db_item))
for item in self.projector_list:
log.debug('New projector list - item: {ip} {name}'.format(ip=item.link.ip, name=item.link.name))
@ -538,9 +535,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.disconnect_from_host()
opt.link.disconnect_from_host()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -573,9 +568,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_power_off()
opt.link.set_power_off()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -593,9 +586,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_power_on()
opt.link.set_power_on()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -613,9 +604,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_shutter_open()
opt.link.set_shutter_open()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -662,9 +651,10 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
data=translate('OpenLP.ProjectorManager', 'Closed')
if projector.link.shutter
else translate('OpenLP', 'Open'))
message = '%s<b>%s</b>: %s<br />' % (message,
translate('OpenLP.ProjectorManager', 'Current source input is'),
projector.link.source)
message = '{msg}<b>{source}</b>: {selected}<br />'.format(msg=message,
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',
@ -685,10 +675,10 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
'Lamp'),
count=count,
status=translate('OpenLP.ProjectorManager',
' is on')
'ON')
if item['On']
else translate('OpenLP.ProjectorManager',
'is off'))
'OFF'))
message += '<b>{title}</b>: {hours}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Hours'),
hours=item['Hours'])

View File

@ -393,9 +393,9 @@ class SourceSelectSingle(QtWidgets.QDialog):
QtCore.Qt.WindowCloseButtonHint)
self.edit = edit
if self.edit:
title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
self.title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
else:
title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
self.title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
self.setObjectName('source_select_single')
self.setWindowIcon(build_icon(':/icon/openlp-log.svg'))
self.setModal(True)

View File

@ -1,4 +1,14 @@
# E402 module level import not at top of file
# E722 do not use bare except, specify exception instead
# F841 local variable '<variable>' is assigned to but never used
[pep8]
exclude=resources.py,vlc.py
max-line-length = 120
ignore = E402,E722
ignore = E402
[flake8]
exclude=resources.py,vlc.py
max-line-length = 120
ignore = E402

View File

@ -59,9 +59,101 @@ class TestPJLink(TestCase):
self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
"Connection request should have been called with TEST_PIN"))
def test_projector_class(self):
def test_projector_process_rfil_save(self):
"""
Test class version from projector
Test saving filter type
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.model_filter = None
filter_model = 'Filter Type Test'
# WHEN: Filter model is received
pjlink.process_rfil(data=filter_model)
# THEN: Filter model number should be saved
self.assertEqual(pjlink.model_filter, filter_model, 'Filter type should have been saved')
def test_projector_process_rfil_nosave(self):
"""
Test saving filter type previously saved
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.model_filter = 'Old filter type'
filter_model = 'Filter Type Test'
# WHEN: Filter model is received
pjlink.process_rfil(data=filter_model)
# THEN: Filter model number should be saved
self.assertNotEquals(pjlink.model_filter, filter_model, 'Filter type should NOT have been saved')
def test_projector_process_rlmp_save(self):
"""
Test saving lamp type
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.model_lamp = None
lamp_model = 'Lamp Type Test'
# WHEN: Filter model is received
pjlink.process_rlmp(data=lamp_model)
# THEN: Filter model number should be saved
self.assertEqual(pjlink.model_lamp, lamp_model, 'Lamp type should have been saved')
def test_projector_process_rlmp_nosave(self):
"""
Test saving lamp type previously saved
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.model_lamp = 'Old lamp type'
lamp_model = 'Filter Type Test'
# WHEN: Filter model is received
pjlink.process_rlmp(data=lamp_model)
# THEN: Filter model number should be saved
self.assertNotEquals(pjlink.model_lamp, lamp_model, 'Lamp type should NOT have been saved')
def test_projector_process_snum_set(self):
"""
Test saving serial number from projector
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.serial_no = None
test_number = 'Test Serial Number'
# WHEN: No serial number is set and we receive serial number command
pjlink.process_snum(data=test_number)
# THEN: Serial number should be set
self.assertEqual(pjlink.serial_no, test_number,
'Projector serial number should have been set')
def test_projector_process_snum_different(self):
"""
Test projector serial number different than saved serial number
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.serial_no = 'Previous serial number'
test_number = 'Test Serial Number'
# WHEN: No serial number is set and we receive serial number command
pjlink.process_snum(data=test_number)
# THEN: Serial number should be set
self.assertNotEquals(pjlink.serial_no, test_number,
'Projector serial number should NOT have been set')
def test_projector_clss_one(self):
"""
Test class 1 sent from projector
"""
# GIVEN: Test object
pjlink = pjlink_test
@ -70,12 +162,26 @@ class TestPJLink(TestCase):
pjlink.process_clss('1')
# THEN: Projector class should be set to 1
self.assertEquals(pjlink.pjlink_class, '1',
'Projector should have returned class=1')
self.assertEqual(pjlink.pjlink_class, '1',
'Projector should have returned class=1')
def test_non_standard_class_reply(self):
def test_projector_clss_two(self):
"""
Bugfix 1550891: CLSS request returns non-standard 'Class N' reply
Test class 2 sent from projector
"""
# GIVEN: Test object
pjlink = pjlink_test
# WHEN: Process class response
pjlink.process_clss('2')
# THEN: Projector class should be set to 1
self.assertEqual(pjlink.pjlink_class, '2',
'Projector should have returned class=2')
def test_bug_1550891_non_standard_class_reply(self):
"""
Bugfix 1550891: CLSS request returns non-standard reply
"""
# GIVEN: Test object
pjlink = pjlink_test
@ -84,8 +190,8 @@ class TestPJLink(TestCase):
pjlink.process_clss('Class 1')
# THEN: Projector class should be set with proper value
self.assertEquals(pjlink.pjlink_class, '1',
'Non-standard class reply should have set proper class')
self.assertEqual(pjlink.pjlink_class, '1',
'Non-standard class reply should have set class=1')
@patch.object(pjlink_test, 'change_status')
def test_status_change(self, mock_change_status):
@ -131,10 +237,10 @@ class TestPJLink(TestCase):
pjlink.process_command('LAMP', '22222 1')
# THEN: Lamp should have been set with status=ON and hours=22222
self.assertEquals(pjlink.lamp[0]['On'], True,
'Lamp power status should have been set to TRUE')
self.assertEquals(pjlink.lamp[0]['Hours'], 22222,
'Lamp hours should have been set to 22222')
self.assertEqual(pjlink.lamp[0]['On'], True,
'Lamp power status should have been set to TRUE')
self.assertEqual(pjlink.lamp[0]['Hours'], 22222,
'Lamp hours should have been set to 22222')
@patch.object(pjlink_test, 'projectorReceivedData')
def test_projector_process_multiple_lamp(self, mock_projectorReceivedData):
@ -148,20 +254,20 @@ class TestPJLink(TestCase):
pjlink.process_command('LAMP', '11111 1 22222 0 33333 1')
# THEN: Lamp should have been set with proper lamp status
self.assertEquals(len(pjlink.lamp), 3,
'Projector should have 3 lamps specified')
self.assertEquals(pjlink.lamp[0]['On'], True,
'Lamp 1 power status should have been set to TRUE')
self.assertEquals(pjlink.lamp[0]['Hours'], 11111,
'Lamp 1 hours should have been set to 11111')
self.assertEquals(pjlink.lamp[1]['On'], False,
'Lamp 2 power status should have been set to FALSE')
self.assertEquals(pjlink.lamp[1]['Hours'], 22222,
'Lamp 2 hours should have been set to 22222')
self.assertEquals(pjlink.lamp[2]['On'], True,
'Lamp 3 power status should have been set to TRUE')
self.assertEquals(pjlink.lamp[2]['Hours'], 33333,
'Lamp 3 hours should have been set to 33333')
self.assertEqual(len(pjlink.lamp), 3,
'Projector should have 3 lamps specified')
self.assertEqual(pjlink.lamp[0]['On'], True,
'Lamp 1 power status should have been set to TRUE')
self.assertEqual(pjlink.lamp[0]['Hours'], 11111,
'Lamp 1 hours should have been set to 11111')
self.assertEqual(pjlink.lamp[1]['On'], False,
'Lamp 2 power status should have been set to FALSE')
self.assertEqual(pjlink.lamp[1]['Hours'], 22222,
'Lamp 2 hours should have been set to 22222')
self.assertEqual(pjlink.lamp[2]['On'], True,
'Lamp 3 power status should have been set to TRUE')
self.assertEqual(pjlink.lamp[2]['Hours'], 33333,
'Lamp 3 hours should have been set to 33333')
@patch.object(pjlink_test, 'projectorReceivedData')
@patch.object(pjlink_test, 'projectorUpdateIcons')
@ -182,9 +288,9 @@ class TestPJLink(TestCase):
pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_ON])
# THEN: Power should be set to ON
self.assertEquals(pjlink.power, S_ON, 'Power should have been set to ON')
self.assertEqual(pjlink.power, S_ON, 'Power should have been set to ON')
mock_send_command.assert_called_once_with('INST')
self.assertEquals(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
@patch.object(pjlink_test, 'projectorReceivedData')
@patch.object(pjlink_test, 'projectorUpdateIcons')
@ -205,9 +311,9 @@ class TestPJLink(TestCase):
pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_STANDBY])
# THEN: Power should be set to STANDBY
self.assertEquals(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
self.assertEquals(mock_send_command.called, False, 'send_command should not have been called')
self.assertEquals(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
self.assertEqual(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
self.assertEqual(mock_send_command.called, False, 'send_command should not have been called')
self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
@patch.object(pjlink_test, 'projectorUpdateIcons')
def test_projector_process_avmt_closed_unmuted(self, mock_projectorReceivedData):
@ -289,7 +395,7 @@ class TestPJLink(TestCase):
pjlink.process_inpt('1')
# THEN: Input selected should reflect current input
self.assertEquals(pjlink.source, '1', 'Input source should be set to "1"')
self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"')
def test_projector_reset_information(self):
"""
@ -318,7 +424,7 @@ class TestPJLink(TestCase):
pjlink.reset_information()
# THEN: All information should be reset and timers stopped
self.assertEquals(pjlink.power, S_OFF, 'Projector power should be OFF')
self.assertEqual(pjlink.power, S_OFF, 'Projector power should be OFF')
self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None')
self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None')
self.assertIsNone(pjlink.model, 'Projector model should be None')
@ -329,7 +435,7 @@ class TestPJLink(TestCase):
self.assertIsNone(pjlink.source_available, 'Projector source_available should be None')
self.assertIsNone(pjlink.source, 'Projector source should be None')
self.assertIsNone(pjlink.other_info, 'Projector other_info should be None')
self.assertEquals(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
self.assertEqual(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False')
self.assertTrue(mock_timer.called, 'Projector timer.stop() should have been called')
self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called')
@ -355,8 +461,8 @@ class TestPJLink(TestCase):
# WHEN: call with authentication request and pin not set
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
# THEN: No Authentication signal should have been sent
mock_authentication.emit.assert_called_with(pjlink.name)
# THEN: 'No Authentication' signal should have been sent
mock_authentication.emit.assert_called_with(pjlink.ip)
@patch.object(pjlink_test, 'waitForReadyRead')
@patch.object(pjlink_test, 'state')
@ -381,8 +487,8 @@ class TestPJLink(TestCase):
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
# 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))
self.assertEqual("{test}".format(test=mock_send_command.call_args),
"call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
@patch.object(pjlink_test, 'disconnect_from_host')
def socket_abort_test(self, mock_disconnect):