PJlink2 update K

- Update notes in PJLink-Notes.odt/update PDF
- Add PJLink.get_socket() (process socket data)
- Add PJLink.get_buffer() (process non-socket data)
- Update PJLink.get_data() to work with get_socket/get_buffer
- Cleanup ProjectorManager code
- Fix incorrect note reference to song db in test_projector_db
- Fix projector tests for change to PJLink() creation
- Start PJLinkUDP class
- Remove unused pjlink2.py module
- Code oops cleanups

[SUCCESS] https://ci.openlp.io/job/Branch-...

bzr-revno: 2767
This commit is contained in:
Ken Roberts 2017-09-22 20:15:37 +01:00 committed by Tim Bentley
commit 3fcc58f116
10 changed files with 88 additions and 144 deletions

Binary file not shown.

Binary file not shown.

View File

@ -341,9 +341,9 @@ class ProjectorDB(Manager):
""" """
old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip) old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip)
if old_projector is not None: if old_projector is not None:
log.warning('add_new() skipping entry ip="{ip}" (Already saved)'.format(ip=old_projector.ip)) log.warning('add_projector() skipping entry ip="{ip}" (Already saved)'.format(ip=old_projector.ip))
return False return False
log.debug('add_new() saving new entry') log.debug('add_projector() saving new entry')
log.debug('ip="{ip}", name="{name}", location="{location}"'.format(ip=projector.ip, log.debug('ip="{ip}", name="{name}", location="{location}"'.format(ip=projector.ip,
name=projector.name, name=projector.name,
location=projector.location)) location=projector.location))

View File

