PJLink update F

This commit is contained in:
Ken Roberts 2017-06-24 19:21:07 -07:00
parent bd3bedcaf4
commit 0e2019faf2
7 changed files with 241 additions and 25 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,8 @@ log.debug('pjlink1 loaded')
__all__ = ['PJLink']
import re
from codecs import decode
from PyQt5 import QtCore, QtNetwork
@ -53,9 +55,12 @@ 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
# Possible future imports
# from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
# Shortcuts
SocketError = QtNetwork.QAbstractSocket.SocketError
SocketSTate = QtNetwork.QAbstractSocket.SocketState
@ -113,8 +118,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 +168,9 @@ 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
}
def reset_information(self):
@ -166,12 +178,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 +204,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):
@ -249,7 +264,10 @@ class PJLink(QtNetwork.QTcpSocket):
# Restart timer
self.timer.start()
# These commands may change during connetion
for command in ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']:
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 +280,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):
"""
@ -341,7 +391,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)
dontcare = self.readLine(self.max_size) # noqa: F841
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
@ -440,7 +490,8 @@ class PJLink(QtNetwork.QTcpSocket):
return
data_split = data.split('=')
try:
(prefix, version, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1])
(prefix, version, cmd, data) = (data_split[0][0], data_split[0][1], # noqa: F841
data_split[0][2:], data_split[1])
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()))
@ -729,11 +780,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 +907,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

@ -25,16 +25,18 @@ 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__)
# Possible future imports
# from sqlalchemy.exc import NoSuchTableError
# from sqlalchemy import inspect
# from openlp.core.common.db import drop_columns
# Initial projector DB was unversioned
__version__ = 2

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
[flake8]
exclude=resources.py,vlc.py
max-line-length = 120
ignore = E402,E722

View File

@ -22,7 +22,7 @@
"""
Package to test the openlp.core.lib.projector.constants package.
"""
from unittest import TestCase, skip
from unittest import TestCase, skip # noqa: F401
class TestProjectorConstants(TestCase):

View File

@ -29,8 +29,8 @@ import os
import shutil
from tempfile import mkdtemp
from unittest import TestCase, skip
from unittest.mock import MagicMock, patch
from unittest import TestCase, skip # noqa: F401
from unittest.mock import MagicMock, patch # noqa: F401
from openlp.core.lib.projector import upgrade
from openlp.core.lib.db import upgrade_db
@ -413,7 +413,7 @@ class TestProjectorDB(TestCase):
Test add_projector() fail
"""
# GIVEN: Test entry in the database
ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))
ignore_result = self.projector.add_projector(Projector(**TEST1_DATA)) # noqa: F841
# WHEN: Attempt to add same projector entry
results = self.projector.add_projector(Projector(**TEST1_DATA))
@ -439,7 +439,7 @@ class TestProjectorDB(TestCase):
Test update_projector() when entry not in database
"""
# GIVEN: Projector entry in database
ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))
ignore_result = self.projector.add_projector(Projector(**TEST1_DATA)) # noqa: F841
projector = Projector(**TEST2_DATA)
# WHEN: Attempt to update data with a different ID

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.assertEquals(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.assertEquals(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.assertEquals(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
@ -73,9 +165,23 @@ class TestPJLink(TestCase):
self.assertEquals(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.assertEquals(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
@ -85,7 +191,7 @@ class TestPJLink(TestCase):
# THEN: Projector class should be set with proper value
self.assertEquals(pjlink.pjlink_class, '1',
'Non-standard class reply should have set proper class')
'Non-standard class reply should have set class=1')
@patch.object(pjlink_test, 'change_status')
def test_status_change(self, mock_change_status):