2014-10-06 19:10:03 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
|
|
# --------------------------------------------------------------------------- #
|
2017-12-29 09:15:48 +00:00
|
|
|
# Copyright (c) 2008-2018 OpenLP Developers #
|
2014-10-06 19:10:03 +00:00
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
# 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 #
|
|
|
|
###############################################################################
|
|
|
|
"""
|
2017-10-07 07:05:07 +00:00
|
|
|
The :mod:`openlp.core.lib.projector.pjlink` module provides the necessary functions for connecting to a PJLink-capable
|
|
|
|
projector.
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-10-07 07:05:07 +00:00
|
|
|
PJLink Class 1 Specifications
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-10-07 07:05:07 +00:00
|
|
|
Website: http://pjlink.jbmia.or.jp/english/dl_class1.html
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-10-07 07:05:07 +00:00
|
|
|
- Section 5-1 PJLink Specifications
|
|
|
|
- Section 5-5 Guidelines for Input Terminals
|
|
|
|
|
|
|
|
PJLink Class 2 Specifications
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
Website: http://pjlink.jbmia.or.jp/english/dl_class2.html
|
|
|
|
|
|
|
|
- Section 5-1 PJLink Specifications
|
|
|
|
- Section 5-5 Guidelines for Input Terminals
|
|
|
|
|
|
|
|
.. note:
|
|
|
|
Function names follow the following syntax::
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-10-07 07:05:07 +00:00
|
|
|
def process_CCCC(...):
|
|
|
|
|
|
|
|
where ``CCCC`` is the PJLink command being processed
|
|
|
|
"""
|
|
|
|
import logging
|
2017-06-25 02:21:07 +00:00
|
|
|
import re
|
2014-10-17 17:28:12 +00:00
|
|
|
from codecs import decode
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2016-07-18 21:36:16 +00:00
|
|
|
from PyQt5 import QtCore, QtNetwork
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-10-07 07:05:07 +00:00
|
|
|
from openlp.core.common import qmd5_hash
|
|
|
|
from openlp.core.common.i18n import translate
|
2017-12-25 08:44:30 +00:00
|
|
|
from openlp.core.projectors.constants import CONNECTION_ERRORS, PJLINK_CLASS, PJLINK_DEFAULT_CODES, PJLINK_ERRORS, \
|
|
|
|
PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PREFIX, PJLINK_PORT, PJLINK_POWR_STATUS, \
|
|
|
|
PJLINK_SUFFIX, PJLINK_VALID_CMD, PROJECTOR_STATE, STATUS_CODE, STATUS_MSG, QSOCKET_STATE, \
|
2018-04-20 06:04:43 +00:00
|
|
|
E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_NETWORK, E_NOT_CONNECTED, E_SOCKET_TIMEOUT, \
|
2018-01-13 05:41:42 +00:00
|
|
|
S_CONNECTED, S_CONNECTING, S_NOT_CONNECTED, S_OFF, S_OK, S_ON
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-10-07 07:05:07 +00:00
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
log.debug('pjlink loaded')
|
|
|
|
|
2018-02-11 11:42:13 +00:00
|
|
|
__all__ = ['PJLink', 'PJLinkUDP']
|
2017-10-07 07:05:07 +00:00
|
|
|
|
2014-10-06 19:10:03 +00:00
|
|
|
# Shortcuts
|
2016-07-18 21:36:16 +00:00
|
|
|
SocketError = QtNetwork.QAbstractSocket.SocketError
|
|
|
|
SocketSTate = QtNetwork.QAbstractSocket.SocketState
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-05-13 09:00:29 +00:00
|
|
|
# Add prefix here, but defer linkclass expansion until later when we have the actual
|
|
|
|
# PJLink class for the command
|
|
|
|
PJLINK_HEADER = '{prefix}{{linkclass}}'.format(prefix=PJLINK_PREFIX)
|
2014-10-06 19:10:03 +00:00
|
|
|
|
|
|
|
|
2017-09-22 12:03:28 +00:00
|
|
|
class PJLinkUDP(QtNetwork.QUdpSocket):
|
|
|
|
"""
|
|
|
|
Socket service for PJLink UDP socket.
|
|
|
|
"""
|
2017-12-23 21:53:13 +00:00
|
|
|
def __init__(self, projector_list, port=PJLINK_PORT):
|
2017-09-22 12:03:28 +00:00
|
|
|
"""
|
2018-02-11 11:42:13 +00:00
|
|
|
Socket services for PJLink UDP packets.
|
|
|
|
|
|
|
|
Since all UDP packets from any projector will come into the same
|
|
|
|
port, process UDP packets here then route to the appropriate
|
|
|
|
projector instance as needed.
|
2017-09-22 12:03:28 +00:00
|
|
|
"""
|
2018-02-11 11:42:13 +00:00
|
|
|
# Keep track of currently defined projectors so we can route
|
|
|
|
# inbound packets to the correct instance
|
|
|
|
super().__init__()
|
2017-12-23 21:53:13 +00:00
|
|
|
self.projector_list = projector_list
|
2017-09-22 12:03:28 +00:00
|
|
|
self.port = port
|
2018-02-11 11:42:13 +00:00
|
|
|
# Local defines
|
|
|
|
self.search_active = False
|
|
|
|
self.search_time = 30000 # 30 seconds for allowed time
|
|
|
|
self.search_timer = QtCore.QTimer()
|
|
|
|
self.readyRead.connect(self.get_datagram)
|
|
|
|
log.debug('(UDP) PJLinkUDP() Initialized')
|
|
|
|
|
|
|
|
@QtCore.pyqtSlot()
|
|
|
|
def get_datagram(self):
|
|
|
|
"""
|
|
|
|
Retrieve packet and basic checks
|
|
|
|
"""
|
|
|
|
log.debug('(UDP) get_datagram() - Receiving data')
|
2018-04-20 06:04:43 +00:00
|
|
|
read_size = self.pendingDatagramSize()
|
|
|
|
if read_size < 0:
|
|
|
|
log.warning('(UDP) No data (-1)')
|
2018-02-11 11:42:13 +00:00
|
|
|
return
|
2018-04-20 06:04:43 +00:00
|
|
|
if read_size < 1:
|
|
|
|
log.warning('(UDP) get_datagram() called when pending data size is 0')
|
2018-02-11 11:42:13 +00:00
|
|
|
return
|
|
|
|
data, peer_address, peer_port = self.readDatagram(self.pendingDatagramSize())
|
|
|
|
log.debug('(UDP) {size} bytes received from {adx} on port {port}'.format(size=len(data),
|
|
|
|
adx=peer_address,
|
|
|
|
port=peer_port))
|
|
|
|
log.debug('(UDP) packet "{data}"'.format(data=data))
|
2018-04-20 06:04:43 +00:00
|
|
|
# Send to appropriate instance to process packet
|
|
|
|
log.debug('(UDP) Checking projector list for ip {host} to process'.format(host=peer_address))
|
|
|
|
for projector in self.projector_list:
|
|
|
|
if peer_address == projector.ip:
|
|
|
|
# Dispatch packet to appropriate remote instance
|
|
|
|
log.debug('(UDP) Dispatching packet to {host}'.format(host=projector.entry.name))
|
|
|
|
return projector.get_data(buff=data, ip=peer_address, host=peer_address, port=peer_port)
|
|
|
|
log.warning('(UDP) Could not find projector with ip {ip} to process packet'.format(ip=peer_address))
|
2018-02-11 11:42:13 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def search_start(self):
|
|
|
|
"""
|
|
|
|
Start search for projectors on local network
|
|
|
|
"""
|
|
|
|
self.search_active = True
|
|
|
|
self.ackn_list = {}
|
|
|
|
# TODO: Send SRCH packet here
|
|
|
|
self.search_timer.singleShot(self.search_time, self.search_stop)
|
|
|
|
|
|
|
|
@QtCore.pyqtSlot()
|
|
|
|
def search_stop(self):
|
|
|
|
"""
|
|
|
|
Stop search
|
|
|
|
"""
|
|
|
|
self.search_active = False
|
|
|
|
self.search_timer.stop()
|
2017-09-22 12:03:28 +00:00
|
|
|
|
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
class PJLinkCommands(object):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Process replies from PJLink projector.
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2018-04-20 06:04:43 +00:00
|
|
|
# List of IP addresses and mac addresses found via UDP search command
|
|
|
|
ackn_list = []
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
def __init__(self, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Setup for the process commands
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
|
2017-05-30 23:26:37 +00:00
|
|
|
super().__init__()
|
2018-04-20 06:04:43 +00:00
|
|
|
# Map PJLink command to method and include pjlink class version for this instance
|
|
|
|
# Default initial pjlink class version is '1'
|
2017-05-20 05:51:58 +00:00
|
|
|
self.pjlink_functions = {
|
2018-04-20 06:04:43 +00:00
|
|
|
'ACKN': {"method": self.process_ackn, # Class 2 (command is SRCH)
|
|
|
|
"version": "2"},
|
|
|
|
'AVMT': {"method": self.process_avmt,
|
|
|
|
"version": "1"},
|
|
|
|
'CLSS': {"method": self.process_clss,
|
|
|
|
"version": "1"},
|
|
|
|
'ERST': {"method": self.process_erst,
|
|
|
|
"version": "1"},
|
|
|
|
'INFO': {"method": self.process_info,
|
|
|
|
"version": "1"},
|
|
|
|
'INF1': {"method": self.process_inf1,
|
|
|
|
"version": "1"},
|
|
|
|
'INF2': {"method": self.process_inf2,
|
|
|
|
"version": "1"},
|
|
|
|
'INPT': {"method": self.process_inpt,
|
|
|
|
"version": "1"},
|
|
|
|
'INST': {"method": self.process_inst,
|
|
|
|
"version": "1"},
|
|
|
|
'LAMP': {"method": self.process_lamp,
|
|
|
|
"version": "1"},
|
|
|
|
'LKUP': {"method": self.process_lkup, # Class 2 (reply only - no cmd)
|
|
|
|
"version": "2"},
|
|
|
|
'NAME': {"method": self.process_name,
|
|
|
|
"version": "1"},
|
|
|
|
'PJLINK': {"method": self.process_pjlink,
|
|
|
|
"version": "1"},
|
|
|
|
'POWR': {"method": self.process_powr,
|
|
|
|
"version": "1"},
|
|
|
|
'SNUM': {"method": self.process_snum,
|
|
|
|
"version": "1"},
|
|
|
|
'SRCH': {"method": self.process_srch, # Class 2 (reply is ACKN)
|
|
|
|
"version": "2"},
|
|
|
|
'SVER': {"method": self.process_sver,
|
|
|
|
"version": "1"},
|
|
|
|
'RFIL': {"method": self.process_rfil,
|
|
|
|
"version": "1"},
|
|
|
|
'RLMP': {"method": self.process_rlmp,
|
|
|
|
"version": "1"}
|
2016-07-18 21:36:16 +00:00
|
|
|
}
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2014-10-13 14:51:23 +00:00
|
|
|
def reset_information(self):
|
2014-10-17 17:28:12 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Initialize instance variables. Also used to reset projector-specific information to default.
|
2014-10-17 17:28:12 +00:00
|
|
|
"""
|
2017-12-25 08:44:30 +00:00
|
|
|
conn_state = STATUS_CODE[QSOCKET_STATE[self.state()]]
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.entry.name,
|
2017-12-25 08:44:30 +00:00
|
|
|
state=conn_state))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.fan = None # ERST
|
|
|
|
self.filter_time = None # FILT
|
|
|
|
self.lamp = None # LAMP
|
|
|
|
self.mac_adx_received = None # ACKN
|
|
|
|
self.manufacturer = None # INF1
|
|
|
|
self.model = None # INF2
|
|
|
|
self.model_filter = None # RFIL
|
|
|
|
self.model_lamp = None # RLMP
|
|
|
|
self.mute = None # AVMT
|
|
|
|
self.other_info = None # INFO
|
|
|
|
self.pjlink_name = None # NAME
|
|
|
|
self.power = S_OFF # POWR
|
|
|
|
self.serial_no = None # SNUM
|
2017-06-25 02:21:07 +00:00
|
|
|
self.serial_no_received = None
|
2017-08-06 07:23:26 +00:00
|
|
|
self.sw_version = None # SVER
|
2017-06-25 02:21:07 +00:00
|
|
|
self.sw_version_received = None
|
2017-08-06 07:23:26 +00:00
|
|
|
self.shutter = None # AVMT
|
|
|
|
self.source_available = None # INST
|
|
|
|
self.source = None # INPT
|
|
|
|
# These should be part of PJLink() class, but set here for convenience
|
2018-01-13 05:41:42 +00:00
|
|
|
if hasattr(self, 'poll_timer'):
|
|
|
|
log.debug('({ip}): Calling poll_timer.stop()'.format(ip=self.entry.name))
|
|
|
|
self.poll_timer.stop()
|
2014-10-17 02:28:51 +00:00
|
|
|
if hasattr(self, 'socket_timer'):
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.entry.name))
|
2014-10-17 02:28:51 +00:00
|
|
|
self.socket_timer.stop()
|
|
|
|
self.send_busy = False
|
2017-08-06 07:23:26 +00:00
|
|
|
self.send_queue = []
|
2017-12-04 00:24:47 +00:00
|
|
|
self.priority_queue = []
|
2018-04-20 06:04:43 +00:00
|
|
|
# Reset default version in command routing dict
|
|
|
|
for cmd in self.pjlink_functions:
|
|
|
|
self.pjlink_functions[cmd]["version"] = PJLINK_VALID_CMD[cmd]['default']
|
2014-10-13 14:51:23 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_command(self, cmd, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Verifies any return error code. Calls the appropriate command handler.
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
:param cmd: Command to process
|
|
|
|
:param data: Data being processed
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.entry.name,
|
2017-08-06 07:23:26 +00:00
|
|
|
cmd=cmd,
|
|
|
|
data=data))
|
2017-12-04 00:24:47 +00:00
|
|
|
# cmd should already be in uppercase, but data may be in mixed-case.
|
|
|
|
# Due to some replies should stay as mixed-case, validate using separate uppercase check
|
2017-08-06 23:33:53 +00:00
|
|
|
_data = data.upper()
|
2017-12-04 00:24:47 +00:00
|
|
|
# Check if we have a future command not available yet
|
2017-12-25 08:44:30 +00:00
|
|
|
if cmd not in self.pjlink_functions:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Unable to process command="{cmd}" (Future option?)'.format(ip=self.entry.name, cmd=cmd))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
2017-08-11 11:04:33 +00:00
|
|
|
elif _data == 'OK':
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.entry.name, cmd=cmd))
|
2017-12-04 00:24:47 +00:00
|
|
|
# A command returned successfully, so do a query on command to verify status
|
|
|
|
return self.send_command(cmd=cmd)
|
2017-08-06 23:33:53 +00:00
|
|
|
elif _data in PJLINK_ERRORS:
|
2017-08-06 07:23:26 +00:00
|
|
|
# Oops - projector error
|
2018-01-13 05:41:42 +00:00
|
|
|
log.error('({ip}) {cmd}: {err}'.format(ip=self.entry.name,
|
2017-12-25 08:44:30 +00:00
|
|
|
cmd=cmd,
|
|
|
|
err=STATUS_MSG[PJLINK_ERRORS[_data]]))
|
|
|
|
if PJLINK_ERRORS[_data] == E_AUTHENTICATION:
|
2017-08-06 07:23:26 +00:00
|
|
|
self.disconnect_from_host()
|
|
|
|
self.projectorAuthentication.emit(self.name)
|
2017-12-25 08:44:30 +00:00
|
|
|
return self.change_status(status=E_AUTHENTICATION)
|
2017-08-06 07:23:26 +00:00
|
|
|
# Command checks already passed
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Calling function for {cmd}'.format(ip=self.entry.name, cmd=cmd))
|
2018-04-20 06:04:43 +00:00
|
|
|
self.pjlink_functions[cmd]["method"](data=data, *args, **kwargs)
|
|
|
|
|
|
|
|
def process_ackn(self, data, host, port):
|
|
|
|
"""
|
|
|
|
Process the ACKN command.
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
:param data: Data in packet
|
|
|
|
:param host: IP address of sending host
|
|
|
|
:param port: Port received on
|
|
|
|
"""
|
|
|
|
log.debug('({ip}) Processing ACKN packet'.format(ip=self.entry.name))
|
|
|
|
if host not in self.ackn_list:
|
|
|
|
log.debug('({ip}) Adding {host} to ACKN list'.format(ip=self.entry.name, host=host))
|
|
|
|
self.ackn_list[host] = {'data': data,
|
|
|
|
'port': port}
|
|
|
|
else:
|
|
|
|
log.warning('({ip}) Host {host} already replied - ignoring'.format(ip=self.entry.name, host=host))
|
|
|
|
|
|
|
|
def process_avmt(self, data, *args, **kwargs):
|
2014-10-17 02:28:51 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Process shutter and speaker status. See PJLink specification for format.
|
|
|
|
Update self.mute (audio) and self.shutter (video shutter).
|
2017-08-11 11:04:33 +00:00
|
|
|
11 = Shutter closed, audio unchanged
|
|
|
|
21 = Shutter unchanged, Audio muted
|
|
|
|
30 = Shutter closed, audio muted
|
|
|
|
31 = Shutter open, audio normal
|
2014-10-17 02:28:51 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
:param data: Shutter and audio status
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 23:33:53 +00:00
|
|
|
settings = {'11': {'shutter': True, 'mute': self.mute},
|
|
|
|
'21': {'shutter': self.shutter, 'mute': True},
|
|
|
|
'30': {'shutter': False, 'mute': False},
|
|
|
|
'31': {'shutter': True, 'mute': True}
|
|
|
|
}
|
2017-08-11 11:04:33 +00:00
|
|
|
if data not in settings:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Invalid shutter response: {data}'.format(ip=self.entry.name, data=data))
|
2017-08-11 11:04:33 +00:00
|
|
|
return
|
|
|
|
shutter = settings[data]['shutter']
|
|
|
|
mute = settings[data]['mute']
|
|
|
|
# Check if we need to update the icons
|
|
|
|
update_icons = (shutter != self.shutter) or (mute != self.mute)
|
|
|
|
self.shutter = shutter
|
|
|
|
self.mute = mute
|
2017-08-06 07:23:26 +00:00
|
|
|
if update_icons:
|
|
|
|
self.projectorUpdateIcons.emit()
|
|
|
|
return
|
2017-06-25 02:21:07 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_clss(self, data, *args, **kwargs):
|
2017-06-25 02:21:07 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
PJLink class that this projector supports. See PJLink specification for format.
|
|
|
|
Updates self.class.
|
|
|
|
|
|
|
|
:param data: Class that projector supports.
|
2017-06-25 02:21:07 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
# bug 1550891: Projector returns non-standard class response:
|
|
|
|
# : Expected: '%1CLSS=1'
|
|
|
|
# : Received: '%1CLSS=Class 1' (Optoma)
|
|
|
|
# : Received: '%1CLSS=Version1' (BenQ)
|
|
|
|
if len(data) > 1:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Non-standard CLSS reply: "{data}"'.format(ip=self.entry.name, data=data))
|
2017-08-06 07:23:26 +00:00
|
|
|
# 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.
|
2018-04-20 06:04:43 +00:00
|
|
|
chk = re.findall('\d', data)
|
|
|
|
if len(chk) < 1:
|
2017-12-09 11:17:05 +00:00
|
|
|
log.error('({ip}) No numbers found in class version reply "{data}" - '
|
2018-01-13 05:41:42 +00:00
|
|
|
'defaulting to class "1"'.format(ip=self.entry.name, data=data))
|
2017-08-06 07:23:26 +00:00
|
|
|
clss = '1'
|
2018-04-20 06:04:43 +00:00
|
|
|
else:
|
|
|
|
clss = chk[0] # Should only be the first match
|
2017-08-06 07:23:26 +00:00
|
|
|
elif not data.isdigit():
|
2017-12-09 11:17:05 +00:00
|
|
|
log.error('({ip}) NAN CLSS version reply "{data}" - '
|
2018-01-13 05:41:42 +00:00
|
|
|
'defaulting to class "1"'.format(ip=self.entry.name, data=data))
|
2017-08-06 07:23:26 +00:00
|
|
|
clss = '1'
|
2017-06-25 02:21:07 +00:00
|
|
|
else:
|
2017-08-06 07:23:26 +00:00
|
|
|
clss = data
|
|
|
|
self.pjlink_class = clss
|
2018-02-11 11:42:13 +00:00
|
|
|
log.debug('({ip}) Setting pjlink_class for this projector '
|
|
|
|
'to "{data}"'.format(ip=self.entry.name,
|
|
|
|
data=self.pjlink_class))
|
2018-04-20 06:04:43 +00:00
|
|
|
# Update method class versions
|
|
|
|
for cmd in self.pjlink_functions:
|
|
|
|
if self.pjlink_class in PJLINK_VALID_CMD[cmd]['version']:
|
|
|
|
self.pjlink_functions[cmd]['version'] = self.pjlink_class
|
|
|
|
|
2018-01-13 05:41:42 +00:00
|
|
|
# Since we call this one on first connect, setup polling from here
|
|
|
|
if not self.no_poll:
|
|
|
|
log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.entry.name))
|
|
|
|
self.poll_timer.setInterval(1000) # Set 1 second for initial information
|
|
|
|
self.poll_timer.start()
|
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
2017-06-25 02:21:07 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_erst(self, data, *args, **kwargs):
|
2017-06-25 02:21:07 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Error status. See PJLink Specifications for format.
|
|
|
|
Updates self.projector_errors
|
|
|
|
|
2017-08-11 11:04:33 +00:00
|
|
|
:param data: Error status
|
2017-06-25 02:21:07 +00:00
|
|
|
"""
|
2017-08-11 11:04:33 +00:00
|
|
|
if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']:
|
|
|
|
count = PJLINK_ERST_DATA['DATA_LENGTH']
|
2018-02-11 11:42:13 +00:00
|
|
|
log.warning('({ip}) Invalid error status response "{data}": '
|
|
|
|
'length != {count}'.format(ip=self.entry.name,
|
|
|
|
data=data,
|
|
|
|
count=count))
|
2017-08-11 11:04:33 +00:00
|
|
|
return
|
2017-08-06 07:23:26 +00:00
|
|
|
try:
|
|
|
|
datacheck = int(data)
|
|
|
|
except ValueError:
|
|
|
|
# Bad data - ignore
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Invalid error status response "{data}"'.format(ip=self.entry.name, data=data))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
|
|
|
if datacheck == 0:
|
|
|
|
self.projector_errors = None
|
2017-08-11 11:04:33 +00:00
|
|
|
# No errors
|
|
|
|
return
|
|
|
|
# We have some sort of status error, so check out what it/they are
|
|
|
|
self.projector_errors = {}
|
|
|
|
fan, lamp, temp, cover, filt, other = (data[PJLINK_ERST_DATA['FAN']],
|
|
|
|
data[PJLINK_ERST_DATA['LAMP']],
|
|
|
|
data[PJLINK_ERST_DATA['TEMP']],
|
|
|
|
data[PJLINK_ERST_DATA['COVER']],
|
|
|
|
data[PJLINK_ERST_DATA['FILTER']],
|
|
|
|
data[PJLINK_ERST_DATA['OTHER']])
|
2017-12-25 08:44:30 +00:00
|
|
|
if fan != PJLINK_ERST_STATUS[S_OK]:
|
2017-08-11 11:04:33 +00:00
|
|
|
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \
|
|
|
|
PJLINK_ERST_STATUS[fan]
|
2017-12-25 08:44:30 +00:00
|
|
|
if lamp != PJLINK_ERST_STATUS[S_OK]:
|
2017-08-11 11:04:33 +00:00
|
|
|
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \
|
|
|
|
PJLINK_ERST_STATUS[lamp]
|
2017-12-25 08:44:30 +00:00
|
|
|
if temp != PJLINK_ERST_STATUS[S_OK]:
|
2017-08-11 11:04:33 +00:00
|
|
|
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \
|
|
|
|
PJLINK_ERST_STATUS[temp]
|
2017-12-25 08:44:30 +00:00
|
|
|
if cover != PJLINK_ERST_STATUS[S_OK]:
|
2017-08-11 11:04:33 +00:00
|
|
|
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \
|
|
|
|
PJLINK_ERST_STATUS[cover]
|
2017-12-25 08:44:30 +00:00
|
|
|
if filt != PJLINK_ERST_STATUS[S_OK]:
|
2017-08-11 11:04:33 +00:00
|
|
|
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \
|
|
|
|
PJLINK_ERST_STATUS[filt]
|
2017-12-25 08:44:30 +00:00
|
|
|
if other != PJLINK_ERST_STATUS[S_OK]:
|
2017-08-11 11:04:33 +00:00
|
|
|
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \
|
|
|
|
PJLINK_ERST_STATUS[other]
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_inf1(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Manufacturer name set in projector.
|
|
|
|
Updates self.manufacturer
|
2014-10-17 17:28:12 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
:param data: Projector manufacturer
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
self.manufacturer = data
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting projector manufacturer data to "{data}"'.format(ip=self.entry.name,
|
|
|
|
data=self.manufacturer))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_inf2(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Projector Model set in projector.
|
|
|
|
Updates self.model.
|
2014-10-17 17:28:12 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
:param data: Model name
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
self.model = data
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.entry.name, data=self.model))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_info(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Any extra info set in projector.
|
|
|
|
Updates self.other_info.
|
2016-06-17 23:54:04 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
:param data: Projector other info
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
self.other_info = data
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.entry.name, data=self.other_info))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_inpt(self, data, *args, **kwargs):
|
2017-07-07 23:43:50 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Current source input selected. See PJLink specification for format.
|
|
|
|
Update self.source
|
|
|
|
|
|
|
|
:param data: Currently selected source
|
2017-07-07 23:43:50 +00:00
|
|
|
"""
|
2017-12-25 08:44:30 +00:00
|
|
|
# First, see if we have a valid input based on what is installed (if available)
|
|
|
|
if self.source_available is not None:
|
|
|
|
# We have available inputs, so verify it's in the list
|
|
|
|
if data not in self.source_available:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warn('({ip}) Input source not listed in available sources - ignoring'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
return
|
|
|
|
elif data not in PJLINK_DEFAULT_CODES:
|
|
|
|
# Hmm - no sources available yet, so check with PJLink defaults
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warn('({ip}) Input source not listed as a PJLink available source '
|
|
|
|
'- ignoring'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
return
|
2017-08-06 07:23:26 +00:00
|
|
|
self.source = data
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting data source to "{data}"'.format(ip=self.entry.name, data=self.source))
|
2017-07-07 23:43:50 +00:00
|
|
|
return
|
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_inst(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Available source inputs. See PJLink specification for format.
|
|
|
|
Updates self.source_available
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
:param data: Sources list
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
sources = []
|
|
|
|
check = data.split()
|
|
|
|
for source in check:
|
|
|
|
sources.append(source)
|
|
|
|
sources.sort()
|
|
|
|
self.source_available = sources
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.entry.name,
|
2017-08-06 07:23:26 +00:00
|
|
|
data=self.source_available))
|
|
|
|
return
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_lamp(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
|
|
|
Lamp(s) status. See PJLink Specifications for format.
|
2014-10-17 17:28:12 +00:00
|
|
|
Data may have more than 1 lamp to process.
|
|
|
|
Update self.lamp dictionary with lamp status.
|
|
|
|
|
|
|
|
:param data: Lamp(s) status.
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
|
|
|
lamps = []
|
2017-11-24 19:08:23 +00:00
|
|
|
lamp_list = data.split()
|
|
|
|
if len(lamp_list) < 2:
|
|
|
|
lamps.append({'Hours': int(lamp_list[0]), 'On': None})
|
2017-11-24 08:30:37 +00:00
|
|
|
else:
|
2017-11-24 19:08:23 +00:00
|
|
|
while lamp_list:
|
2017-11-24 08:30:37 +00:00
|
|
|
try:
|
2017-11-24 19:08:23 +00:00
|
|
|
fill = {'Hours': int(lamp_list[0]), 'On': False if lamp_list[1] == '0' else True}
|
2017-11-24 08:30:37 +00:00
|
|
|
except ValueError:
|
|
|
|
# In case of invalid entry
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.entry.name, data=data))
|
2017-11-24 08:30:37 +00:00
|
|
|
return
|
|
|
|
lamps.append(fill)
|
2017-11-24 19:08:23 +00:00
|
|
|
lamp_list.pop(0) # Remove lamp hours
|
|
|
|
lamp_list.pop(0) # Remove lamp on/off
|
2014-10-06 19:10:03 +00:00
|
|
|
self.lamp = lamps
|
|
|
|
return
|
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_lkup(self, data, host, port):
|
|
|
|
"""
|
|
|
|
Process reply indicating remote is available for connection
|
|
|
|
|
|
|
|
:param data: Data packet from remote
|
|
|
|
:param host: Remote IP address
|
|
|
|
:param port: Local port packet received on
|
|
|
|
"""
|
|
|
|
# TODO: Check if autoconnect is enabled and connect?
|
|
|
|
pass
|
|
|
|
|
|
|
|
def process_name(self, data, *args, **kwargs):
|
2017-08-06 07:23:26 +00:00
|
|
|
"""
|
|
|
|
Projector name set in projector.
|
|
|
|
Updates self.pjlink_name
|
|
|
|
|
|
|
|
:param data: Projector name
|
|
|
|
"""
|
|
|
|
self.pjlink_name = data
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.entry.name, data=self.pjlink_name))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_pjlink(self, data, *args, **kwargs):
|
2017-12-04 00:24:47 +00:00
|
|
|
"""
|
|
|
|
Process initial socket connection to terminal.
|
|
|
|
|
|
|
|
:param data: Initial packet with authentication scheme
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Processing PJLINK command'.format(ip=self.entry.name))
|
2017-12-09 11:17:05 +00:00
|
|
|
chk = data.split(' ')
|
2017-12-04 00:24:47 +00:00
|
|
|
if len(chk[0]) != 1:
|
|
|
|
# Invalid - after splitting, first field should be 1 character, either '0' or '1' only
|
2018-01-13 05:41:42 +00:00
|
|
|
log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
return self.disconnect_from_host()
|
|
|
|
elif chk[0] == '0':
|
|
|
|
# Normal connection no authentication
|
|
|
|
if len(chk) > 1:
|
|
|
|
# Invalid data - there should be nothing after a normal authentication scheme
|
2018-01-13 05:41:42 +00:00
|
|
|
log.error('({ip}) Normal connection with extra information - aborting'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
return self.disconnect_from_host()
|
|
|
|
elif self.pin:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
return self.disconnect_from_host()
|
|
|
|
else:
|
|
|
|
data_hash = None
|
|
|
|
elif chk[0] == '1':
|
|
|
|
if len(chk) < 2:
|
|
|
|
# Not enough information for authenticated connection
|
2018-01-13 05:41:42 +00:00
|
|
|
log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
return self.disconnect_from_host()
|
|
|
|
elif not self.pin:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
return self.disconnect_from_host()
|
|
|
|
else:
|
|
|
|
data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')),
|
|
|
|
encoding='ascii')
|
|
|
|
# Passed basic checks, so start connection
|
|
|
|
self.readyRead.connect(self.get_socket)
|
|
|
|
self.change_status(S_CONNECTED)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
# Since this is an initial connection, make it a priority just in case
|
|
|
|
return self.send_command(cmd="CLSS", salt=data_hash, priority=True)
|
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_powr(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
|
|
|
Power status. See PJLink specification for format.
|
2014-10-17 17:28:12 +00:00
|
|
|
Update self.power with status. Update icons if change from previous setting.
|
|
|
|
|
|
|
|
:param data: Power status
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}: Processing POWR command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
if data in PJLINK_POWR_STATUS:
|
2014-10-15 17:22:12 +00:00
|
|
|
power = PJLINK_POWR_STATUS[data]
|
|
|
|
update_icons = self.power != power
|
|
|
|
self.power = power
|
2014-10-06 19:10:03 +00:00
|
|
|
self.change_status(PJLINK_POWR_STATUS[data])
|
2014-10-15 17:22:12 +00:00
|
|
|
if update_icons:
|
|
|
|
self.projectorUpdateIcons.emit()
|
2014-10-16 20:33:29 +00:00
|
|
|
# Update the input sources available
|
|
|
|
if power == S_ON:
|
|
|
|
self.send_command('INST')
|
2014-10-06 19:10:03 +00:00
|
|
|
else:
|
|
|
|
# Log unknown status response
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.entry.name, data=data))
|
2014-10-06 19:10:03 +00:00
|
|
|
return
|
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_rfil(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Process replacement filter type
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.model_filter is None:
|
|
|
|
self.model_filter = data
|
2014-10-06 19:10:03 +00:00
|
|
|
else:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Filter model already set'.format(ip=self.entry.name))
|
|
|
|
log.warning('({ip}) Saved model: "{old}"'.format(ip=self.entry.name, old=self.model_filter))
|
|
|
|
log.warning('({ip}) New model: "{new}"'.format(ip=self.entry.name, new=data))
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_rlmp(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Process replacement lamp type
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.model_lamp is None:
|
|
|
|
self.model_lamp = data
|
|
|
|
else:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Lamp model already set'.format(ip=self.entry.name))
|
|
|
|
log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.entry.name, old=self.model_lamp))
|
|
|
|
log.warning('({ip}) New lamp: "{new}"'.format(ip=self.entry.name, new=data))
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_snum(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Serial number of projector.
|
2014-10-17 17:28:12 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
:param data: Serial number from projector.
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.serial_no is None:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting projector serial number to "{data}"'.format(ip=self.entry.name, data=data))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.serial_no = data
|
|
|
|
self.db_update = False
|
2016-02-28 10:26:38 +00:00
|
|
|
else:
|
2017-08-06 07:23:26 +00:00
|
|
|
# Compare serial numbers and see if we got the same projector
|
|
|
|
if self.serial_no != data:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Projector serial number does not match saved serial '
|
|
|
|
'number'.format(ip=self.entry.name))
|
|
|
|
log.warning('({ip}) Saved: "{old}"'.format(ip=self.entry.name, old=self.serial_no))
|
|
|
|
log.warning('({ip}) Received: "{new}"'.format(ip=self.entry.name, new=data))
|
|
|
|
log.warning('({ip}) NOT saving serial number'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.serial_no_received = data
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def process_srch(self, data, host, port):
|
|
|
|
"""
|
|
|
|
Process the SRCH command.
|
|
|
|
|
|
|
|
SRCH is processed by terminals so we ignore any packet.
|
|
|
|
|
|
|
|
:param data: Data in packet
|
|
|
|
:param host: IP address of sending host
|
|
|
|
:param port: Port received on
|
|
|
|
"""
|
|
|
|
log.warning('(UDP) SRCH packet received from {host} - ignoring'.format(host=host))
|
|
|
|
return
|
|
|
|
|
|
|
|
def process_sver(self, data, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Software version of projector
|
|
|
|
"""
|
2017-08-12 09:48:38 +00:00
|
|
|
if len(data) > 32:
|
|
|
|
# Defined in specs max version is 32 characters
|
2017-12-09 11:17:05 +00:00
|
|
|
log.warning('Invalid software version - too long')
|
2017-08-12 09:48:38 +00:00
|
|
|
return
|
|
|
|
elif self.sw_version is None:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting projector software version to "{data}"'.format(ip=self.entry.name, data=data))
|
2017-08-06 07:23:26 +00:00
|
|
|
else:
|
2017-12-25 08:44:30 +00:00
|
|
|
if self.sw_version != data:
|
2017-12-09 11:17:05 +00:00
|
|
|
log.warning('({ip}) Projector software version does not match saved '
|
2018-01-13 05:41:42 +00:00
|
|
|
'software version'.format(ip=self.entry.name))
|
|
|
|
log.warning('({ip}) Saved: "{old}"'.format(ip=self.entry.name, old=self.sw_version))
|
|
|
|
log.warning('({ip}) Received: "{new}"'.format(ip=self.entry.name, new=data))
|
|
|
|
log.warning('({ip}) Updating software version'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
self.sw_version = data
|
|
|
|
self.db_update = True
|
2014-10-17 17:28:12 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
|
2017-10-23 22:09:57 +00:00
|
|
|
class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
|
2017-08-06 07:23:26 +00:00
|
|
|
"""
|
2018-02-11 11:42:13 +00:00
|
|
|
Socket services for PJLink TCP packets.
|
2017-08-06 07:23:26 +00:00
|
|
|
"""
|
|
|
|
# Signals sent by this module
|
|
|
|
changeStatus = QtCore.pyqtSignal(str, int, str)
|
|
|
|
projectorStatus = QtCore.pyqtSignal(int) # Status update
|
|
|
|
projectorAuthentication = QtCore.pyqtSignal(str) # Authentication error
|
|
|
|
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
|
|
|
|
|
2017-09-22 12:03:28 +00:00
|
|
|
def __init__(self, projector, *args, **kwargs):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Setup for instance.
|
|
|
|
Options should be in kwargs except for port which does have a default.
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-09-22 12:03:28 +00:00
|
|
|
:param projector: Database record of projector
|
2017-08-06 07:23:26 +00:00
|
|
|
|
|
|
|
Optional parameters
|
|
|
|
:param poll_time: Time (in seconds) to poll connected projector
|
|
|
|
:param socket_timeout: Time (in seconds) to abort the connection if no response
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-12-09 11:17:05 +00:00
|
|
|
log.debug('PJlink(projector="{projector}", args="{args}" kwargs="{kwargs}")'.format(projector=projector,
|
|
|
|
args=args,
|
|
|
|
kwargs=kwargs))
|
2017-08-06 07:23:26 +00:00
|
|
|
super().__init__()
|
2017-09-22 12:03:28 +00:00
|
|
|
self.entry = projector
|
|
|
|
self.ip = self.entry.ip
|
|
|
|
self.location = self.entry.location
|
|
|
|
self.mac_adx = self.entry.mac_adx
|
|
|
|
self.name = self.entry.name
|
|
|
|
self.notes = self.entry.notes
|
|
|
|
self.pin = self.entry.pin
|
|
|
|
self.port = self.entry.port
|
2018-01-13 05:41:42 +00:00
|
|
|
self.pjlink_class = PJLINK_CLASS if self.entry.pjlink_class is None else self.entry.pjlink_class
|
2018-04-20 06:04:43 +00:00
|
|
|
self.ackn_list = {} # Replies from online projectors (Class 2 option)
|
2017-08-06 07:23:26 +00:00
|
|
|
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
|
|
|
|
self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
|
2018-01-13 05:41:42 +00:00
|
|
|
# Socket timeout (in case of brain-dead projectors) 5 seconds unless called with something else
|
2017-08-06 07:23:26 +00:00
|
|
|
self.socket_timeout = 5000 if 'socket_timeout' not in kwargs else kwargs['socket_timeout'] * 1000
|
|
|
|
# In case we're called from somewhere that only wants information
|
|
|
|
self.no_poll = 'no_poll' in kwargs
|
|
|
|
self.status_connect = S_NOT_CONNECTED
|
|
|
|
self.last_command = ''
|
|
|
|
self.projector_status = S_NOT_CONNECTED
|
|
|
|
self.error_status = S_OK
|
|
|
|
# Socket information
|
|
|
|
# Add enough space to input buffer for extraneous \n \r
|
|
|
|
self.max_size = PJLINK_MAX_PACKET + 2
|
|
|
|
self.setReadBufferSize(self.max_size)
|
|
|
|
self.reset_information()
|
|
|
|
self.send_queue = []
|
2017-12-04 00:24:47 +00:00
|
|
|
self.priority_queue = []
|
2017-08-06 07:23:26 +00:00
|
|
|
self.send_busy = False
|
2018-01-13 05:41:42 +00:00
|
|
|
# Poll timer for status updates
|
|
|
|
self.poll_timer = QtCore.QTimer(self) # Timer that calls the poll_loop
|
|
|
|
self.poll_timer.setInterval(self.poll_time)
|
|
|
|
self.poll_timer.timeout.connect(self.poll_loop)
|
|
|
|
# Socket timer for some possible brain-dead projectors or network issues
|
|
|
|
self.socket_timer = QtCore.QTimer(self)
|
|
|
|
self.socket_timer.setInterval(self.socket_timeout)
|
|
|
|
self.socket_timer.timeout.connect(self.socket_abort)
|
|
|
|
# Socket status signals
|
2017-08-06 07:23:26 +00:00
|
|
|
self.connected.connect(self.check_login)
|
|
|
|
self.disconnected.connect(self.disconnect_from_host)
|
|
|
|
self.error.connect(self.get_error)
|
2017-12-04 00:24:47 +00:00
|
|
|
self.projectorReceivedData.connect(self._send_command)
|
2017-08-06 07:23:26 +00:00
|
|
|
|
|
|
|
def socket_abort(self):
|
|
|
|
"""
|
|
|
|
Aborts connection and closes socket in case of brain-dead projectors.
|
|
|
|
Should normally be called by socket_timer().
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) socket_abort() - Killing connection'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.disconnect_from_host(abort=True)
|
|
|
|
|
|
|
|
def poll_loop(self):
|
|
|
|
"""
|
|
|
|
Retrieve information from projector that changes.
|
|
|
|
Normally called by timer().
|
|
|
|
"""
|
2017-12-25 08:44:30 +00:00
|
|
|
if QSOCKET_STATE[self.state()] != S_CONNECTED:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) poll_loop(): Not connected - returning'.format(ip=self.entry.name))
|
|
|
|
# Stop timer just in case it's missed elsewhere
|
|
|
|
self.poll_timer.stop()
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
# The following commands do not change, so only check them once
|
2017-12-25 08:44:30 +00:00
|
|
|
# Call them first in case other functions rely on something here
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.power == S_ON and self.source_available is None:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_command('INST')
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.other_info is None:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_command('INFO')
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.manufacturer is None:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_command('INF1')
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.model is None:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_command('INF2')
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.pjlink_name is None:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_command('NAME')
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.pjlink_class == '2':
|
|
|
|
# Class 2 specific checks
|
|
|
|
if self.serial_no is None:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_command('SNUM')
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.sw_version is None:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_command('SVER')
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.model_filter is None:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_command('RFIL')
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.model_lamp is None:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_command('RLMP')
|
2017-12-25 08:44:30 +00:00
|
|
|
# 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)
|
2018-01-13 05:41:42 +00:00
|
|
|
# Reset the poll_timer for normal operations in case of initial connection
|
|
|
|
self.poll_timer.setInterval(self.poll_time)
|
2017-08-06 07:23:26 +00:00
|
|
|
|
|
|
|
def _get_status(self, status):
|
|
|
|
"""
|
|
|
|
Helper to retrieve status/error codes and convert to strings.
|
|
|
|
|
|
|
|
:param status: Status/Error code
|
2017-12-25 08:44:30 +00:00
|
|
|
:returns: tuple (-1 if code not INT, None)
|
|
|
|
:returns: tuple (string: code as string, None if no description)
|
|
|
|
:returns: tuple (string: code as string, string: Status/Error description)
|
2017-08-06 07:23:26 +00:00
|
|
|
"""
|
2017-08-12 09:48:38 +00:00
|
|
|
if not isinstance(status, int):
|
2017-12-25 08:44:30 +00:00
|
|
|
return -1, None
|
|
|
|
elif status not in STATUS_MSG:
|
|
|
|
return None, None
|
2017-08-06 07:23:26 +00:00
|
|
|
else:
|
2017-12-25 08:44:30 +00:00
|
|
|
return STATUS_CODE[status], STATUS_MSG[status]
|
2017-08-06 07:23:26 +00:00
|
|
|
|
|
|
|
def change_status(self, status, msg=None):
|
|
|
|
"""
|
|
|
|
Check connection/error status, set status for projector, then emit status change signal
|
|
|
|
for gui to allow changing the icons.
|
|
|
|
|
|
|
|
:param status: Status code
|
|
|
|
:param msg: Optional message
|
|
|
|
"""
|
2017-12-25 08:44:30 +00:00
|
|
|
if status in STATUS_CODE:
|
|
|
|
log.debug('({ip}) Changing status to {status} '
|
2018-01-13 05:41:42 +00:00
|
|
|
'"{msg}"'.format(ip=self.entry.name,
|
2017-12-25 08:44:30 +00:00
|
|
|
status=STATUS_CODE[status],
|
|
|
|
msg=msg if msg is not None else STATUS_MSG[status]))
|
|
|
|
else:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Unknown status change code: {code}'.format(ip=self.entry.name,
|
2017-12-25 08:44:30 +00:00
|
|
|
code=status))
|
|
|
|
return
|
2017-08-06 07:23:26 +00:00
|
|
|
if status in CONNECTION_ERRORS:
|
2017-12-25 08:44:30 +00:00
|
|
|
# Connection state error affects both socket and projector
|
|
|
|
self.error_status = status
|
|
|
|
self.status_connect = E_NOT_CONNECTED
|
|
|
|
elif status >= S_NOT_CONNECTED and status in QSOCKET_STATE:
|
|
|
|
# Socket connection status update
|
2017-08-06 07:23:26 +00:00
|
|
|
self.status_connect = status
|
2017-12-25 08:44:30 +00:00
|
|
|
elif status >= S_NOT_CONNECTED and status in PROJECTOR_STATE:
|
|
|
|
# Only affects the projector status
|
2017-08-06 07:23:26 +00:00
|
|
|
self.projector_status = status
|
2017-12-25 08:44:30 +00:00
|
|
|
|
|
|
|
# These log entries are for troubleshooting only
|
2017-08-06 07:23:26 +00:00
|
|
|
(status_code, status_message) = self._get_status(self.status_connect)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) status_connect: {code}: "{message}"'.format(ip=self.entry.name,
|
2017-08-06 07:23:26 +00:00
|
|
|
code=status_code,
|
|
|
|
message=status_message if msg is None else msg))
|
|
|
|
(status_code, status_message) = self._get_status(self.projector_status)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) projector_status: {code}: "{message}"'.format(ip=self.entry.name,
|
2017-08-06 07:23:26 +00:00
|
|
|
code=status_code,
|
|
|
|
message=status_message if msg is None else msg))
|
|
|
|
(status_code, status_message) = self._get_status(self.error_status)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) error_status: {code}: "{message}"'.format(ip=self.entry.name,
|
2017-08-06 07:23:26 +00:00
|
|
|
code=status_code,
|
|
|
|
message=status_message if msg is None else msg))
|
2017-12-25 08:44:30 +00:00
|
|
|
|
|
|
|
# Now that we logged extra information for debugging, broadcast the original change/message
|
|
|
|
(code, message) = self._get_status(status)
|
|
|
|
if msg is not None:
|
|
|
|
message = msg
|
|
|
|
elif message is None:
|
|
|
|
# No message for status code
|
|
|
|
message = translate('OpenLP.PJLink', 'No message') if msg is None else msg
|
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
self.changeStatus.emit(self.ip, status, message)
|
2017-12-04 00:24:47 +00:00
|
|
|
self.projectorUpdateIcons.emit()
|
2017-08-06 07:23:26 +00:00
|
|
|
|
|
|
|
@QtCore.pyqtSlot()
|
|
|
|
def check_login(self, data=None):
|
|
|
|
"""
|
2017-12-04 00:24:47 +00:00
|
|
|
Processes the initial connection and convert to a PJLink packet if valid initial connection
|
2017-08-06 07:23:26 +00:00
|
|
|
|
|
|
|
:param data: Optional data if called from another routine
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) check_login(data="{data}")'.format(ip=self.entry.name, data=data))
|
2017-08-06 07:23:26 +00:00
|
|
|
if data is None:
|
|
|
|
# Reconnected setup?
|
|
|
|
if not self.waitForReadyRead(2000):
|
|
|
|
# Possible timeout issue
|
2018-01-13 05:41:42 +00:00
|
|
|
log.error('({ip}) Socket timeout waiting for login'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.change_status(E_SOCKET_TIMEOUT)
|
|
|
|
return
|
|
|
|
read = self.readLine(self.max_size)
|
2017-12-04 00:24:47 +00:00
|
|
|
self.readLine(self.max_size) # Clean out any trailing whitespace
|
2017-08-06 07:23:26 +00:00
|
|
|
if read is None:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) read is None - socket error?'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
|
|
|
elif len(read) < 8:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) Not enough data read - skipping'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
|
|
|
data = decode(read, 'utf-8')
|
|
|
|
# Possibility of extraneous data on input when reading.
|
|
|
|
# Clean out extraneous characters in buffer.
|
2017-12-26 04:14:39 +00:00
|
|
|
self.read(1024)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) check_login() read "{data}"'.format(ip=self.entry.name, data=data.strip()))
|
2017-08-06 07:23:26 +00:00
|
|
|
# At this point, we should only have the initial login prompt with
|
|
|
|
# possible authentication
|
|
|
|
# PJLink initial login will be:
|
|
|
|
# 'PJLink 0' - Unauthenticated login - no extra steps required.
|
|
|
|
# 'PJLink 1 XXXXXX' Authenticated login - extra processing required.
|
2017-12-04 00:24:47 +00:00
|
|
|
if not data.startswith('PJLINK'):
|
|
|
|
# Invalid initial packet - close socket
|
2018-01-13 05:41:42 +00:00
|
|
|
log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
return self.disconnect_from_host()
|
2017-12-25 08:44:30 +00:00
|
|
|
# Convert the initial login prompt with the expected PJLink normal command format for processing
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) check_login(): Formatting initial connection prompt'
|
|
|
|
'to PJLink packet'.format(ip=self.entry.name))
|
2017-12-09 11:17:05 +00:00
|
|
|
return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX,
|
|
|
|
clss='1',
|
|
|
|
data=data.replace(' ', '=', 1)).encode('utf-8'))
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
def _trash_buffer(self, msg=None):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Clean out extraneous stuff in the buffer.
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2018-04-20 06:04:43 +00:00
|
|
|
log.debug('({ip}) Cleaning buffer - msg = "{message}"'.format(ip=self.entry.name, message=msg))
|
|
|
|
if msg is None:
|
|
|
|
msg = 'Invalid packet'
|
|
|
|
log.warning('({ip}) {message}'.format(ip=self.entry.name, message=msg))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.send_busy = False
|
|
|
|
trash_count = 0
|
|
|
|
while self.bytesAvailable() > 0:
|
|
|
|
trash = self.read(self.max_size)
|
|
|
|
trash_count += len(trash)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Finished cleaning buffer - {count} bytes dropped'.format(ip=self.entry.name,
|
2017-08-06 07:23:26 +00:00
|
|
|
count=trash_count))
|
2014-10-06 19:10:03 +00:00
|
|
|
return
|
|
|
|
|
2017-09-22 12:03:28 +00:00
|
|
|
@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.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) get_buffer(data="{buff}" ip="{ip_in}"'.format(ip=self.entry.name, buff=data, ip_in=ip))
|
2017-09-22 12:03:28 +00:00
|
|
|
if ip is None:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.entry.name))
|
2017-09-22 12:03:28 +00:00
|
|
|
return
|
|
|
|
return self.get_data(buff=data, ip=ip)
|
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
@QtCore.pyqtSlot()
|
2017-09-22 12:03:28 +00:00
|
|
|
def get_socket(self):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-09-22 12:03:28 +00:00
|
|
|
Get data from TCP socket.
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) get_socket(): Reading data'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
if QSOCKET_STATE[self.state()] != S_CONNECTED:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) get_socket(): Not connected - returning'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.send_busy = False
|
|
|
|
return
|
2017-08-12 09:48:38 +00:00
|
|
|
# Although we have a packet length limit, go ahead and use a larger buffer
|
|
|
|
read = self.readLine(1024)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) get_socket(): "{buff}"'.format(ip=self.entry.name, buff=read))
|
2017-08-06 07:23:26 +00:00
|
|
|
if read == -1:
|
|
|
|
# No data available
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
return self.receive_data_signal()
|
|
|
|
self.socket_timer.stop()
|
2017-12-25 08:44:30 +00:00
|
|
|
return self.get_data(buff=read, ip=self.ip)
|
2017-09-22 12:03:28 +00:00
|
|
|
|
2018-04-20 06:04:43 +00:00
|
|
|
def get_data(self, buff, ip=None, *args, **kwargs):
|
2017-09-22 12:03:28 +00:00
|
|
|
"""
|
|
|
|
Process received data
|
|
|
|
|
|
|
|
:param buff: Data to process.
|
|
|
|
:param ip: (optional) Destination IP.
|
|
|
|
"""
|
2017-12-09 11:17:05 +00:00
|
|
|
# Since "self" is not available to options and the "ip" keyword is a "maybe I'll use in the future",
|
|
|
|
# set to default here
|
|
|
|
if ip is None:
|
|
|
|
ip = self.ip
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.entry.name, ip_in=ip, buff=buff))
|
2017-08-06 07:23:26 +00:00
|
|
|
# NOTE: Class2 has changed to some values being UTF-8
|
2018-04-20 06:04:43 +00:00
|
|
|
if isinstance(buff, bytes):
|
|
|
|
data_in = decode(buff, 'utf-8')
|
|
|
|
else:
|
|
|
|
data_in = buff
|
2017-08-06 07:23:26 +00:00
|
|
|
data = data_in.strip()
|
2017-12-04 00:24:47 +00:00
|
|
|
# Initial packet checks
|
|
|
|
if (len(data) < 7):
|
2017-12-25 08:44:30 +00:00
|
|
|
self._trash_buffer(msg='get_data(): Invalid packet - length')
|
|
|
|
return self.receive_data_signal()
|
2017-08-12 09:48:38 +00:00
|
|
|
elif len(data) > self.max_size:
|
2018-04-20 06:04:43 +00:00
|
|
|
self._trash_buffer(msg='get_data(): Invalid packet - too long ({length} bytes)'.format(length=len(data)))
|
2017-12-25 08:44:30 +00:00
|
|
|
return self.receive_data_signal()
|
2017-12-04 00:24:47 +00:00
|
|
|
elif not data.startswith(PJLINK_PREFIX):
|
2017-12-25 08:44:30 +00:00
|
|
|
self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing')
|
|
|
|
return self.receive_data_signal()
|
2018-04-20 06:04:43 +00:00
|
|
|
elif data[6] != '=':
|
2017-12-25 08:44:30 +00:00
|
|
|
self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="')
|
|
|
|
return self.receive_data_signal()
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.entry.name, data=data))
|
2017-08-06 07:23:26 +00:00
|
|
|
header, data = data.split('=')
|
2018-04-20 06:04:43 +00:00
|
|
|
log.debug('({ip}) get_data() header="{header}" data="{data}"'.format(ip=self.entry.name,
|
|
|
|
header=header, data=data))
|
2017-12-04 00:24:47 +00:00
|
|
|
# At this point, the header should contain:
|
|
|
|
# "PVCCCC"
|
|
|
|
# Where:
|
|
|
|
# P = PJLINK_PREFIX
|
|
|
|
# V = PJLink class or version
|
|
|
|
# C = PJLink command
|
2018-04-20 06:04:43 +00:00
|
|
|
version, cmd = header[1], header[2:].upper()
|
|
|
|
log.debug('({ip}) get_data() version="{version}" cmd="{cmd}"'.format(ip=self.entry.name,
|
|
|
|
version=version, cmd=cmd))
|
|
|
|
# TODO: Below commented for now since it seems to cause issues with testing some invalid data.
|
|
|
|
# Revisit after more refactoring is finished.
|
|
|
|
'''
|
2017-08-06 07:23:26 +00:00
|
|
|
try:
|
2017-12-04 00:24:47 +00:00
|
|
|
version, cmd = header[1], header[2:].upper()
|
2018-04-20 06:04:43 +00:00
|
|
|
log.debug('({ip}) get_data() version="{version}" cmd="{cmd}"'.format(ip=self.entry.name,
|
|
|
|
version=version, cmd=cmd))
|
2017-08-06 07:23:26 +00:00
|
|
|
except ValueError as e:
|
|
|
|
self.change_status(E_INVALID_DATA)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.entry.name, data=data_in))
|
2017-12-25 08:44:30 +00:00
|
|
|
self._trash_buffer('get_data(): Expected header + command + data')
|
|
|
|
return self.receive_data_signal()
|
2018-04-20 06:04:43 +00:00
|
|
|
'''
|
2017-08-06 07:23:26 +00:00
|
|
|
if cmd not in PJLINK_VALID_CMD:
|
2018-04-20 06:04:43 +00:00
|
|
|
self._trash_buffer('get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.entry.name,
|
2018-01-13 05:41:42 +00:00
|
|
|
data=cmd))
|
2017-12-25 08:44:30 +00:00
|
|
|
return self.receive_data_signal()
|
2018-04-20 06:04:43 +00:00
|
|
|
elif version not in PJLINK_VALID_CMD[cmd]['version']:
|
|
|
|
self._trash_buffer(msg='get_data() Command reply version does not match a valid command version')
|
|
|
|
return self.receive_data_signal()
|
|
|
|
elif int(self.pjlink_class) < int(version):
|
2017-08-12 20:38:50 +00:00
|
|
|
log.warning('({ip}) get_data(): Projector returned class reply higher '
|
2018-01-13 05:41:42 +00:00
|
|
|
'than projector stated class'.format(ip=self.entry.name))
|
2018-04-20 06:04:43 +00:00
|
|
|
self.process_command(cmd, data, *args, **kwargs)
|
2017-12-25 08:44:30 +00:00
|
|
|
return self.receive_data_signal()
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
@QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)
|
|
|
|
def get_error(self, err):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Process error from SocketError signal.
|
|
|
|
Remaps system error codes to projector error codes.
|
2014-10-17 17:28:12 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
:param err: Error code
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) get_error(err={error}): {data}'.format(ip=self.entry.name,
|
|
|
|
error=err,
|
|
|
|
data=self.errorString()))
|
2017-08-06 07:23:26 +00:00
|
|
|
if err <= 18:
|
|
|
|
# QSocket errors. Redefined in projector.constants so we don't mistake
|
|
|
|
# them for system errors
|
|
|
|
check = err + E_CONNECTION_REFUSED
|
2018-01-13 05:41:42 +00:00
|
|
|
self.poll_timer.stop()
|
2017-08-06 07:23:26 +00:00
|
|
|
else:
|
|
|
|
check = err
|
|
|
|
if check < E_GENERAL:
|
|
|
|
# Some system error?
|
|
|
|
self.change_status(err, self.errorString())
|
|
|
|
else:
|
|
|
|
self.change_status(E_NETWORK, self.errorString())
|
2014-10-15 20:53:17 +00:00
|
|
|
self.projectorUpdateIcons.emit()
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.status_connect == E_NOT_CONNECTED:
|
|
|
|
self.abort()
|
|
|
|
self.reset_information()
|
2014-10-06 19:10:03 +00:00
|
|
|
return
|
|
|
|
|
2017-12-04 00:24:47 +00:00
|
|
|
def send_command(self, cmd, opts='?', salt=None, priority=False):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
Add command to output queue if not already in queue.
|
2014-10-17 17:28:12 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
:param cmd: Command to send
|
|
|
|
:param opts: Command option (if any) - defaults to '?' (get information)
|
|
|
|
:param salt: Optional salt for md5 hash initial authentication
|
2017-12-04 00:24:47 +00:00
|
|
|
:param priority: Option to send packet now rather than queue it up
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2017-12-25 08:44:30 +00:00
|
|
|
if QSOCKET_STATE[self.state()] != S_CONNECTED:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
return self.reset_information()
|
2017-08-06 07:23:26 +00:00
|
|
|
if cmd not in PJLINK_VALID_CMD:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.entry.name,
|
2017-08-06 07:23:26 +00:00
|
|
|
command=cmd,
|
|
|
|
data=opts,
|
|
|
|
salt='' if salt is None
|
|
|
|
else ' with hash'))
|
2018-04-20 06:04:43 +00:00
|
|
|
header = PJLINK_HEADER.format(linkclass=self.pjlink_functions[cmd]["version"])
|
2017-08-06 07:23:26 +00:00
|
|
|
out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt,
|
|
|
|
header=header,
|
|
|
|
command=cmd,
|
|
|
|
options=opts,
|
2017-12-25 08:44:30 +00:00
|
|
|
suffix=PJLINK_SUFFIX)
|
2017-12-04 00:24:47 +00:00
|
|
|
if out in self.priority_queue:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
elif out in self.send_queue:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.entry.name))
|
2017-06-25 02:21:07 +00:00
|
|
|
else:
|
2017-12-04 00:24:47 +00:00
|
|
|
if priority:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) send_command(): Adding to priority queue'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
self.priority_queue.append(out)
|
|
|
|
else:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) send_command(): Adding to normal queue'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
self.send_queue.append(out)
|
|
|
|
if self.priority_queue or self.send_queue:
|
|
|
|
# May be some initial connection setup so make sure we send data
|
2017-08-06 07:23:26 +00:00
|
|
|
self._send_command()
|
2017-06-25 02:21:07 +00:00
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
@QtCore.pyqtSlot()
|
|
|
|
def _send_command(self, data=None, utf8=False):
|
2017-06-25 02:21:07 +00:00
|
|
|
"""
|
2017-08-06 07:23:26 +00:00
|
|
|
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
|
2017-06-25 02:21:07 +00:00
|
|
|
"""
|
2017-12-04 00:24:47 +00:00
|
|
|
# Funny looking data check, but it's a quick check for data=None
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.entry.name, data=data.strip() if data else data))
|
2017-12-25 08:44:30 +00:00
|
|
|
conn_state = STATUS_CODE[QSOCKET_STATE[self.state()]]
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.entry.name,
|
2017-12-25 08:44:30 +00:00
|
|
|
data=conn_state))
|
|
|
|
if QSOCKET_STATE[self.state()] != S_CONNECTED:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.send_busy = False
|
2017-12-04 00:24:47 +00:00
|
|
|
return self.disconnect_from_host()
|
|
|
|
if data and data not in self.priority_queue:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) _send_command(): Priority packet - adding to priority queue'.format(ip=self.entry.name))
|
2017-12-04 00:24:47 +00:00
|
|
|
self.priority_queue.append(data)
|
|
|
|
|
2017-08-06 07:23:26 +00:00
|
|
|
if self.send_busy:
|
|
|
|
# Still waiting for response from last command sent
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) _send_command(): Still busy, returning'.format(ip=self.entry.name))
|
|
|
|
log.debug('({ip}) _send_command(): Priority queue = {data}'.format(ip=self.entry.name,
|
|
|
|
data=self.priority_queue))
|
|
|
|
log.debug('({ip}) _send_command(): Normal queue = {data}'.format(ip=self.entry.name, data=self.send_queue))
|
2017-08-06 07:23:26 +00:00
|
|
|
return
|
2017-12-04 00:24:47 +00:00
|
|
|
|
|
|
|
if len(self.priority_queue) != 0:
|
|
|
|
out = self.priority_queue.pop(0)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
elif len(self.send_queue) != 0:
|
|
|
|
out = self.send_queue.pop(0)
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.entry.name))
|
2017-06-25 02:21:07 +00:00
|
|
|
else:
|
2017-08-06 07:23:26 +00:00
|
|
|
# No data to send
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) _send_command(): No data to send'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.send_busy = False
|
|
|
|
return
|
|
|
|
self.send_busy = True
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) _send_command(): Sending "{data}"'.format(ip=self.entry.name, data=out.strip()))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.socket_timer.start()
|
|
|
|
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?
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) _send_command(): -1 received - disconnecting from host'.format(ip=self.entry.name))
|
2017-08-06 07:23:26 +00:00
|
|
|
self.change_status(E_NETWORK,
|
|
|
|
translate('OpenLP.PJLink', 'Error while sending data to projector'))
|
2017-12-04 00:24:47 +00:00
|
|
|
self.disconnect_from_host()
|
2017-06-25 02:21:07 +00:00
|
|
|
|
2014-10-06 19:10:03 +00:00
|
|
|
def connect_to_host(self):
|
|
|
|
"""
|
2014-10-17 17:28:12 +00:00
|
|
|
Initiate connection to projector.
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) connect_to_host(): Starting connection'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
if QSOCKET_STATE[self.state()] == S_CONNECTED:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return
|
2017-12-25 08:44:30 +00:00
|
|
|
self.error_status = S_OK
|
2014-10-06 19:10:03 +00:00
|
|
|
self.change_status(S_CONNECTING)
|
2016-07-18 21:36:16 +00:00
|
|
|
self.connectToHost(self.ip, self.port if isinstance(self.port, int) else int(self.port))
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2016-07-18 21:36:16 +00:00
|
|
|
@QtCore.pyqtSlot()
|
2014-10-17 02:28:51 +00:00
|
|
|
def disconnect_from_host(self, abort=False):
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
|
|
|
Close socket and cleanup.
|
|
|
|
"""
|
2017-12-25 08:44:30 +00:00
|
|
|
if abort or QSOCKET_STATE[self.state()] != S_NOT_CONNECTED:
|
2014-10-17 02:28:51 +00:00
|
|
|
if abort:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
self.abort()
|
2014-10-17 02:28:51 +00:00
|
|
|
else:
|
2018-01-13 05:41:42 +00:00
|
|
|
log.warning('({ip}) disconnect_from_host(): Not connected'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
self.disconnectFromHost()
|
2014-10-06 19:10:03 +00:00
|
|
|
try:
|
2017-09-22 12:03:28 +00:00
|
|
|
self.readyRead.disconnect(self.get_socket)
|
2014-10-06 19:10:03 +00:00
|
|
|
except TypeError:
|
|
|
|
pass
|
2017-12-04 00:24:47 +00:00
|
|
|
log.debug('({ip}) disconnect_from_host() '
|
2018-01-13 05:41:42 +00:00
|
|
|
'Current status {data}'.format(ip=self.entry.name, data=self._get_status(self.status_connect)[0]))
|
2014-10-17 02:28:51 +00:00
|
|
|
if abort:
|
|
|
|
self.change_status(E_NOT_CONNECTED)
|
|
|
|
else:
|
2017-12-04 00:24:47 +00:00
|
|
|
self.change_status(S_NOT_CONNECTED)
|
2014-10-13 16:40:58 +00:00
|
|
|
self.reset_information()
|
2014-10-06 19:10:03 +00:00
|
|
|
|
2017-08-12 09:48:38 +00:00
|
|
|
def get_av_mute_status(self):
|
|
|
|
"""
|
|
|
|
Send command to retrieve shutter status.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending AVMT command'.format(ip=self.entry.name))
|
2017-08-12 09:48:38 +00:00
|
|
|
return self.send_command(cmd='AVMT')
|
|
|
|
|
2014-10-06 19:10:03 +00:00
|
|
|
def get_available_inputs(self):
|
|
|
|
"""
|
|
|
|
Send command to retrieve available source inputs.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending INST command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return self.send_command(cmd='INST')
|
|
|
|
|
|
|
|
def get_error_status(self):
|
|
|
|
"""
|
|
|
|
Send command to retrieve currently known errors.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending ERST command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return self.send_command(cmd='ERST')
|
|
|
|
|
|
|
|
def get_input_source(self):
|
|
|
|
"""
|
|
|
|
Send command to retrieve currently selected source input.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending INPT command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return self.send_command(cmd='INPT')
|
|
|
|
|
|
|
|
def get_lamp_status(self):
|
|
|
|
"""
|
|
|
|
Send command to return the lap status.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending LAMP command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return self.send_command(cmd='LAMP')
|
|
|
|
|
|
|
|
def get_manufacturer(self):
|
|
|
|
"""
|
|
|
|
Send command to retrieve manufacturer name.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending INF1 command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return self.send_command(cmd='INF1')
|
|
|
|
|
|
|
|
def get_model(self):
|
|
|
|
"""
|
|
|
|
Send command to retrieve the model name.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending INF2 command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return self.send_command(cmd='INF2')
|
|
|
|
|
|
|
|
def get_name(self):
|
|
|
|
"""
|
|
|
|
Send command to retrieve name as set by end-user (if set).
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending NAME command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return self.send_command(cmd='NAME')
|
|
|
|
|
|
|
|
def get_other_info(self):
|
|
|
|
"""
|
|
|
|
Send command to retrieve extra info set by manufacturer.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending INFO command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return self.send_command(cmd='INFO')
|
|
|
|
|
|
|
|
def get_power_status(self):
|
|
|
|
"""
|
|
|
|
Send command to retrieve power status.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Sending POWR command'.format(ip=self.entry.name))
|
2014-10-06 19:10:03 +00:00
|
|
|
return self.send_command(cmd='POWR')
|
|
|
|
|
|
|
|
def set_input_source(self, src=None):
|
|
|
|
"""
|
|
|
|
Verify input source available as listed in 'INST' command,
|
|
|
|
then send the command to select the input source.
|
2014-10-17 17:28:12 +00:00
|
|
|
|
|
|
|
:param src: Video source to select in projector
|
2014-10-06 19:10:03 +00:00
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) set_input_source(src="{data}")'.format(ip=self.entry.name, data=src))
|
2014-10-06 19:10:03 +00:00
|
|
|
if self.source_available is None:
|
|
|
|
return
|
|
|
|
elif src not in self.source_available:
|
|
|
|
return
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting input source to "{data}"'.format(ip=self.entry.name, data=src))
|
2017-12-25 08:44:30 +00:00
|
|
|
self.send_command(cmd='INPT', opts=src, priority=True)
|
2014-10-15 17:29:15 +00:00
|
|
|
self.poll_loop()
|
2014-10-06 19:10:03 +00:00
|
|
|
|
|
|
|
def set_power_on(self):
|
|
|
|
"""
|
|
|
|
Send command to turn power to on.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting POWR to 1 (on)'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
self.send_command(cmd='POWR', opts='1', priority=True)
|
2014-10-15 17:29:15 +00:00
|
|
|
self.poll_loop()
|
2014-10-06 19:10:03 +00:00
|
|
|
|
|
|
|
def set_power_off(self):
|
|
|
|
"""
|
|
|
|
Send command to turn power to standby.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting POWR to 0 (standby)'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
self.send_command(cmd='POWR', opts='0', priority=True)
|
2014-10-15 17:29:15 +00:00
|
|
|
self.poll_loop()
|
2014-10-06 19:10:03 +00:00
|
|
|
|
|
|
|
def set_shutter_closed(self):
|
|
|
|
"""
|
|
|
|
Send command to set shutter to closed position.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting AVMT to 11 (shutter closed)'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
self.send_command(cmd='AVMT', opts='11', priority=True)
|
2014-10-15 17:29:15 +00:00
|
|
|
self.poll_loop()
|
2014-10-06 19:10:03 +00:00
|
|
|
|
|
|
|
def set_shutter_open(self):
|
|
|
|
"""
|
|
|
|
Send command to set shutter to open position.
|
|
|
|
"""
|
2018-01-13 05:41:42 +00:00
|
|
|
log.debug('({ip}) Setting AVMT to "10" (shutter open)'.format(ip=self.entry.name))
|
2017-12-25 08:44:30 +00:00
|
|
|
self.send_command(cmd='AVMT', opts='10', priority=True)
|
2014-10-15 17:29:15 +00:00
|
|
|
self.poll_loop()
|
2017-12-25 08:44:30 +00:00
|
|
|
self.projectorUpdateIcons.emit()
|
2017-05-12 09:51:56 +00:00
|
|
|
|
2017-05-13 09:00:29 +00:00
|
|
|
def receive_data_signal(self):
|
|
|
|
"""
|
|
|
|
Clear any busy flags and send data received signal
|
|
|
|
"""
|
|
|
|
self.send_busy = False
|
|
|
|
self.projectorReceivedData.emit()
|
|
|
|
return
|