@ -72,6 +72,28 @@ PJLINK_HEADER = '{prefix}{{linkclass}}'.format(prefix=PJLINK_PREFIX)
PJLINK_SUFFIX = CR PJLINK_SUFFIX = CR
class PJLinkUDP(QtNetwork.QUdpSocket):
"""
Socket service for PJLink UDP socket.
"""
# New commands available in PJLink Class 2
pjlink_udp_commands = [
'ACKN', # Class 2 (cmd is SRCH)
'ERST', # Class 1/2
'INPT', # Class 1/2
'LKUP', # Class 2 (reply only - no cmd)
'POWR', # Class 1/2
'SRCH' # Class 2 (reply is ACKN)
]
def __init__(self, port=PJLINK_PORT):
"""
Initialize socket
"""
self.port = port
class PJLinkCommands(object): class PJLinkCommands(object):
""" """
Process replies from PJLink projector. Process replies from PJLink projector.
@ -488,7 +510,7 @@ class PJLinkCommands(object):
class PJLink(PJLinkCommands, QtNetwork.QTcpSocket): class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
""" """
Socket service for connecting to a PJLink-capable projector. Socket service for PJLink TCP socket.
""" """
# Signals sent by this module # Signals sent by this module
changeStatus = QtCore.pyqtSignal(str, int, str) changeStatus = QtCore.pyqtSignal(str, int, str)
@ -499,43 +521,29 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing
projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar
# New commands available in PJLink Class 2 def __init__(self, projector, *args, **kwargs):
pjlink_udp_commands = [
'ACKN', # Class 2
'ERST', # Class 1 or 2
'INPT', # Class 1 or 2
'LKUP', # Class 2
'POWR', # Class 1 or 2
'SRCH' # Class 2
]
def __init__(self, port=PJLINK_PORT, *args, **kwargs):
""" """
Setup for instance. Setup for instance.
Options should be in kwargs except for port which does have a default. Options should be in kwargs except for port which does have a default.
:param name: Display name :param projector: Database record of projector
:param ip: IP address to connect to
:param port: Port to use. Default to PJLINK_PORT
:param pin: Access pin (if needed)
Optional parameters Optional parameters
:param dbid: Database ID number
:param location: Location where projector is physically located
:param notes: Extra notes about the projector
:param poll_time: Time (in seconds) to poll connected projector :param poll_time: Time (in seconds) to poll connected projector
:param socket_timeout: Time (in seconds) to abort the connection if no response :param socket_timeout: Time (in seconds) to abort the connection if no response
""" """
log.debug('PJlink(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs)) log.debug('PJlink(projector={projector}, args={args} kwargs={kwargs})'.format(projector=projector,
args=args,
kwargs=kwargs))
super().__init__() super().__init__()
self.dbid = kwargs.get('dbid') self.entry = projector
self.ip = kwargs.get('ip') self.ip = self.entry.ip
self.location = kwargs.get('location') self.location = self.entry.location
self.mac_adx = kwargs.get('mac_adx') self.mac_adx = self.entry.mac_adx
self.name = kwargs.get('name') self.name = self.entry.name
self.notes = kwargs.get('notes') self.notes = self.entry.notes
self.pin = kwargs.get('pin') self.pin = self.entry.pin
self.port = port self.port = self.entry.port
self.db_update = False # Use to check if db needs to be updated prior to exiting self.db_update = False # Use to check if db needs to be updated prior to exiting
# Poll time 20 seconds unless called with something else # Poll time 20 seconds unless called with something else
self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000 self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
@ -751,7 +759,7 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
self.change_status(E_AUTHENTICATION) self.change_status(E_AUTHENTICATION)
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip)) log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
return return
elif data_check[1] == '0' and self.pin is not None: elif (data_check[1] == '0') and (self.pin):
# Pin set and no authentication needed # Pin set and no authentication needed
log.warning('({ip}) Regular connection but PIN set'.format(ip=self.name)) log.warning('({ip}) Regular connection but PIN set'.format(ip=self.name))
self.disconnect_from_host() self.disconnect_from_host()
@ -761,7 +769,7 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
return return
elif data_check[1] == '1': elif data_check[1] == '1':
# Authenticated login with salt # Authenticated login with salt
if self.pin is None: if not self.pin:
log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.ip)) log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.ip))
self.disconnect_from_host() self.disconnect_from_host()
self.change_status(E_AUTHENTICATION) self.change_status(E_AUTHENTICATION)
@ -776,7 +784,7 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
else: else:
data_hash = None data_hash = None
# We're connected at this point, so go ahead and setup regular I/O # We're connected at this point, so go ahead and setup regular I/O
self.readyRead.connect(self.get_data) self.readyRead.connect(self.get_socket)
self.projectorReceivedData.connect(self._send_command) self.projectorReceivedData.connect(self._send_command)
# Initial data we should know about # Initial data we should know about
self.send_command(cmd='CLSS', salt=data_hash) self.send_command(cmd='CLSS', salt=data_hash)
@ -800,27 +808,51 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
count=trash_count)) count=trash_count))
return return
@QtCore.pyqtSlot(str, str)
def get_buffer(self, data, ip):
"""
Get data from somewhere other than TCP socket
:param data: Data to process. buffer must be formatted as a proper PJLink packet.
:param ip: Destination IP for buffer.
"""
log.debug("({ip}) get_buffer(data='{buff}' ip='{ip_in}'".format(ip=self.ip, buff=data, ip_in=ip))
if ip is None:
log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.ip))
return
return self.get_data(buff=data, ip=ip)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def get_data(self): def get_socket(self):
""" """
Socket interface to retrieve data. Get data from TCP socket.
""" """
log.debug('({ip}) get_data(): Reading data'.format(ip=self.ip)) log.debug('({ip}) get_socket(): Reading data'.format(ip=self.ip))
if self.state() != self.ConnectedState: if self.state() != self.ConnectedState:
log.debug('({ip}) get_data(): Not connected - returning'.format(ip=self.ip)) log.debug('({ip}) get_socket(): Not connected - returning'.format(ip=self.ip))
self.send_busy = False self.send_busy = False
return return
# Although we have a packet length limit, go ahead and use a larger buffer # Although we have a packet length limit, go ahead and use a larger buffer
read = self.readLine(1024) read = self.readLine(1024)
log.debug("({ip}) get_data(): '{buff}'".format(ip=self.ip, buff=read)) log.debug("({ip}) get_socket(): '{buff}'".format(ip=self.ip, buff=read))
if read == -1: if read == -1:
# No data available # No data available
log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip)) log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip))
return self.receive_data_signal() return self.receive_data_signal()
self.socket_timer.stop() self.socket_timer.stop()
self.projectorNetwork.emit(S_NETWORK_RECEIVED) self.projectorNetwork.emit(S_NETWORK_RECEIVED)
return self.get_data(buff=read, ip=self.ip)
def get_data(self, buff, ip):
"""
Process received data
:param buff: Data to process.
:param ip: (optional) Destination IP.
"""
log.debug("({ip}) get_data(ip='{ip_in}' buffer='{buff}'".format(ip=self.ip, ip_in=ip, buff=buff))
# NOTE: Class2 has changed to some values being UTF-8 # NOTE: Class2 has changed to some values being UTF-8
data_in = decode(read, 'utf-8') data_in = decode(buff, 'utf-8')
data = data_in.strip() data = data_in.strip()
if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)): if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)):
return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix') return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix')
@ -990,7 +1022,7 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
self.reset_information() self.reset_information()
self.disconnectFromHost() self.disconnectFromHost()
try: try:
self.readyRead.disconnect(self.get_data) self.readyRead.disconnect(self.get_socket)
except TypeError: except TypeError:
pass pass
if abort: if abort:

