Merge lp:~alisonken1/openlp/pjlink2g into lp:openlp

Proposed by Ken Roberts
Status: Superseded
Proposed branch: lp:~alisonken1/openlp/pjlink2g
Merge into: lp:openlp
Diff against target: 1988 lines (+916/-809)
9 files modified
openlp/core/lib/__init__.py (+1/-1)
openlp/core/lib/projector/constants.py (+1/-1)
openlp/core/lib/projector/pjlink.py (+419/-420)
openlp/core/lib/projector/upgrade.py (+5/-5)
openlp/core/ui/projector/manager.py (+1/-1)
tests/functional/openlp_core_lib/test_projector_constants.py (+2/-2)
tests/functional/openlp_core_lib/test_projector_db.py (+4/-4)
tests/functional/openlp_core_lib/test_projector_pjlink_base.py (+15/-375)
tests/functional/openlp_core_lib/test_projector_pjlink_commands.py (+468/-0)
To merge this branch: bzr merge lp:~alisonken1/openlp/pjlink2g
Reviewer Review Type Date Requested Status
Tim Bentley Needs Fixing
Review via email: mp+328634@code.launchpad.net

This proposal has been superseded by a proposal from 2017-08-07.

Commit message

PJLink2 update

Description of the change

- Break PJLink class into base class and process commands class
- Restructure class methods
- Break projector PJLink tests into pjlink_base and pjlink_commands
- Restructure test methods
- Remove unused test imports
- Rename several tests
- Remove extraneous test (test_projector_return_ok)
- Added tests for process_erst reply

--------------------------------
lp:~alisonken1/openlp/pjlink2g (revision 2756)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2117/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2027/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1935/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1312/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1155/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/285/
[SUCCESS] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/130/

To post a comment you must log in.
Revision history for this message
Ken Roberts (alisonken1) wrote :

Other than tests - there should be no new code here, only restructured code.

Revision history for this message
Tim Bentley (trb143) wrote :

Some comments on the code as we are improving it !

review: Needs Fixing
lp:~alisonken1/openlp/pjlink2g updated
2757. By Ken Roberts

Code cleanups

2758. By Ken Roberts

