forked from openlp/openlp
PJLink update F
This commit is contained in:
parent
bd3bedcaf4
commit
0e2019faf2
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
10
setup.cfg
10
setup.cfg
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user