View File

@ -1,85 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 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 #
###############################################################################
"""
:mod:`openlp.core.lib.projector.pjlink2` module provides the PJLink Class 2
updates from PJLink Class 1.
This module only handles the UDP socket functionality. Command/query/status
change messages will still be processed by the PJLink 1 module.
Currently, the only variance is the addition of a UDP "search" command to
query the local network for Class 2 capable projectors,
and UDP "notify" messages from projectors to connected software of status
changes (i.e., power change, input change, error changes).
Differences between Class 1 and Class 2 PJLink specifications are as follows.
New Functionality:
* Search - UDP Query local network for Class 2 capabable projector(s).
* Status - UDP Status change with connected projector(s). Status change
messages consist of:
* Initial projector power up when network communication becomes available
* Lamp off/standby to warmup or on
* Lamp on to cooldown or off/standby
* Input source select change completed
* Error status change (i.e., fan/lamp/temp/cover open/filter/other error(s))
New Commands:
* Query serial number of projector
* Query version number of projector software
* Query model number of replacement lamp
* Query model number of replacement air filter
* Query current projector screen resolution
* Query recommended screen resolution
* Query name of specific input terminal (video source)
* Adjust projector microphone in 1-step increments
* Adjust projector speacker in 1-step increments
Extended Commands:
* Addition of INTERNAL terminal (video source) for a total of 6 types of terminals.
* Number of terminals (video source) has been expanded from [1-9]
to [1-9a-z] (Addition of 26 terminals for each type of input).
See PJLink Class 2 Specifications for details.
http://pjlink.jbmia.or.jp/english/dl_class2.html
Section 5-1 PJLink Specifications
Section 5-5 Guidelines for Input Terminals
"""
import logging
log = logging.getLogger(__name__)
log.debug('pjlink2 loaded')
from PyQt5 import QtNetwork
class PJLinkUDP(QtNetwork.QUdpSocket):
"""
Socket service for handling datagram (UDP) sockets.
"""
log.debug('PJLinkUDP loaded')
# Class varialbe for projector list. Should be replaced by ProjectorManager's
# projector list after being loaded there.
projector_list = None
projectors_found = None # UDP search found list

View File