Fix AVMT test

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/lib/__init__.py'
2--- openlp/core/lib/__init__.py 2017-05-20 05:51:58 +0000
3+++ openlp/core/lib/__init__.py 2017-08-07 00:09:00 +0000
4@@ -621,5 +621,5 @@
5 from .renderer import Renderer
6 from .mediamanageritem import MediaManagerItem
7 from .projector.db import ProjectorDB, Projector
8-from .projector.pjlink1 import PJLink
9+from .projector.pjlink import PJLink
10 from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
11
12=== modified file 'openlp/core/lib/projector/constants.py'
13--- openlp/core/lib/projector/constants.py 2017-06-09 12:12:39 +0000
14+++ openlp/core/lib/projector/constants.py 2017-08-07 00:09:00 +0000
15@@ -154,7 +154,7 @@
16 },
17 'SRCH': {'version': ['2', ],
18 'description': translate('OpenLP.PJLinkConstants',
19- 'UDP broadcast search request for available projectors.')
20+ 'UDP broadcast search request for available projectors. Reply is ACKN.')
21 },
22 'SVER': {'version': ['2', ],
23 'description': translate('OpenLP.PJLinkConstants',
24
25=== renamed file 'openlp/core/lib/projector/pjlink1.py' => 'openlp/core/lib/projector/pjlink.py'
26--- openlp/core/lib/projector/pjlink1.py 2017-07-20 15:31:50 +0000
27+++ openlp/core/lib/projector/pjlink.py 2017-08-07 00:09:00 +0000
28@@ -20,14 +20,17 @@
29 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
30 ###############################################################################
31 """
32- :mod:`openlp.core.lib.projector.pjlink1` module
33+ :mod:`openlp.core.lib.projector.pjlink` module
34 Provides the necessary functions for connecting to a PJLink-capable projector.
35
36- See PJLink Class 1 Specifications for details.
37- http://pjlink.jbmia.or.jp/english/dl.html
38-
39- Section 5-1 PJLink Specifications
40-
41+ PJLink Class 1 Specifications.
42+ http://pjlink.jbmia.or.jp/english/dl_class1.html
43+ Section 5-1 PJLink Specifications
44+ Section 5-5 Guidelines for Input Terminals
45+
46+ PJLink Class 2 Specifications.
47+ http://pjlink.jbmia.or.jp/english/dl_class2.html
48+ Section 5-1 PJLink Specifications
49 Section 5-5 Guidelines for Input Terminals
50
51 NOTE:
52@@ -40,7 +43,7 @@
53 import logging
54 log = logging.getLogger(__name__)
55
56-log.debug('pjlink1 loaded')
57+log.debug('pjlink loaded')
58
59 __all__ = ['PJLink']
60
61@@ -69,7 +72,403 @@
62 PJLINK_SUFFIX = CR
63
64
65-class PJLink(QtNetwork.QTcpSocket):
66+class PJLinkCommands(object):
67+ """
68+ Process replies from PJLink projector.
69+ """
70+
71+ def __init__(self, *args, **kwargs):
72+ """
73+ Setup for the process commands
74+ """
75+ log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
76+ super().__init__()
77+ # Map command to function
78+ self.pjlink_functions = {
79+ 'AVMT': self.process_avmt,
80+ 'CLSS': self.process_clss,
81+ 'ERST': self.process_erst,
82+ 'INFO': self.process_info,
83+ 'INF1': self.process_inf1,
84+ 'INF2': self.process_inf2,
85+ 'INPT': self.process_inpt,
86+ 'INST': self.process_inst,
87+ 'LAMP': self.process_lamp,
88+ 'NAME': self.process_name,
89+ 'PJLINK': self.check_login,
90+ 'POWR': self.process_powr,
91+ 'SNUM': self.process_snum,
92+ 'SVER': self.process_sver,
93+ 'RFIL': self.process_rfil,
94+ 'RLMP': self.process_rlmp
95+ }
96+
97+ def reset_information(self):
98+ """
99+ Initialize instance variables. Also used to reset projector-specific information to default.
100+ """
101+ log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state()))
102+ self.fan = None # ERST
103+ self.filter_time = None # FILT
104+ self.lamp = None # LAMP
105+ self.mac_adx_received = None # ACKN
106+ self.manufacturer = None # INF1
107+ self.model = None # INF2
108+ self.model_filter = None # RFIL
109+ self.model_lamp = None # RLMP
110+ self.mute = None # AVMT
111+ self.other_info = None # INFO
112+ self.pjlink_class = PJLINK_CLASS # Default class
113+ self.pjlink_name = None # NAME
114+ self.power = S_OFF # POWR
115+ self.serial_no = None # SNUM
116+ self.serial_no_received = None
117+ self.sw_version = None # SVER
118+ self.sw_version_received = None
119+ self.shutter = None # AVMT
120+ self.source_available = None # INST
121+ self.source = None # INPT
122+ # These should be part of PJLink() class, but set here for convenience
123+ if hasattr(self, 'timer'):
124+ log.debug('({ip}): Calling timer.stop()'.format(ip=self.ip))
125+ self.timer.stop()
126+ if hasattr(self, 'socket_timer'):
127+ log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.ip))
128+ self.socket_timer.stop()
129+ self.send_busy = False
130+ self.send_queue = []
131+
132+ def process_command(self, cmd, data):
133+ """
134+ Verifies any return error code. Calls the appropriate command handler.
135+
136+ :param cmd: Command to process
137+ :param data: Data being processed
138+ """
139+ log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip,
140+ cmd=cmd,
141+ data=data))
142+ # Check if we have a future command not available yet
143+ _cmd = cmd.upper()
144+ _data = data.upper()
145+ if _cmd not in PJLINK_VALID_CMD:
146+ log.error("({ip}) Ignoring command='{cmd}' (Invalid/Unknown)".format(ip=self.ip, cmd=cmd))
147+ return
148+ elif _cmd not in self.pjlink_functions:
149+ log.warn("({ip}) Unable to process command='{cmd}' (Future option)".format(ip=self.ip, cmd=cmd))
150+ return
151+ elif _data in PJLINK_ERRORS:
152+ # Oops - projector error
153+ log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data))
154+ if _data == 'ERRA':
155+ # Authentication error
156+ self.disconnect_from_host()
157+ self.change_status(E_AUTHENTICATION)
158+ log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
159+ self.projectorAuthentication.emit(self.name)
160+ elif _data == 'ERR1':
161+ # Undefined command
162+ self.change_status(E_UNDEFINED, '{error}: "{data}"'.format(error=ERROR_MSG[E_UNDEFINED],
163+ data=cmd))
164+ elif _data == 'ERR2':
165+ # Invalid parameter
166+ self.change_status(E_PARAMETER)
167+ elif _data == 'ERR3':
168+ # Projector busy
169+ self.change_status(E_UNAVAILABLE)
170+ elif _data == 'ERR4':
171+ # Projector/display error
172+ self.change_status(E_PROJECTOR)
173+ self.receive_data_signal()
174+ return
175+ # Command succeeded - no extra information
176+ elif _data == 'OK':
177+ log.debug('({ip}) Command returned OK'.format(ip=self.ip))
178+ # A command returned successfully
179+ self.receive_data_signal()
180+ return
181+ # Command checks already passed
182+ log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
183+ self.receive_data_signal()
184+ self.pjlink_functions[_cmd](data)
185+
186+ def process_avmt(self, data):
187+ """
188+ Process shutter and speaker status. See PJLink specification for format.
189+ Update self.mute (audio) and self.shutter (video shutter).
190+
191+ :param data: Shutter and audio status
192+ """
193+ settings = {'11': {'shutter': True, 'mute': self.mute},
194+ '21': {'shutter': self.shutter, 'mute': True},
195+ '30': {'shutter': False, 'mute': False},
196+ '31': {'shutter': True, 'mute': True}
197+ }
198+ if data in settings:
199+ shutter = settings[data]['shutter']
200+ mute = settings[data]['mute']
201+ # Check if we need to update the icons
202+ update_icons = (shutter != self.shutter) or (mute != self.mute)
203+ self.shutter = shutter
204+ self.mute = mute
205+ else:
206+ log.warning('({ip}) Unknown shutter response: {data}'.format(ip=self.ip, data=data))
207+ if update_icons:
208+ self.projectorUpdateIcons.emit()
209+ return
210+
211+ def process_clss(self, data):
212+ """
213+ PJLink class that this projector supports. See PJLink specification for format.
214+ Updates self.class.
215+
216+ :param data: Class that projector supports.
217+ """
218+ # bug 1550891: Projector returns non-standard class response:
219+ # : Expected: '%1CLSS=1'
220+ # : Received: '%1CLSS=Class 1' (Optoma)
221+ # : Received: '%1CLSS=Version1' (BenQ)
222+ if len(data) > 1:
223+ log.warn("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data))
224+ # Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
225+ # AND the different responses that can be received, the semi-permanent way to
226+ # fix the class reply is to just remove all non-digit characters.
227+ try:
228+ clss = re.findall('\d', data)[0] # Should only be the first match
229+ except IndexError:
230+ log.error("({ip}) No numbers found in class version reply - defaulting to class '1'".format(ip=self.ip))
231+ clss = '1'
232+ elif not data.isdigit():
233+ log.error("({ip}) NAN class version reply - defaulting to class '1'".format(ip=self.ip))
234+ clss = '1'
235+ else:
236+ clss = data
237+ self.pjlink_class = clss
238+ log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.ip,
239+ data=self.pjlink_class))
240+ return
241+
242+ def process_erst(self, data):
243+ """
244+ Error status. See PJLink Specifications for format.
245+ Updates self.projector_errors
246+
247+\ :param data: Error status
248+ """
249+ try:
250+ datacheck = int(data)
251+ except ValueError:
252+ # Bad data - ignore
253+ return
254+ if datacheck == 0:
255+ self.projector_errors = None
256+ else:
257+ self.projector_errors = {}
258+ # Fan
259+ if data[0] != '0':
260+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \
261+ PJLINK_ERST_STATUS[data[0]]
262+ # Lamp
263+ if data[1] != '0':
264+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \
265+ PJLINK_ERST_STATUS[data[1]]
266+ # Temp
267+ if data[2] != '0':
268+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \
269+ PJLINK_ERST_STATUS[data[2]]
270+ # Cover
271+ if data[3] != '0':
272+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \
273+ PJLINK_ERST_STATUS[data[3]]
274+ # Filter
275+ if data[4] != '0':
276+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \
277+ PJLINK_ERST_STATUS[data[4]]
278+ # Other
279+ if data[5] != '0':
280+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \
281+ PJLINK_ERST_STATUS[data[5]]
282+ return
283+
284+ def process_inf1(self, data):
285+ """
286+ Manufacturer name set in projector.
287+ Updates self.manufacturer
288+
289+ :param data: Projector manufacturer
290+ """
291+ self.manufacturer = data
292+ log.debug('({ip}) Setting projector manufacturer data to "{data}"'.format(ip=self.ip, data=self.manufacturer))
293+ return
294+
295+ def process_inf2(self, data):
296+ """
297+ Projector Model set in projector.
298+ Updates self.model.
299+
300+ :param data: Model name
301+ """
302+ self.model = data
303+ log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.ip, data=self.model))
304+ return
305+
306+ def process_info(self, data):
307+ """
308+ Any extra info set in projector.
309+ Updates self.other_info.
310+
311+ :param data: Projector other info
312+ """
313+ self.other_info = data
314+ log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.ip, data=self.other_info))
315+ return
316+
317+ def process_inpt(self, data):
318+ """
319+ Current source input selected. See PJLink specification for format.
320+ Update self.source
321+
322+ :param data: Currently selected source
323+ """
324+ self.source = data
325+ log.info('({ip}) Setting data source to "{data}"'.format(ip=self.ip, data=self.source))
326+ return
327+
328+ def process_inst(self, data):
329+ """
330+ Available source inputs. See PJLink specification for format.
331+ Updates self.source_available
332+
333+ :param data: Sources list
334+ """
335+ sources = []
336+ check = data.split()
337+ for source in check:
338+ sources.append(source)
339+ sources.sort()
340+ self.source_available = sources
341+ self.projectorUpdateIcons.emit()
342+ log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.ip,
343+ data=self.source_available))
344+ return
345+
346+ def process_lamp(self, data):
347+ """
348+ Lamp(s) status. See PJLink Specifications for format.
349+ Data may have more than 1 lamp to process.
350+ Update self.lamp dictionary with lamp status.
351+
352+ :param data: Lamp(s) status.
353+ """
354+ lamps = []
355+ data_dict = data.split()
356+ while data_dict:
357+ try:
358+ fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True}
359+ except ValueError:
360+ # In case of invalid entry
361+ log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data))
362+ return
363+ lamps.append(fill)
364+ data_dict.pop(0) # Remove lamp hours
365+ data_dict.pop(0) # Remove lamp on/off
366+ self.lamp = lamps
367+ return
368+
369+ def process_name(self, data):
370+ """
371+ Projector name set in projector.
372+ Updates self.pjlink_name
373+
374+ :param data: Projector name
375+ """
376+ self.pjlink_name = data
377+ log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name))
378+ return
379+
380+ def process_powr(self, data):
381+ """
382+ Power status. See PJLink specification for format.
383+ Update self.power with status. Update icons if change from previous setting.
384+
385+ :param data: Power status
386+ """
387+ log.debug('({ip}: Processing POWR command'.format(ip=self.ip))
388+ if data in PJLINK_POWR_STATUS:
389+ power = PJLINK_POWR_STATUS[data]
390+ update_icons = self.power != power
391+ self.power = power
392+ self.change_status(PJLINK_POWR_STATUS[data])
393+ if update_icons:
394+ self.projectorUpdateIcons.emit()
395+ # Update the input sources available
396+ if power == S_ON:
397+ self.send_command('INST')
398+ else:
399+ # Log unknown status response
400+ log.warning('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data))
401+ return
402+
403+ def process_rfil(self, data):
404+ """
405+ Process replacement filter type
406+ """
407+ if self.model_filter is None:
408+ self.model_filter = data
409+ else:
410+ log.warn("({ip}) Filter model already set".format(ip=self.ip))
411+ log.warn("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter))
412+ log.warn("({ip}) New model: '{new}'".format(ip=self.ip, new=data))
413+
414+ def process_rlmp(self, data):
415+ """
416+ Process replacement lamp type
417+ """
418+ if self.model_lamp is None:
419+ self.model_lamp = data
420+ else:
421+ log.warn("({ip}) Lamp model already set".format(ip=self.ip))
422+ log.warn("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp))
423+ log.warn("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data))
424+
425+ def process_snum(self, data):
426+ """
427+ Serial number of projector.
428+
429+ :param data: Serial number from projector.
430+ """
431+ if self.serial_no is None:
432+ log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data))
433+ self.serial_no = data
434+ self.db_update = False
435+ else:
436+ # Compare serial numbers and see if we got the same projector
437+ if self.serial_no != data:
438+ log.warn("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip))
439+ log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no))
440+ log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
441+ log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
442+ self.serial_no_received = data
443+
444+ def process_sver(self, data):
445+ """
446+ Software version of projector
447+ """
448+ if self.sw_version is None:
449+ log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data))
450+ self.sw_version = data
451+ self.db_update = True
452+ else:
453+ # Compare software version and see if we got the same projector
454+ if self.serial_no != data:
455+ log.warn("({ip}) Projector software version does not match saved software version".format(ip=self.ip))
456+ log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version))
457+ log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
458+ log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
459+ self.sw_version_received = data
460+
461+
462+class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
463 """
464 Socket service for connecting to a PJLink-capable projector.
465 """
466@@ -84,17 +483,18 @@
467
468 # New commands available in PJLink Class 2
469 pjlink_udp_commands = [
470- 'ACKN',
471+ 'ACKN', # Class 2
472 'ERST', # Class 1 or 2
473 'INPT', # Class 1 or 2
474- 'LKUP',
475+ 'LKUP', # Class 2
476 'POWR', # Class 1 or 2
477- 'SRCH'
478+ 'SRCH' # Class 2
479 ]
480
481- def __init__(self, name=None, ip=None, port=PJLINK_PORT, pin=None, *args, **kwargs):
482+ def __init__(self, port=PJLINK_PORT, *args, **kwargs):
483 """
484 Setup for instance.
485+ Options should be in kwargs except for port which does have a default.
486
487 :param name: Display name
488 :param ip: IP address to connect to
489@@ -109,23 +509,16 @@
490 :param socket_timeout: Time (in seconds) to abort the connection if no response
491 """
492 log.debug('PJlink(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
493- self.name = name
494- self.ip = ip
495- self.port = port
496- self.pin = pin
497 super().__init__()
498- self.model_lamp = None
499- self.model_filter = None
500- self.mac_adx = kwargs.get('mac_adx')
501- self.serial_no = None
502- self.serial_no_received = None # Used only if saved serial number is different than received serial number
503- self.dbid = None
504- self.db_update = False # Use to check if db needs to be updated prior to exiting
505- self.location = None
506- self.notes = None
507 self.dbid = kwargs.get('dbid')
508+ self.ip = kwargs.get('ip')
509 self.location = kwargs.get('location')
510+ self.mac_adx = kwargs.get('mac_adx')
511+ self.name = kwargs.get('name')
512 self.notes = kwargs.get('notes')
513+ self.pin = kwargs.get('pin')
514+ self.port = port
515+ self.db_update = False # Use to check if db needs to be updated prior to exiting
516 # Poll time 20 seconds unless called with something else
517 self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
518 # Timeout 5 seconds unless called with something else
519@@ -141,8 +534,6 @@
520 # Add enough space to input buffer for extraneous \n \r
521 self.max_size = PJLINK_MAX_PACKET + 2
522 self.setReadBufferSize(self.max_size)
523- # PJLink information
524- self.pjlink_class = '1' # Default class
525 self.reset_information()
526 # Set from ProjectorManager.add_projector()
527 self.widget = None # QListBox entry
528@@ -151,58 +542,6 @@
529 self.send_busy = False
530 # Socket timer for some possible brain-dead projectors or network cable pulled
531 self.socket_timer = None
532- # Map command to function
533- self.pjlink_functions = {
534- 'AVMT': self.process_avmt,
535- 'CLSS': self.process_clss,
536- 'ERST': self.process_erst,
537- 'INFO': self.process_info,
538- 'INF1': self.process_inf1,
539- 'INF2': self.process_inf2,
540- 'INPT': self.process_inpt,
541- 'INST': self.process_inst,
542- 'LAMP': self.process_lamp,
543- 'NAME': self.process_name,
544- 'PJLINK': self.check_login,
545- 'POWR': self.process_powr,
546- 'SNUM': self.process_snum,
547- 'SVER': self.process_sver,
548- 'RFIL': self.process_rfil,
549- 'RLMP': self.process_rlmp
550- }
551-
552- def reset_information(self):
553- """
554- Reset projector-specific information to default
555- """
556- log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state()))
557- self.send_queue = []
558- self.power = S_OFF
559- self.pjlink_name = None
560- self.manufacturer = None
561- self.model = None
562- self.serial_no = None
563- self.serial_no_received = None
564- self.sw_version = None
565- self.sw_version_received = None
566- self.mac_adx = None
567- self.shutter = None
568- self.mute = None
569- self.lamp = None
570- self.model_lamp = None
571- self.fan = None
572- self.filter_time = None
573- self.model_filter = None
574- self.source_available = None
575- self.source = None
576- self.other_info = None
577- if hasattr(self, 'timer'):
578- log.debug('({ip}): Calling timer.stop()'.format(ip=self.ip))
579- self.timer.stop()
580- if hasattr(self, 'socket_timer'):
581- log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.ip))
582- self.socket_timer.stop()
583- self.send_busy = False
584
585 def thread_started(self):
586 """
587@@ -290,28 +629,6 @@
588 if self.model_lamp is None:
589 self.send_command('RLMP', queue=True)
590
591- def process_rfil(self, data):
592- """
593- Process replacement filter type
594- """
595- if self.model_filter is None:
596- self.model_filter = data
597- else:
598- log.warn("({ip}) Filter model already set".format(ip=self.ip))
599- log.warn("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter))
600- log.warn("({ip}) New model: '{new}'".format(ip=self.ip, new=data))
601-
602- def process_rlmp(self, data):
603- """
604- Process replacement lamp type
605- """
606- if self.model_lamp is None:
607- self.model_lamp = data
608- else:
609- log.warn("({ip}) Lamp model already set".format(ip=self.ip))
610- log.warn("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp))
611- log.warn("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data))
612-
613 def _get_status(self, status):
614 """
615 Helper to retrieve status/error codes and convert to strings.
616@@ -474,6 +791,7 @@
617 self.send_busy = False
618 return
619 read = self.readLine(self.max_size)
620+ log.debug("({ip}) get_data(): '{buff}'".format(ip=self.ip, buff=read))
621 if read == -1:
622 # No data available
623 log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip))
624@@ -626,317 +944,6 @@
625 self.change_status(E_NETWORK,
626 translate('OpenLP.PJLink', 'Error while sending data to projector'))
627
628- def process_command(self, cmd, data):
629- """
630- Verifies any return error code. Calls the appropriate command handler.
631-
632- :param cmd: Command to process
633- :param data: Data being processed
634- """
635- log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip,
636- cmd=cmd,
637- data=data))
638- # Check if we have a future command not available yet
639- if cmd not in PJLINK_VALID_CMD:
640- log.error('({ip}) Unknown command received - ignoring'.format(ip=self.ip))
641- return
642- elif cmd not in self.pjlink_functions:
643- log.warn('({ip}) Future command received - unable to process yet'.format(ip=self.ip))
644- return
645- elif data in PJLINK_ERRORS:
646- # Oops - projector error
647- log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data))
648- if data.upper() == 'ERRA':
649- # Authentication error
650- self.disconnect_from_host()
651- self.change_status(E_AUTHENTICATION)
652- log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
653- self.projectorAuthentication.emit(self.name)
654- elif data.upper() == 'ERR1':
655- # Undefined command
656- self.change_status(E_UNDEFINED, '{error}: "{data}"'.format(error=ERROR_MSG[E_UNDEFINED],
657- data=cmd))
658- elif data.upper() == 'ERR2':
659- # Invalid parameter
660- self.change_status(E_PARAMETER)
661- elif data.upper() == 'ERR3':
662- # Projector busy
663- self.change_status(E_UNAVAILABLE)
664- elif data.upper() == 'ERR4':
665- # Projector/display error
666- self.change_status(E_PROJECTOR)
667- self.receive_data_signal()
668- return
669- # Command succeeded - no extra information
670- elif data.upper() == 'OK':
671- log.debug('({ip}) Command returned OK'.format(ip=self.ip))
672- # A command returned successfully
673- self.receive_data_signal()
674- return
675- # Command checks already passed
676- log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
677- self.receive_data_signal()
678- self.pjlink_functions[cmd](data)
679-
680- def process_lamp(self, data):
681- """
682- Lamp(s) status. See PJLink Specifications for format.
683- Data may have more than 1 lamp to process.
684- Update self.lamp dictionary with lamp status.
685-
686- :param data: Lamp(s) status.
687- """
688- lamps = []
689- data_dict = data.split()
690- while data_dict:
691- try:
692- fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True}
693- except ValueError:
694- # In case of invalid entry
695- log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data))
696- return
697- lamps.append(fill)
698- data_dict.pop(0) # Remove lamp hours
699- data_dict.pop(0) # Remove lamp on/off
700- self.lamp = lamps
701- return
702-
703- def process_powr(self, data):
704- """
705- Power status. See PJLink specification for format.
706- Update self.power with status. Update icons if change from previous setting.
707-
708- :param data: Power status
709- """
710- log.debug('({ip}: Processing POWR command'.format(ip=self.ip))
711- if data in PJLINK_POWR_STATUS:
712- power = PJLINK_POWR_STATUS[data]
713- update_icons = self.power != power
714- self.power = power
715- self.change_status(PJLINK_POWR_STATUS[data])
716- if update_icons:
717- self.projectorUpdateIcons.emit()
718- # Update the input sources available
719- if power == S_ON:
720- self.send_command('INST')
721- else:
722- # Log unknown status response
723- log.warning('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data))
724- return
725-
726- def process_avmt(self, data):
727- """
728- Process shutter and speaker status. See PJLink specification for format.
729- Update self.mute (audio) and self.shutter (video shutter).
730-
731- :param data: Shutter and audio status
732- """
733- shutter = self.shutter
734- mute = self.mute
735- if data == '11':
736- shutter = True
737- mute = False
738- elif data == '21':
739- shutter = False
740- mute = True
741- elif data == '30':
742- shutter = False
743- mute = False
744- elif data == '31':
745- shutter = True
746- mute = True
747- else:
748- log.warning('({ip}) Unknown shutter response: {data}'.format(ip=self.ip, data=data))
749- update_icons = shutter != self.shutter
750- update_icons = update_icons or mute != self.mute
751- self.shutter = shutter
752- self.mute = mute
753- if update_icons:
754- self.projectorUpdateIcons.emit()
755- return
756-
757- def process_inpt(self, data):
758- """
759- Current source input selected. See PJLink specification for format.
760- Update self.source
761-
762- :param data: Currently selected source
763- """
764- self.source = data
765- log.info('({ip}) Setting data source to "{data}"'.format(ip=self.ip, data=self.source))
766- return
767-
768- def process_clss(self, data):
769- """
770- PJLink class that this projector supports. See PJLink specification for format.
771- Updates self.class.
772-
773- :param data: Class that projector supports.
774- """
775- # bug 1550891: Projector returns non-standard class response:
776- # : Expected: '%1CLSS=1'
777- # : Received: '%1CLSS=Class 1' (Optoma)
778- # : Received: '%1CLSS=Version1' (BenQ)
779- if len(data) > 1:
780- log.warn("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data))
781- # Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
782- # AND the different responses that can be received, the semi-permanent way to
783- # fix the class reply is to just remove all non-digit characters.
784- try:
785- clss = re.findall('\d', data)[0] # Should only be the first match
786- except IndexError:
787- log.error("({ip}) No numbers found in class version reply - defaulting to class '1'".format(ip=self.ip))
788- clss = '1'
789- elif not data.isdigit():
790- log.error("({ip}) NAN class version reply - defaulting to class '1'".format(ip=self.ip))
791- clss = '1'
792- else:
793- clss = data
794- self.pjlink_class = clss
795- log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.ip,
796- data=self.pjlink_class))
797- return
798-
799- def process_name(self, data):
800- """
801- Projector name set in projector.
802- Updates self.pjlink_name
803-
804- :param data: Projector name
805- """
806- self.pjlink_name = data
807- log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name))
808- return
809-
810- def process_inf1(self, data):
811- """
812- Manufacturer name set in projector.
813- Updates self.manufacturer
814-
815- :param data: Projector manufacturer
816- """
817- self.manufacturer = data
818- log.debug('({ip}) Setting projector manufacturer data to "{data}"'.format(ip=self.ip, data=self.manufacturer))
819- return
820-
821- def process_inf2(self, data):
822- """
823- Projector Model set in projector.
824- Updates self.model.
825-
826- :param data: Model name
827- """
828- self.model = data
829- log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.ip, data=self.model))
830- return
831-
832- def process_info(self, data):
833- """
834- Any extra info set in projector.
835- Updates self.other_info.
836-
837- :param data: Projector other info
838- """
839- self.other_info = data
840- log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.ip, data=self.other_info))
841- return
842-
843- def process_inst(self, data):
844- """
845- Available source inputs. See PJLink specification for format.
846- Updates self.source_available
847-
848- :param data: Sources list
849- """
850- sources = []
851- check = data.split()
852- for source in check:
853- sources.append(source)
854- sources.sort()
855- self.source_available = sources
856- self.projectorUpdateIcons.emit()
857- log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.ip,
858- data=self.source_available))
859- return
860-
861- def process_erst(self, data):
862- """
863- Error status. See PJLink Specifications for format.
864- Updates self.projector_errors
865-
866- :param data: Error status
867- """
868- try:
869- datacheck = int(data)
870- except ValueError:
871- # Bad data - ignore
872- return
873- if datacheck == 0:
874- self.projector_errors = None
875- else:
876- self.projector_errors = {}
877- # Fan
878- if data[0] != '0':
879- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \
880- PJLINK_ERST_STATUS[data[0]]
881- # Lamp
882- if data[1] != '0':
883- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \
884- PJLINK_ERST_STATUS[data[1]]
885- # Temp
886- if data[2] != '0':
887- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \
888- PJLINK_ERST_STATUS[data[2]]
889- # Cover
890- if data[3] != '0':
891- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \
892- PJLINK_ERST_STATUS[data[3]]
893- # Filter
894- if data[4] != '0':
895- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \
896- PJLINK_ERST_STATUS[data[4]]
897- # Other
898- if data[5] != '0':
899- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \
900- PJLINK_ERST_STATUS[data[5]]
901- return
902-
903- def process_snum(self, data):
904- """
905- Serial number of projector.
906-
907- :param data: Serial number from projector.
908- """
909- if self.serial_no is None:
910- log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data))
911- self.serial_no = data
912- self.db_update = False
913- else:
914- # Compare serial numbers and see if we got the same projector
915- if self.serial_no != data:
916- log.warn("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip))
917- log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no))
918- log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
919- log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
920- self.serial_no_received = data
921-
922- def process_sver(self, data):
923- """
924- Software version of projector
925- """
926- if self.sw_version is None:
927- log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data))
928- self.sw_version = data
929- self.db_update = True
930- else:
931- # Compare software version and see if we got the same projector
932- if self.serial_no != data:
933- log.warn("({ip}) Projector software version does not match saved software version".format(ip=self.ip))
934- log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version))
935- log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
936- log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
937- self.sw_version_received = data
938-
939 def connect_to_host(self):
940 """
941 Initiate connection to projector.
942@@ -1098,11 +1105,3 @@
943 self.send_busy = False
944 self.projectorReceivedData.emit()
945 return
946-
947- def _not_implemented(self, cmd):
948- """
949- Log when a future PJLink command has not been implemented yet.
950- """
951- log.warn("({ip}) Future command '{cmd}' has not been implemented yet".format(ip=self.ip,
952- cmd=cmd))
953- return
954
955=== modified file 'openlp/core/lib/projector/upgrade.py'
956--- openlp/core/lib/projector/upgrade.py 2017-07-07 23:43:50 +0000
957+++ openlp/core/lib/projector/upgrade.py 2017-08-07 00:09:00 +0000
958@@ -42,7 +42,7 @@
959 """
960 Version 1 upgrade - old db might/might not be versioned.
961 """
962- log.debug('Skipping upgrade_1 of projector DB - not used')
963+ log.debug('Skipping projector DB upgrade to version 1 - not used')
964
965
966 def upgrade_2(session, metadata):
967@@ -60,14 +60,14 @@
968 :param session: DB session instance
969 :param metadata: Metadata of current DB
970 """
971+ log.debug('Checking projector DB upgrade to version 2')
972 projector_table = Table('projector', metadata, autoload=True)
973- if 'mac_adx' not in [col.name for col in projector_table.c.values()]:
974- log.debug("Upgrading projector DB to version '2'")
975+ upgrade_db = 'mac_adx' not in [col.name for col in projector_table.c.values()]
976+ if upgrade_db:
977 new_op = get_upgrade_op(session)
978 new_op.add_column('projector', Column('mac_adx', types.String(18), server_default=null()))
979 new_op.add_column('projector', Column('serial_no', types.String(30), server_default=null()))
980 new_op.add_column('projector', Column('sw_version', types.String(30), server_default=null()))
981 new_op.add_column('projector', Column('model_filter', types.String(30), server_default=null()))
982 new_op.add_column('projector', Column('model_lamp', types.String(30), server_default=null()))
983- else:
984- log.warn("Skipping upgrade_2 of projector DB")
985+ log.debug('{status} projector DB upgrade to version 2'.format(status='Updated' if upgrade_db else 'Skipping'))
986
987=== modified file 'openlp/core/ui/projector/manager.py'
988--- openlp/core/ui/projector/manager.py 2017-07-07 23:43:50 +0000
989+++ openlp/core/ui/projector/manager.py 2017-08-07 00:09:00 +0000
990@@ -38,7 +38,7 @@
991 E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
992 S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
993 from openlp.core.lib.projector.db import ProjectorDB
994-from openlp.core.lib.projector.pjlink1 import PJLink
995+from openlp.core.lib.projector.pjlink import PJLink
996 from openlp.core.lib.projector.pjlink2 import PJLinkUDP
997 from openlp.core.ui.projector.editform import ProjectorEditForm
998 from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
999
1000=== modified file 'tests/functional/openlp_core_lib/test_projector_constants.py'
1001--- tests/functional/openlp_core_lib/test_projector_constants.py 2017-06-29 02:58:08 +0000
1002+++ tests/functional/openlp_core_lib/test_projector_constants.py 2017-08-07 00:09:00 +0000
1003@@ -22,7 +22,7 @@
1004 """
1005 Package to test the openlp.core.lib.projector.constants package.
1006 """
1007-from unittest import TestCase, skip
1008+from unittest import TestCase
1009
1010
1011 class TestProjectorConstants(TestCase):
1012@@ -40,4 +40,4 @@
1013 from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
1014
1015 # THEN: Verify dictionary was build correctly
1016- self.assertEquals(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match')
1017+ self.assertEqual(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match')
1018
1019=== modified file 'tests/functional/openlp_core_lib/test_projector_db.py'
1020--- tests/functional/openlp_core_lib/test_projector_db.py 2017-06-29 02:58:08 +0000
1021+++ tests/functional/openlp_core_lib/test_projector_db.py 2017-08-07 00:09:00 +0000
1022@@ -29,8 +29,8 @@
1023 import shutil
1024 from tempfile import mkdtemp
1025
1026-from unittest import TestCase, skip
1027-from unittest.mock import MagicMock, patch
1028+from unittest import TestCase
1029+from unittest.mock import patch
1030
1031 from openlp.core.lib.projector import upgrade
1032 from openlp.core.lib.db import upgrade_db
1033@@ -413,7 +413,7 @@
1034 Test add_projector() fail
1035 """
1036 # GIVEN: Test entry in the database
1037- ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))
1038+ self.projector.add_projector(Projector(**TEST1_DATA))
1039
1040 # WHEN: Attempt to add same projector entry
1041 results = self.projector.add_projector(Projector(**TEST1_DATA))
1042@@ -439,7 +439,7 @@
1043 Test update_projector() when entry not in database
1044 """
1045 # GIVEN: Projector entry in database
1046- ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))
1047+ self.projector.add_projector(Projector(**TEST1_DATA))
1048 projector = Projector(**TEST2_DATA)
1049
1050 # WHEN: Attempt to update data with a different ID
1051
1052=== renamed file 'tests/functional/openlp_core_lib/test_projector_pjlink1.py' => 'tests/functional/openlp_core_lib/test_projector_pjlink_base.py'
1053--- tests/functional/openlp_core_lib/test_projector_pjlink1.py 2017-06-29 02:58:08 +0000
1054+++ tests/functional/openlp_core_lib/test_projector_pjlink_base.py 2017-08-07 00:09:00 +0000
1055@@ -20,21 +20,20 @@
1056 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1057 ###############################################################################
1058 """
1059-Package to test the openlp.core.lib.projector.pjlink1 package.
1060+Package to test the openlp.core.lib.projector.pjlink base package.
1061 """
1062 from unittest import TestCase
1063 from unittest.mock import call, patch, MagicMock
1064
1065-from openlp.core.lib.projector.pjlink1 import PJLink
1066-from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_OFF, S_STANDBY, S_ON, \
1067- PJLINK_POWR_STATUS, S_CONNECTED
1068+from openlp.core.lib.projector.pjlink import PJLink
1069+from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED
1070
1071 from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH
1072
1073 pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
1074
1075
1076-class TestPJLink(TestCase):
1077+class TestPJLinkBase(TestCase):
1078 """
1079 Tests for the PJLink module
1080 """
1081@@ -42,7 +41,10 @@
1082 @patch.object(pjlink_test, 'send_command')
1083 @patch.object(pjlink_test, 'waitForReadyRead')
1084 @patch('openlp.core.common.qmd5_hash')
1085- def test_authenticated_connection_call(self, mock_qmd5_hash, mock_waitForReadyRead, mock_send_command,
1086+ def test_authenticated_connection_call(self,
1087+ mock_qmd5_hash,
1088+ mock_waitForReadyRead,
1089+ mock_send_command,
1090 mock_readyRead):
1091 """
1092 Ticket 92187: Fix for projector connect with PJLink authentication exception.
1093@@ -59,140 +61,6 @@
1094 self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
1095 "Connection request should have been called with TEST_PIN"))
1096
1097- def test_projector_process_rfil_save(self):
1098- """
1099- Test saving filter type
1100- """
1101- # GIVEN: Test object
1102- pjlink = pjlink_test
1103- pjlink.model_filter = None
1104- filter_model = 'Filter Type Test'
1105-
1106- # WHEN: Filter model is received
1107- pjlink.process_rfil(data=filter_model)
1108-
1109- # THEN: Filter model number should be saved
1110- self.assertEqual(pjlink.model_filter, filter_model, 'Filter type should have been saved')
1111-
1112- def test_projector_process_rfil_nosave(self):
1113- """
1114- Test saving filter type previously saved
1115- """
1116- # GIVEN: Test object
1117- pjlink = pjlink_test
1118- pjlink.model_filter = 'Old filter type'
1119- filter_model = 'Filter Type Test'
1120-
1121- # WHEN: Filter model is received
1122- pjlink.process_rfil(data=filter_model)
1123-
1124- # THEN: Filter model number should be saved
1125- self.assertNotEquals(pjlink.model_filter, filter_model, 'Filter type should NOT have been saved')
1126-
1127- def test_projector_process_rlmp_save(self):
1128- """
1129- Test saving lamp type
1130- """
1131- # GIVEN: Test object
1132- pjlink = pjlink_test
1133- pjlink.model_lamp = None
1134- lamp_model = 'Lamp Type Test'
1135-
1136- # WHEN: Filter model is received
1137- pjlink.process_rlmp(data=lamp_model)
1138-
1139- # THEN: Filter model number should be saved
1140- self.assertEqual(pjlink.model_lamp, lamp_model, 'Lamp type should have been saved')
1141-
1142- def test_projector_process_rlmp_nosave(self):
1143- """
1144- Test saving lamp type previously saved
1145- """
1146- # GIVEN: Test object
1147- pjlink = pjlink_test
1148- pjlink.model_lamp = 'Old lamp type'
1149- lamp_model = 'Filter Type Test'
1150-
1151- # WHEN: Filter model is received
1152- pjlink.process_rlmp(data=lamp_model)
1153-
1154- # THEN: Filter model number should be saved
1155- self.assertNotEquals(pjlink.model_lamp, lamp_model, 'Lamp type should NOT have been saved')
1156-
1157- def test_projector_process_snum_set(self):
1158- """
1159- Test saving serial number from projector
1160- """
1161- # GIVEN: Test object
1162- pjlink = pjlink_test
1163- pjlink.serial_no = None
1164- test_number = 'Test Serial Number'
1165-
1166- # WHEN: No serial number is set and we receive serial number command
1167- pjlink.process_snum(data=test_number)
1168-
1169- # THEN: Serial number should be set
1170- self.assertEqual(pjlink.serial_no, test_number,
1171- 'Projector serial number should have been set')
1172-
1173- def test_projector_process_snum_different(self):
1174- """
1175- Test projector serial number different than saved serial number
1176- """
1177- # GIVEN: Test object
1178- pjlink = pjlink_test
1179- pjlink.serial_no = 'Previous serial number'
1180- test_number = 'Test Serial Number'
1181-
1182- # WHEN: No serial number is set and we receive serial number command
1183- pjlink.process_snum(data=test_number)
1184-
1185- # THEN: Serial number should be set
1186- self.assertNotEquals(pjlink.serial_no, test_number,
1187- 'Projector serial number should NOT have been set')
1188-
1189- def test_projector_clss_one(self):
1190- """
1191- Test class 1 sent from projector
1192- """
1193- # GIVEN: Test object
1194- pjlink = pjlink_test
1195-
1196- # WHEN: Process class response
1197- pjlink.process_clss('1')
1198-
1199- # THEN: Projector class should be set to 1
1200- self.assertEqual(pjlink.pjlink_class, '1',
1201- 'Projector should have returned class=1')
1202-
1203- def test_projector_clss_two(self):
1204- """
1205- Test class 2 sent from projector
1206- """
1207- # GIVEN: Test object
1208- pjlink = pjlink_test
1209-
1210- # WHEN: Process class response
1211- pjlink.process_clss('2')
1212-
1213- # THEN: Projector class should be set to 1
1214- self.assertEqual(pjlink.pjlink_class, '2',
1215- 'Projector should have returned class=2')
1216-
1217- def test_bug_1550891_non_standard_class_reply(self):
1218- """
1219- Bugfix 1550891: CLSS request returns non-standard reply
1220- """
1221- # GIVEN: Test object
1222- pjlink = pjlink_test
1223-
1224- # WHEN: Process non-standard reply
1225- pjlink.process_clss('Class 1')
1226-
1227- # THEN: Projector class should be set with proper value
1228- self.assertEqual(pjlink.pjlink_class, '1',
1229- 'Non-standard class reply should have set class=1')
1230-
1231 @patch.object(pjlink_test, 'change_status')
1232 def test_status_change(self, mock_change_status):
1233 """
1234@@ -210,242 +78,13 @@
1235 'change_status should have been called with "{}"'.format(
1236 ERROR_STRING[E_PARAMETER]))
1237
1238- @patch.object(pjlink_test, 'process_inpt')
1239- def test_projector_return_ok(self, mock_process_inpt):
1240- """
1241- Test projector calls process_inpt command when process_command is called with INPT option
1242- """
1243- # GIVEN: Test object
1244- pjlink = pjlink_test
1245-
1246- # WHEN: process_command is called with INST command and 31 input:
1247- pjlink.process_command('INPT', '31')
1248-
1249- # THEN: process_inpt method should have been called with 31
1250- mock_process_inpt.called_with('31',
1251- "process_inpt should have been called with 31")
1252-
1253- @patch.object(pjlink_test, 'projectorReceivedData')
1254- def test_projector_process_lamp(self, mock_projectorReceivedData):
1255- """
1256- Test status lamp on/off and hours
1257- """
1258- # GIVEN: Test object
1259- pjlink = pjlink_test
1260-
1261- # WHEN: Call process_command with lamp data
1262- pjlink.process_command('LAMP', '22222 1')
1263-
1264- # THEN: Lamp should have been set with status=ON and hours=22222
1265- self.assertEqual(pjlink.lamp[0]['On'], True,
1266- 'Lamp power status should have been set to TRUE')
1267- self.assertEqual(pjlink.lamp[0]['Hours'], 22222,
1268- 'Lamp hours should have been set to 22222')
1269-
1270- @patch.object(pjlink_test, 'projectorReceivedData')
1271- def test_projector_process_multiple_lamp(self, mock_projectorReceivedData):
1272- """
1273- Test status multiple lamp on/off and hours
1274- """
1275- # GIVEN: Test object
1276- pjlink = pjlink_test
1277-
1278- # WHEN: Call process_command with lamp data
1279- pjlink.process_command('LAMP', '11111 1 22222 0 33333 1')
1280-
1281- # THEN: Lamp should have been set with proper lamp status
1282- self.assertEqual(len(pjlink.lamp), 3,
1283- 'Projector should have 3 lamps specified')
1284- self.assertEqual(pjlink.lamp[0]['On'], True,
1285- 'Lamp 1 power status should have been set to TRUE')
1286- self.assertEqual(pjlink.lamp[0]['Hours'], 11111,
1287- 'Lamp 1 hours should have been set to 11111')
1288- self.assertEqual(pjlink.lamp[1]['On'], False,
1289- 'Lamp 2 power status should have been set to FALSE')
1290- self.assertEqual(pjlink.lamp[1]['Hours'], 22222,
1291- 'Lamp 2 hours should have been set to 22222')
1292- self.assertEqual(pjlink.lamp[2]['On'], True,
1293- 'Lamp 3 power status should have been set to TRUE')
1294- self.assertEqual(pjlink.lamp[2]['Hours'], 33333,
1295- 'Lamp 3 hours should have been set to 33333')
1296-
1297- @patch.object(pjlink_test, 'projectorReceivedData')
1298- @patch.object(pjlink_test, 'projectorUpdateIcons')
1299- @patch.object(pjlink_test, 'send_command')
1300- @patch.object(pjlink_test, 'change_status')
1301- def test_projector_process_power_on(self, mock_change_status,
1302- mock_send_command,
1303- mock_UpdateIcons,
1304- mock_ReceivedData):
1305- """
1306- Test status power to ON
1307- """
1308- # GIVEN: Test object and preset
1309- pjlink = pjlink_test
1310- pjlink.power = S_STANDBY
1311-
1312- # WHEN: Call process_command with turn power on command
1313- pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_ON])
1314-
1315- # THEN: Power should be set to ON
1316- self.assertEqual(pjlink.power, S_ON, 'Power should have been set to ON')
1317- mock_send_command.assert_called_once_with('INST')
1318- self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
1319-
1320- @patch.object(pjlink_test, 'projectorReceivedData')
1321- @patch.object(pjlink_test, 'projectorUpdateIcons')
1322- @patch.object(pjlink_test, 'send_command')
1323- @patch.object(pjlink_test, 'change_status')
1324- def test_projector_process_power_off(self, mock_change_status,
1325- mock_send_command,
1326- mock_UpdateIcons,
1327- mock_ReceivedData):
1328- """
1329- Test status power to STANDBY
1330- """
1331- # GIVEN: Test object and preset
1332- pjlink = pjlink_test
1333- pjlink.power = S_ON
1334-
1335- # WHEN: Call process_command with turn power on command
1336- pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_STANDBY])
1337-
1338- # THEN: Power should be set to STANDBY
1339- self.assertEqual(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
1340- self.assertEqual(mock_send_command.called, False, 'send_command should not have been called')
1341- self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
1342-
1343- @patch.object(pjlink_test, 'projectorUpdateIcons')
1344- def test_projector_process_avmt_closed_unmuted(self, mock_projectorReceivedData):
1345- """
1346- Test avmt status shutter closed and audio muted
1347- """
1348- # GIVEN: Test object
1349- pjlink = pjlink_test
1350- pjlink.shutter = False
1351- pjlink.mute = True
1352-
1353- # WHEN: Called with setting shutter closed and mute off
1354- pjlink.process_avmt('11')
1355-
1356- # THEN: Shutter should be True and mute should be False
1357- self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
1358- self.assertFalse(pjlink.mute, 'Audio should be off')
1359-
1360- @patch.object(pjlink_test, 'projectorUpdateIcons')
1361- def test_projector_process_avmt_open_muted(self, mock_projectorReceivedData):
1362- """
1363- Test avmt status shutter open and mute on
1364- """
1365- # GIVEN: Test object
1366- pjlink = pjlink_test
1367- pjlink.shutter = True
1368- pjlink.mute = False
1369-
1370- # WHEN: Called with setting shutter closed and mute on
1371- pjlink.process_avmt('21')
1372-
1373- # THEN: Shutter should be closed and mute should be True
1374- self.assertFalse(pjlink.shutter, 'Shutter should have been set to closed')
1375- self.assertTrue(pjlink.mute, 'Audio should be off')
1376-
1377- @patch.object(pjlink_test, 'projectorUpdateIcons')
1378- def test_projector_process_avmt_open_unmuted(self, mock_projectorReceivedData):
1379- """
1380- Test avmt status shutter open and mute off off
1381- """
1382- # GIVEN: Test object
1383- pjlink = pjlink_test
1384- pjlink.shutter = True
1385- pjlink.mute = True
1386-
1387- # WHEN: Called with setting shutter to closed and mute on
1388- pjlink.process_avmt('30')
1389-
1390- # THEN: Shutter should be closed and mute should be True
1391- self.assertFalse(pjlink.shutter, 'Shutter should have been set to open')
1392- self.assertFalse(pjlink.mute, 'Audio should be on')
1393-
1394- @patch.object(pjlink_test, 'projectorUpdateIcons')
1395- def test_projector_process_avmt_closed_muted(self, mock_projectorReceivedData):
1396- """
1397- Test avmt status shutter closed and mute off
1398- """
1399- # GIVEN: Test object
1400- pjlink = pjlink_test
1401- pjlink.shutter = False
1402- pjlink.mute = False
1403-
1404- # WHEN: Called with setting shutter to closed and mute on
1405- pjlink.process_avmt('31')
1406-
1407- # THEN: Shutter should be closed and mute should be True
1408- self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
1409- self.assertTrue(pjlink.mute, 'Audio should be on')
1410-
1411- def test_projector_process_input(self):
1412- """
1413- Test input source status shows current input
1414- """
1415- # GIVEN: Test object
1416- pjlink = pjlink_test
1417- pjlink.source = '0'
1418-
1419- # WHEN: Called with input source
1420- pjlink.process_inpt('1')
1421-
1422- # THEN: Input selected should reflect current input
1423- self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"')
1424-
1425- def test_projector_reset_information(self):
1426- """
1427- Test reset_information() resets all information and stops timers
1428- """
1429- # GIVEN: Test object and test data
1430- pjlink = pjlink_test
1431- pjlink.power = S_ON
1432- pjlink.pjlink_name = 'OPENLPTEST'
1433- pjlink.manufacturer = 'PJLINK'
1434- pjlink.model = '1'
1435- pjlink.shutter = True
1436- pjlink.mute = True
1437- pjlink.lamp = True
1438- pjlink.fan = True
1439- pjlink.source_available = True
1440- pjlink.other_info = 'ANOTHER TEST'
1441- pjlink.send_queue = True
1442- pjlink.send_busy = True
1443- pjlink.timer = MagicMock()
1444- pjlink.socket_timer = MagicMock()
1445-
1446- # WHEN: reset_information() is called
1447- with patch.object(pjlink.timer, 'stop') as mock_timer:
1448- with patch.object(pjlink.socket_timer, 'stop') as mock_socket_timer:
1449- pjlink.reset_information()
1450-
1451- # THEN: All information should be reset and timers stopped
1452- self.assertEqual(pjlink.power, S_OFF, 'Projector power should be OFF')
1453- self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None')
1454- self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None')
1455- self.assertIsNone(pjlink.model, 'Projector model should be None')
1456- self.assertIsNone(pjlink.shutter, 'Projector shutter should be None')
1457- self.assertIsNone(pjlink.mute, 'Projector shuttter should be None')
1458- self.assertIsNone(pjlink.lamp, 'Projector lamp should be None')
1459- self.assertIsNone(pjlink.fan, 'Projector fan should be None')
1460- self.assertIsNone(pjlink.source_available, 'Projector source_available should be None')
1461- self.assertIsNone(pjlink.source, 'Projector source should be None')
1462- self.assertIsNone(pjlink.other_info, 'Projector other_info should be None')
1463- self.assertEqual(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
1464- self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False')
1465- self.assertTrue(mock_timer.called, 'Projector timer.stop() should have been called')
1466- self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called')
1467-
1468 @patch.object(pjlink_test, 'send_command')
1469 @patch.object(pjlink_test, 'waitForReadyRead')
1470 @patch.object(pjlink_test, 'projectorAuthentication')
1471 @patch.object(pjlink_test, 'timer')
1472 @patch.object(pjlink_test, 'socket_timer')
1473- def test_bug_1593882_no_pin_authenticated_connection(self, mock_socket_timer,
1474+ def test_bug_1593882_no_pin_authenticated_connection(self,
1475+ mock_socket_timer,
1476 mock_timer,
1477 mock_authentication,
1478 mock_ready_read,
1479@@ -469,7 +108,8 @@
1480 @patch.object(pjlink_test, '_send_command')
1481 @patch.object(pjlink_test, 'timer')
1482 @patch.object(pjlink_test, 'socket_timer')
1483- def test_bug_1593883_pjlink_authentication(self, mock_socket_timer,
1484+ def test_bug_1593883_pjlink_authentication(self,
1485+ mock_socket_timer,
1486 mock_timer,
1487 mock_send_command,
1488 mock_state,
1489@@ -491,7 +131,7 @@
1490 "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
1491
1492 @patch.object(pjlink_test, 'disconnect_from_host')
1493- def socket_abort_test(self, mock_disconnect):
1494+ def test_socket_abort(self, mock_disconnect):
1495 """
1496 Test PJLink.socket_abort calls disconnect_from_host
1497 """
1498@@ -504,7 +144,7 @@
1499 # THEN: disconnect_from_host should be called
1500 self.assertTrue(mock_disconnect.called, 'Should have called disconnect_from_host')
1501
1502- def poll_loop_not_connected_test(self):
1503+ def test_poll_loop_not_connected(self):
1504 """
1505 Test PJLink.poll_loop not connected return
1506 """
1507@@ -522,7 +162,7 @@
1508 self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method')
1509
1510 @patch.object(pjlink_test, 'send_command')
1511- def poll_loop_start_test(self, mock_send_command):
1512+ def test_poll_loop_start(self, mock_send_command):
1513 """
1514 Test PJLink.poll_loop makes correct calls
1515 """
1516
1517=== added file 'tests/functional/openlp_core_lib/test_projector_pjlink_commands.py'
1518--- tests/functional/openlp_core_lib/test_projector_pjlink_commands.py 1970-01-01 00:00:00 +0000
1519+++ tests/functional/openlp_core_lib/test_projector_pjlink_commands.py 2017-08-07 00:09:00 +0000
1520@@ -0,0 +1,468 @@
1521+# -*- coding: utf-8 -*-
1522+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
1523+
1524+###############################################################################
1525+# OpenLP - Open Source Lyrics Projection #
1526+# --------------------------------------------------------------------------- #
1527+# Copyright (c) 2008-2015 OpenLP Developers #
1528+# --------------------------------------------------------------------------- #
1529+# This program is free software; you can redistribute it and/or modify it #
1530+# under the terms of the GNU General Public License as published by the Free #
1531+# Software Foundation; version 2 of the License. #
1532+# #
1533+# This program is distributed in the hope that it will be useful, but WITHOUT #
1534+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
1535+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
1536+# more details. #
1537+# #
1538+# You should have received a copy of the GNU General Public License along #
1539+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
1540+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1541+###############################################################################
1542+"""
1543+Package to test the openlp.core.lib.projector.pjlink commands package.
1544+"""
1545+from unittest import TestCase
1546+from unittest.mock import patch, MagicMock
1547+
1548+from openlp.core.lib.projector.pjlink import PJLink
1549+from openlp.core.lib.projector.constants import PJLINK_ERST_STATUS, PJLINK_POWR_STATUS, \
1550+ S_OFF, S_STANDBY, S_ON
1551+
1552+from tests.resources.projector.data import TEST_PIN
1553+
1554+pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
1555+
1556+# ERST status codes
1557+ERST_OK, ERST_WARN, ERST_ERR = '0', '1', '2'
1558+
1559+
1560+class TestPJLinkCommands(TestCase):
1561+ """
1562+ Tests for the PJLink module
1563+ """
1564+ def test_projector_reset_information(self):
1565+ """
1566+ Test reset_information() resets all information and stops timers
1567+ """
1568+ # GIVEN: Test object and test data
1569+ pjlink = pjlink_test
1570+ pjlink.power = S_ON
1571+ pjlink.pjlink_name = 'OPENLPTEST'
1572+ pjlink.manufacturer = 'PJLINK'
1573+ pjlink.model = '1'
1574+ pjlink.shutter = True
1575+ pjlink.mute = True
1576+ pjlink.lamp = True
1577+ pjlink.fan = True
1578+ pjlink.source_available = True
1579+ pjlink.other_info = 'ANOTHER TEST'
1580+ pjlink.send_queue = True
1581+ pjlink.send_busy = True
1582+ pjlink.timer = MagicMock()
1583+ pjlink.socket_timer = MagicMock()
1584+
1585+ # WHEN: reset_information() is called
1586+ with patch.object(pjlink.timer, 'stop') as mock_timer:
1587+ with patch.object(pjlink.socket_timer, 'stop') as mock_socket_timer:
1588+ pjlink.reset_information()
1589+
1590+ # THEN: All information should be reset and timers stopped
1591+ self.assertEqual(pjlink.power, S_OFF, 'Projector power should be OFF')
1592+ self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None')
1593+ self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None')
1594+ self.assertIsNone(pjlink.model, 'Projector model should be None')
1595+ self.assertIsNone(pjlink.shutter, 'Projector shutter should be None')
1596+ self.assertIsNone(pjlink.mute, 'Projector shuttter should be None')
1597+ self.assertIsNone(pjlink.lamp, 'Projector lamp should be None')
1598+ self.assertIsNone(pjlink.fan, 'Projector fan should be None')
1599+ self.assertIsNone(pjlink.source_available, 'Projector source_available should be None')
1600+ self.assertIsNone(pjlink.source, 'Projector source should be None')
1601+ self.assertIsNone(pjlink.other_info, 'Projector other_info should be None')
1602+ self.assertEqual(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
1603+ self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False')
1604+ self.assertTrue(mock_timer.called, 'Projector timer.stop() should have been called')
1605+ self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called')
1606+
1607+ @patch.object(pjlink_test, 'projectorUpdateIcons')
1608+ def test_projector_process_avmt_closed_muted(self, mock_projectorReceivedData):
1609+ """
1610+ Test avmt status shutter closed and mute off
1611+ """
1612+ # GIVEN: Test object
1613+ pjlink = pjlink_test
1614+ pjlink.shutter = False
1615+ pjlink.mute = False
1616+
1617+ # WHEN: Called with setting shutter to closed and mute on
1618+ pjlink.process_avmt('31')
1619+
1620+ # THEN: Shutter should be closed and mute should be True
1621+ self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
1622+ self.assertTrue(pjlink.mute, 'Audio should be off')
1623+
1624+ @patch.object(pjlink_test, 'projectorUpdateIcons')
1625+ def test_projector_process_avmt_shutter_closed(self, mock_projectorReceivedData):
1626+ """
1627+ Test avmt status shutter closed and audio muted
1628+ """
1629+ # GIVEN: Test object
1630+ pjlink = pjlink_test
1631+ pjlink.shutter = False
1632+ pjlink.mute = True
1633+
1634+ # WHEN: Called with setting shutter closed and mute off
1635+ pjlink.process_avmt('11')
1636+
1637+ # THEN: Shutter should be True and mute should be False
1638+ self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
1639+ self.assertTrue(pjlink.mute, 'Audio should not have changed')
1640+
1641+ @patch.object(pjlink_test, 'projectorUpdateIcons')
1642+ def test_projector_process_avmt_audio_muted(self, mock_projectorReceivedData):
1643+ """
1644+ Test avmt status shutter open and mute on
1645+ """
1646+ # GIVEN: Test object
1647+ pjlink = pjlink_test
1648+ pjlink.shutter = True
1649+ pjlink.mute = False
1650+
1651+ # WHEN: Called with setting shutter closed and mute on
1652+ pjlink.process_avmt('21')
1653+
1654+ # THEN: Shutter should be closed and mute should be True
1655+ self.assertTrue(pjlink.shutter, 'Shutter should not have changed')
1656+ self.assertTrue(pjlink.mute, 'Audio should be off')
1657+
1658+ @patch.object(pjlink_test, 'projectorUpdateIcons')
1659+ def test_projector_process_avmt_open_unmuted(self, mock_projectorReceivedData):
1660+ """
1661+ Test avmt status shutter open and mute off off
1662+ """
1663+ # GIVEN: Test object
1664+ pjlink = pjlink_test
1665+ pjlink.shutter = True
1666+ pjlink.mute = True
1667+
1668+ # WHEN: Called with setting shutter to closed and mute on
1669+ pjlink.process_avmt('30')
1670+
1671+ # THEN: Shutter should be closed and mute should be True
1672+ self.assertFalse(pjlink.shutter, 'Shutter should have been set to open')
1673+ self.assertFalse(pjlink.mute, 'Audio should be on')
1674+
1675+ def test_projector_process_clss_one(self):
1676+ """
1677+ Test class 1 sent from projector
1678+ """
1679+ # GIVEN: Test object
1680+ pjlink = pjlink_test
1681+
1682+ # WHEN: Process class response
1683+ pjlink.process_clss('1')
1684+
1685+ # THEN: Projector class should be set to 1
1686+ self.assertEqual(pjlink.pjlink_class, '1',
1687+ 'Projector should have returned class=1')
1688+
1689+ def test_projector_process_clss_two(self):
1690+ """
1691+ Test class 2 sent from projector
1692+ """
1693+ # GIVEN: Test object
1694+ pjlink = pjlink_test
1695+
1696+ # WHEN: Process class response
1697+ pjlink.process_clss('2')
1698+
1699+ # THEN: Projector class should be set to 1
1700+ self.assertEqual(pjlink.pjlink_class, '2',
1701+ 'Projector should have returned class=2')
1702+
1703+ def test_projector_process_clss_nonstandard_reply(self):
1704+ """
1705+ Bugfix 1550891: CLSS request returns non-standard reply
1706+ """
1707+ # GIVEN: Test object
1708+ pjlink = pjlink_test
1709+
1710+ # WHEN: Process non-standard reply
1711+ pjlink.process_clss('Class 1')
1712+
1713+ # THEN: Projector class should be set with proper value
1714+ self.assertEqual(pjlink.pjlink_class, '1',
1715+ 'Non-standard class reply should have set class=1')
1716+
1717+ def test_projector_process_erst_all_ok(self):
1718+ """
1719+ Test test_projector_process_erst_all_ok
1720+ """
1721+ # GIVEN: Test object
1722+ pjlink = pjlink_test
1723+ chk_test = ERST_OK
1724+
1725+ # WHEN: process_erst with no errors
1726+ pjlink.process_erst('{fan}{lamp}{temp}{cover}{filter}{other}'.format(fan=chk_test,
1727+ lamp=chk_test,
1728+ temp=chk_test,
1729+ cover=chk_test,
1730+ filter=chk_test,
1731+ other=chk_test))
1732+
1733+ # PJLink instance errors should be None
1734+ self.assertIsNone(pjlink.projector_errors, 'projector_errors should have been set to None')
1735+
1736+ def test_projector_process_erst_all_warn(self):
1737+ """
1738+ Test test_projector_process_erst_all_warn
1739+ """
1740+ # GIVEN: Test object
1741+ pjlink = pjlink_test
1742+ chk_test = ERST_WARN
1743+ chk_code = PJLINK_ERST_STATUS[chk_test]
1744+ chk_value = {'Fan': chk_code,
1745+ 'Lamp': chk_code,
1746+ 'Temperature': chk_code,
1747+ 'Cover': chk_code,
1748+ 'Filter': chk_code,
1749+ 'Other': chk_code
1750+ }
1751+
1752+ # WHEN: process_erst with status set to WARN
1753+ pjlink.process_erst('{fan}{lamp}{temp}{cover}{filter}{other}'.format(fan=chk_test,
1754+ lamp=chk_test,
1755+ temp=chk_test,
1756+ cover=chk_test,
1757+ filter=chk_test,
1758+ other=chk_test))
1759+
1760+ # PJLink instance errors should match chk_value
1761+ self.assertEqual(pjlink.projector_errors, chk_value,
1762+ 'projector_errors should have been set to all {err}'.format(err=chk_code))
1763+
1764+ def test_projector_process_erst_all_error(self):
1765+ """
1766+ Test test_projector_process_erst_all_error
1767+ """
1768+ # GIVEN: Test object
1769+ pjlink = pjlink_test
1770+ chk_test = ERST_ERR
1771+ chk_code = PJLINK_ERST_STATUS[chk_test]
1772+ chk_value = {'Fan': chk_code,
1773+ 'Lamp': chk_code,
1774+ 'Temperature': chk_code,
1775+ 'Cover': chk_code,
1776+ 'Filter': chk_code,
1777+ 'Other': chk_code
1778+ }
1779+
1780+ # WHEN: process_erst with status set to ERROR
1781+ pjlink.process_erst('{fan}{lamp}{temp}{cover}{filter}{other}'.format(fan=chk_test,
1782+ lamp=chk_test,
1783+ temp=chk_test,
1784+ cover=chk_test,
1785+ filter=chk_test,
1786+ other=chk_test))
1787+
1788+ # PJLink instance errors should be set to chk_value
1789+ self.assertEqual(pjlink.projector_errors, chk_value,
1790+ 'projector_errors should have been set to all {err}'.format(err=chk_code))
1791+
1792+ def test_projector_process_inpt(self):
1793+ """
1794+ Test input source status shows current input
1795+ """
1796+ # GIVEN: Test object
1797+ pjlink = pjlink_test
1798+ pjlink.source = '0'
1799+
1800+ # WHEN: Called with input source
1801+ pjlink.process_inpt('1')
1802+
1803+ # THEN: Input selected should reflect current input
1804+ self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"')
1805+
1806+ @patch.object(pjlink_test, 'projectorReceivedData')
1807+ def test_projector_process_lamp_single(self, mock_projectorReceivedData):
1808+ """
1809+ Test status lamp on/off and hours
1810+ """
1811+ # GIVEN: Test object
1812+ pjlink = pjlink_test
1813+
1814+ # WHEN: Call process_command with lamp data
1815+ pjlink.process_command('LAMP', '22222 1')
1816+
1817+ # THEN: Lamp should have been set with status=ON and hours=22222
1818+ self.assertEqual(pjlink.lamp[0]['On'], True,
1819+ 'Lamp power status should have been set to TRUE')
1820+ self.assertEqual(pjlink.lamp[0]['Hours'], 22222,
1821+ 'Lamp hours should have been set to 22222')
1822+
1823+ @patch.object(pjlink_test, 'projectorReceivedData')
1824+ def test_projector_process_lamp_multiple(self, mock_projectorReceivedData):
1825+ """
1826+ Test status multiple lamp on/off and hours
1827+ """
1828+ # GIVEN: Test object
1829+ pjlink = pjlink_test
1830+
1831+ # WHEN: Call process_command with lamp data
1832+ pjlink.process_command('LAMP', '11111 1 22222 0 33333 1')
1833+
1834+ # THEN: Lamp should have been set with proper lamp status
1835+ self.assertEqual(len(pjlink.lamp), 3,
1836+ 'Projector should have 3 lamps specified')
1837+ self.assertEqual(pjlink.lamp[0]['On'], True,
1838+ 'Lamp 1 power status should have been set to TRUE')
1839+ self.assertEqual(pjlink.lamp[0]['Hours'], 11111,
1840+ 'Lamp 1 hours should have been set to 11111')
1841+ self.assertEqual(pjlink.lamp[1]['On'], False,
1842+ 'Lamp 2 power status should have been set to FALSE')
1843+ self.assertEqual(pjlink.lamp[1]['Hours'], 22222,
1844+ 'Lamp 2 hours should have been set to 22222')
1845+ self.assertEqual(pjlink.lamp[2]['On'], True,
1846+ 'Lamp 3 power status should have been set to TRUE')
1847+ self.assertEqual(pjlink.lamp[2]['Hours'], 33333,
1848+ 'Lamp 3 hours should have been set to 33333')
1849+
1850+ @patch.object(pjlink_test, 'projectorReceivedData')
1851+ @patch.object(pjlink_test, 'projectorUpdateIcons')
1852+ @patch.object(pjlink_test, 'send_command')
1853+ @patch.object(pjlink_test, 'change_status')
1854+ def test_projector_process_powr_on(self,
1855+ mock_change_status,
1856+ mock_send_command,
1857+ mock_UpdateIcons,
1858+ mock_ReceivedData):
1859+ """
1860+ Test status power to ON
1861+ """
1862+ # GIVEN: Test object and preset
1863+ pjlink = pjlink_test
1864+ pjlink.power = S_STANDBY
1865+
1866+ # WHEN: Call process_command with turn power on command
1867+ pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_ON])
1868+
1869+ # THEN: Power should be set to ON
1870+ self.assertEqual(pjlink.power, S_ON, 'Power should have been set to ON')
1871+ mock_send_command.assert_called_once_with('INST')
1872+ self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
1873+
1874+ @patch.object(pjlink_test, 'projectorReceivedData')
1875+ @patch.object(pjlink_test, 'projectorUpdateIcons')
1876+ @patch.object(pjlink_test, 'send_command')
1877+ @patch.object(pjlink_test, 'change_status')
1878+ def test_projector_process_powr_off(self,
1879+ mock_change_status,
1880+ mock_send_command,
1881+ mock_UpdateIcons,
1882+ mock_ReceivedData):
1883+ """
1884+ Test status power to STANDBY
1885+ """
1886+ # GIVEN: Test object and preset
1887+ pjlink = pjlink_test
1888+ pjlink.power = S_ON
1889+
1890+ # WHEN: Call process_command with turn power on command
1891+ pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_STANDBY])
1892+
1893+ # THEN: Power should be set to STANDBY
1894+ self.assertEqual(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
1895+ self.assertEqual(mock_send_command.called, False, 'send_command should not have been called')
1896+ self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
1897+
1898+ def test_projector_process_rfil_save(self):
1899+ """
1900+ Test saving filter type
1901+ """
1902+ # GIVEN: Test object
1903+ pjlink = pjlink_test
1904+ pjlink.model_filter = None
1905+ filter_model = 'Filter Type Test'
1906+
1907+ # WHEN: Filter model is received
1908+ pjlink.process_rfil(data=filter_model)
1909+
1910+ # THEN: Filter model number should be saved
1911+ self.assertEqual(pjlink.model_filter, filter_model, 'Filter type should have been saved')
1912+
1913+ def test_projector_process_rfil_nosave(self):
1914+ """
1915+ Test saving filter type previously saved
1916+ """
1917+ # GIVEN: Test object
1918+ pjlink = pjlink_test
1919+ pjlink.model_filter = 'Old filter type'
1920+ filter_model = 'Filter Type Test'
1921+
1922+ # WHEN: Filter model is received
1923+ pjlink.process_rfil(data=filter_model)
1924+
1925+ # THEN: Filter model number should be saved
1926+ self.assertNotEquals(pjlink.model_filter, filter_model, 'Filter type should NOT have been saved')
1927+
1928+ def test_projector_process_rlmp_save(self):
1929+ """
1930+ Test saving lamp type
1931+ """
1932+ # GIVEN: Test object
1933+ pjlink = pjlink_test
1934+ pjlink.model_lamp = None
1935+ lamp_model = 'Lamp Type Test'
1936+
1937+ # WHEN: Filter model is received
1938+ pjlink.process_rlmp(data=lamp_model)
1939+
1940+ # THEN: Filter model number should be saved
1941+ self.assertEqual(pjlink.model_lamp, lamp_model, 'Lamp type should have been saved')
1942+
1943+ def test_projector_process_rlmp_nosave(self):
1944+ """
1945+ Test saving lamp type previously saved
1946+ """
1947+ # GIVEN: Test object
1948+ pjlink = pjlink_test
1949+ pjlink.model_lamp = 'Old lamp type'
1950+ lamp_model = 'Filter Type Test'
1951+
1952+ # WHEN: Filter model is received
1953+ pjlink.process_rlmp(data=lamp_model)
1954+
1955+ # THEN: Filter model number should be saved
1956+ self.assertNotEquals(pjlink.model_lamp, lamp_model, 'Lamp type should NOT have been saved')
1957+
1958+ def test_projector_process_snum_set(self):
1959+ """
1960+ Test saving serial number from projector
1961+ """
1962+ # GIVEN: Test object
1963+ pjlink = pjlink_test
1964+ pjlink.serial_no = None
1965+ test_number = 'Test Serial Number'
1966+
1967+ # WHEN: No serial number is set and we receive serial number command
1968+ pjlink.process_snum(data=test_number)
1969+
1970+ # THEN: Serial number should be set
1971+ self.assertEqual(pjlink.serial_no, test_number,
1972+ 'Projector serial number should have been set')
1973+
1974+ def test_projector_process_snum_different(self):
1975+ """
1976+ Test projector serial number different than saved serial number
1977+ """
1978+ # GIVEN: Test object
1979+ pjlink = pjlink_test
1980+ pjlink.serial_no = 'Previous serial number'
1981+ test_number = 'Test Serial Number'
1982+
1983+ # WHEN: No serial number is set and we receive serial number command
1984+ pjlink.process_snum(data=test_number)
1985+
1986+ # THEN: Serial number should be set
1987+ self.assertNotEquals(pjlink.serial_no, test_number,
1988+ 'Projector serial number should NOT have been set')