@ -38,8 +38,7 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \ E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
from openlp.core.lib.projector.db import ProjectorDB from openlp.core.lib.projector.db import ProjectorDB
from openlp.core.lib.projector.pjlink import PJLink from openlp.core.lib.projector.pjlink import PJLink, PJLinkUDP
from openlp.core.lib.projector.pjlink2 import PJLinkUDP
from openlp.core.ui.projector.editform import ProjectorEditForm from openlp.core.ui.projector.editform import ProjectorEditForm
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
@ -700,16 +699,9 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:returns: PJLink() instance :returns: PJLink() instance
""" """
log.debug('_add_projector()') log.debug('_add_projector()')
return PJLink(dbid=projector.id, return PJLink(projector=projector,
ip=projector.ip,
port=int(projector.port),
name=projector.name,
location=projector.location,
notes=projector.notes,
pin=None if projector.pin == '' else projector.pin,
poll_time=self.poll_time, poll_time=self.poll_time,
socket_timeout=self.socket_timeout socket_timeout=self.socket_timeout)
)
def add_projector(self, projector, start=False): def add_projector(self, projector, start=False):
""" """

View File

@ -111,7 +111,7 @@ class TestProjectorDBUpdate(TestCase):
""" """
Test that we can upgrade an old song db to the current schema Test that we can upgrade an old song db to the current schema
""" """
# GIVEN: An old song db # GIVEN: An old prjector db
old_db = os.path.join(TEST_RESOURCES_PATH, "projector", TEST_DB_PJLINK1) old_db = os.path.join(TEST_RESOURCES_PATH, "projector", TEST_DB_PJLINK1)
tmp_db = os.path.join(self.tmp_folder, TEST_DB) tmp_db = os.path.join(self.tmp_folder, TEST_DB)
shutil.copyfile(old_db, tmp_db) shutil.copyfile(old_db, tmp_db)

View File

@ -25,12 +25,13 @@ Package to test the openlp.core.lib.projector.pjlink base package.
from unittest import TestCase from unittest import TestCase
from unittest.mock import call, patch, MagicMock from unittest.mock import call, patch, MagicMock
from openlp.core.lib.projector.db import Projector
from openlp.core.lib.projector.pjlink import PJLink from openlp.core.lib.projector.pjlink import PJLink
from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA
pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True) pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
class TestPJLinkBase(TestCase): class TestPJLinkBase(TestCase):

View File

@ -27,6 +27,7 @@ from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
import openlp.core.lib.projector.pjlink import openlp.core.lib.projector.pjlink
from openlp.core.lib.projector.db import Projector
from openlp.core.lib.projector.pjlink import PJLink from openlp.core.lib.projector.pjlink import PJLink
from openlp.core.lib.projector.constants import PJLINK_ERRORS, \ from openlp.core.lib.projector.constants import PJLINK_ERRORS, \
E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED
@ -35,9 +36,10 @@ from openlp.core.lib.projector.constants import PJLINK_ERRORS, \
from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
''' '''
from tests.resources.projector.data import TEST_PIN from tests.resources.projector.data import TEST_PIN, TEST1_DATA
pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True) pjlink_test = PJLink(Projector(**TEST1_DATA), pin=TEST_PIN, no_poll=True)
pjlink_test.ip = '127.0.0.1'
class TestPJLinkRouting(TestCase): class TestPJLinkRouting(TestCase):

View File

@ -26,15 +26,17 @@ from unittest import TestCase
from unittest.mock import patch from unittest.mock import patch
import openlp.core.lib.projector.pjlink import openlp.core.lib.projector.pjlink
from openlp.core.lib.projector.db import Projector
from openlp.core.lib.projector.pjlink import PJLink from openlp.core.lib.projector.pjlink import PJLink
from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \ from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
PJLINK_POWR_STATUS, \ PJLINK_POWR_STATUS, \
E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \ E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \
S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY
from tests.resources.projector.data import TEST_PIN from tests.resources.projector.data import TEST_PIN, TEST1_DATA
pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True) pjlink_test = PJLink(Projector(**TEST1_DATA), pin=TEST_PIN, no_poll=True)
pjlink_test.ip = '127.0.0.1'
# Create a list of ERST positional data so we don't have to redo the same buildup multiple times # Create a list of ERST positional data so we don't have to redo the same buildup multiple times
PJLINK_ERST_POSITIONS = [] PJLINK_ERST_POSITIONS = []