Merge lp:~alisonken1/openlp/pjlink2-m into lp:openlp

Proposed by Ken Roberts
Status: Merged
Merged at revision: 2795
Proposed branch: lp:~alisonken1/openlp/pjlink2-m
Merge into: lp:openlp
Diff against target: 1323 lines (+502/-243)
9 files modified
openlp/core/projectors/__init__.py (+0/-2)
openlp/core/projectors/constants.py (+18/-0)
openlp/core/projectors/db.py (+1/-1)
openlp/core/projectors/pjlink.py (+195/-117)
tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py (+8/-35)
tests/functional/openlp_core/projectors/test_projector_pjlink_base.py (+34/-48)
tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py (+35/-27)
tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py (+13/-13)
tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py (+198/-0)
To merge this branch: bzr merge lp:~alisonken1/openlp/pjlink2-m
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Phill Approve
Review via email: mp+335001@code.launchpad.net

This proposal supersedes a proposal from 2017-12-05.

Commit message

PJLink2 update M

Description of the change

- Added pjlink.process_pjlink
- Split pjlink.check_login() to use process_pjlink()
- Added QAbstractSocket connect enum to constants
- Minor code cleanups for connection and command processing
- Updated packet queueing
- Fix get_object_filtered()
- Fix tests in test_projector_pjlink_base
- Fix tests in test_projector_pjlink_cmd_routing
- Added tests for process_pjlink method
- Updated test_projector_bugfixes_01
- Some OLP style cleanups

--------------------------------------------------------------------------------
lp:~alisonken1/openlp/pjlink2-m (revision 2796)
https://ci.openlp.io/job/Branch-01-Pull/2339/ [SUCCESS]
https://ci.openlp.io/job/Branch-02-Functional-Tests/2240/ [SUCCESS]
https://ci.openlp.io/job/Branch-03-Interface-Tests/2110/ [SUCCESS]
https://ci.openlp.io/job/Branch-04a-Code_Analysis/1436/ [SUCCESS]
https://ci.openlp.io/job/Branch-04b-Test_Coverage/1255/ [SUCCESS]
https://ci.openlp.io/job/Branch-04c-Code_Analysis2/385/ [SUCCESS]
https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/214/ [FAILURE]

To post a comment you must log in.
Revision history for this message
Phill (phill-ridout) wrote : Posted in a previous version of this proposal

Just some style fixes please.

There's a couple inline comments.

Also you've got a right mismatch between single and double quoted strings. Can you change these so that strings use single quotes. I'm not sure there is a standard for quotes within the string, but double quotes are tidier as they do not need escaping. I'm sure I've seen this somewhere in the wiki, but I cannot find it on the coding standards page.

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

See inline

review: Needs Fixing
Revision history for this message
Phill (phill-ridout) wrote :

Great. Thanks for those changes :-)

review: Approve
Revision history for this message
Tim Bentley (trb143) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'openlp/core/projectors/__init__.py'
--- openlp/core/projectors/__init__.py 2017-11-16 23:53:53 +0000
+++ openlp/core/projectors/__init__.py 2017-12-09 11:19:42 +0000
@@ -25,8 +25,6 @@
25 Initialization for the openlp.core.projectors modules.25 Initialization for the openlp.core.projectors modules.
26"""26"""
2727
28from openlp.core.projectors.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
29
3028
31class DialogSourceStyle(object):29class DialogSourceStyle(object):
32 """30 """
3331
=== modified file 'openlp/core/projectors/constants.py'
--- openlp/core/projectors/constants.py 2017-11-10 11:59:38 +0000
+++ openlp/core/projectors/constants.py 2017-12-09 11:19:42 +0000
@@ -144,6 +144,24 @@
144 }144 }
145}145}
146146
147# QAbstractSocketState enums converted to string
148S_QSOCKET_STATE = {
149 0: 'QSocketState - UnconnectedState',
150 1: 'QSocketState - HostLookupState',
151 2: 'QSocketState - ConnectingState',
152 3: 'QSocketState - ConnectedState',
153 4: 'QSocketState - BoundState',
154 5: 'QSocketState - ListeningState (internal use only)',
155 6: 'QSocketState - ClosingState',
156 'UnconnectedState': 0,
157 'HostLookupState': 1,
158 'ConnectingState': 2,
159 'ConnectedState': 3,
160 'BoundState': 4,
161 'ListeningState': 5,
162 'ClosingState': 6
163}
164
147# Error and status codes165# Error and status codes
148S_OK = E_OK = 0 # E_OK included since I sometimes forget166S_OK = E_OK = 0 # E_OK included since I sometimes forget
149# Error codes. Start at 200 so we don't duplicate system error codes.167# Error codes. Start at 200 so we don't duplicate system error codes.
150168
=== modified file 'openlp/core/projectors/db.py'
--- openlp/core/projectors/db.py 2017-11-10 11:59:38 +0000
+++ openlp/core/projectors/db.py 2017-12-09 11:19:42 +0000
@@ -415,7 +415,7 @@
415 for key in projector.source_available:415 for key in projector.source_available:
416 item = self.get_object_filtered(ProjectorSource,416 item = self.get_object_filtered(ProjectorSource,
417 and_(ProjectorSource.code == key,417 and_(ProjectorSource.code == key,
418 ProjectorSource.projector_id == projector.dbid))418 ProjectorSource.projector_id == projector.id))
419 if item is None:419 if item is None:
420 source_dict[key] = PJLINK_DEFAULT_CODES[key]420 source_dict[key] = PJLINK_DEFAULT_CODES[key]
421 else:421 else:
422422
=== modified file 'openlp/core/projectors/pjlink.py'
--- openlp/core/projectors/pjlink.py 2017-11-24 19:08:23 +0000
+++ openlp/core/projectors/pjlink.py 2017-12-09 11:19:42 +0000
@@ -58,8 +58,7 @@
58 E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \58 E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \
59 E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \59 E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \
60 PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \60 PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
61 STATUS_STRING, S_CONNECTED, S_CONNECTING, S_INFO, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \61 STATUS_STRING, S_CONNECTED, S_CONNECTING, S_INFO, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_QSOCKET_STATE, S_STATUS
62 S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS
6362
64log = logging.getLogger(__name__)63log = logging.getLogger(__name__)
65log.debug('pjlink loaded')64log.debug('pjlink loaded')
@@ -111,7 +110,7 @@
111 """110 """
112 log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))111 log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
113 super().__init__()112 super().__init__()
114 # Map command to function113 # Map PJLink command to method
115 self.pjlink_functions = {114 self.pjlink_functions = {
116 'AVMT': self.process_avmt,115 'AVMT': self.process_avmt,
117 'CLSS': self.process_clss,116 'CLSS': self.process_clss,
@@ -123,7 +122,9 @@
123 'INST': self.process_inst,122 'INST': self.process_inst,
124 'LAMP': self.process_lamp,123 'LAMP': self.process_lamp,
125 'NAME': self.process_name,124 'NAME': self.process_name,
126 'PJLINK': self.check_login,125 'PJLINK': self.process_pjlink,
126 # TODO: Part of check_login refactor - remove when done
127 # 'PJLINK': self.check_login,
127 'POWR': self.process_powr,128 'POWR': self.process_powr,
128 'SNUM': self.process_snum,129 'SNUM': self.process_snum,
129 'SVER': self.process_sver,130 'SVER': self.process_sver,
@@ -135,7 +136,8 @@
135 """136 """
136 Initialize instance variables. Also used to reset projector-specific information to default.137 Initialize instance variables. Also used to reset projector-specific information to default.
137 """138 """
138 log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state()))139 log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip,
140 state=S_QSOCKET_STATE[self.state()]))
139 self.fan = None # ERST141 self.fan = None # ERST
140 self.filter_time = None # FILT142 self.filter_time = None # FILT
141 self.lamp = None # LAMP143 self.lamp = None # LAMP
@@ -165,6 +167,7 @@
165 self.socket_timer.stop()167 self.socket_timer.stop()
166 self.send_busy = False168 self.send_busy = False
167 self.send_queue = []169 self.send_queue = []
170 self.priority_queue = []
168171
169 def process_command(self, cmd, data):172 def process_command(self, cmd, data):
170 """173 """
@@ -176,18 +179,19 @@
176 log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip,179 log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip,
177 cmd=cmd,180 cmd=cmd,
178 data=data))181 data=data))
182 # cmd should already be in uppercase, but data may be in mixed-case.
183 # Due to some replies should stay as mixed-case, validate using separate uppercase check
184 _data = data.upper()
179 # Check if we have a future command not available yet185 # Check if we have a future command not available yet
180 _cmd = cmd.upper()186 if cmd not in PJLINK_VALID_CMD:
181 _data = data.upper()187 log.error('({ip}) Ignoring command="{cmd}" (Invalid/Unknown)'.format(ip=self.ip, cmd=cmd))
182 if _cmd not in PJLINK_VALID_CMD:
183 log.error("({ip}) Ignoring command='{cmd}' (Invalid/Unknown)".format(ip=self.ip, cmd=cmd))
184 return188 return
185 elif _data == 'OK':189 elif _data == 'OK':
186 log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd))190 log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd))
187 # A command returned successfully, no further processing needed191 # A command returned successfully, so do a query on command to verify status
188 return192 return self.send_command(cmd=cmd)
189 elif _cmd not in self.pjlink_functions:193 elif cmd not in self.pjlink_functions:
190 log.warning("({ip}) Unable to process command='{cmd}' (Future option)".format(ip=self.ip, cmd=cmd))194 log.warning('({ip}) Unable to process command="{cmd}" (Future option?)'.format(ip=self.ip, cmd=cmd))
191 return195 return
192 elif _data in PJLINK_ERRORS:196 elif _data in PJLINK_ERRORS:
193 # Oops - projector error197 # Oops - projector error
@@ -211,12 +215,10 @@
211 elif _data == PJLINK_ERRORS[E_PROJECTOR]:215 elif _data == PJLINK_ERRORS[E_PROJECTOR]:
212 # Projector/display error216 # Projector/display error
213 self.change_status(E_PROJECTOR)217 self.change_status(E_PROJECTOR)
214 self.receive_data_signal()
215 return218 return
216 # Command checks already passed219 # Command checks already passed
217 log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))220 log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
218 self.receive_data_signal()221 self.pjlink_functions[cmd](data)
219 self.pjlink_functions[_cmd](data)
220222
221 def process_avmt(self, data):223 def process_avmt(self, data):
222 """224 """
@@ -259,19 +261,19 @@
259 # : Received: '%1CLSS=Class 1' (Optoma)261 # : Received: '%1CLSS=Class 1' (Optoma)
260 # : Received: '%1CLSS=Version1' (BenQ)262 # : Received: '%1CLSS=Version1' (BenQ)
261 if len(data) > 1:263 if len(data) > 1:
262 log.warning("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data))264 log.warning('({ip}) Non-standard CLSS reply: "{data}"'.format(ip=self.ip, data=data))
263 # Due to stupid projectors not following standards (Optoma, BenQ comes to mind),265 # Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
264 # AND the different responses that can be received, the semi-permanent way to266 # AND the different responses that can be received, the semi-permanent way to
265 # fix the class reply is to just remove all non-digit characters.267 # fix the class reply is to just remove all non-digit characters.
266 try:268 try:
267 clss = re.findall('\d', data)[0] # Should only be the first match269 clss = re.findall('\d', data)[0] # Should only be the first match
268 except IndexError:270 except IndexError:
269 log.error("({ip}) No numbers found in class version reply '{data}' - "271 log.error('({ip}) No numbers found in class version reply "{data}" - '
270 "defaulting to class '1'".format(ip=self.ip, data=data))272 'defaulting to class "1"'.format(ip=self.ip, data=data))
271 clss = '1'273 clss = '1'
272 elif not data.isdigit():274 elif not data.isdigit():
273 log.error("({ip}) NAN clss version reply '{data}' - "275 log.error('({ip}) NAN CLSS version reply "{data}" - '
274 "defaulting to class '1'".format(ip=self.ip, data=data))276 'defaulting to class "1"'.format(ip=self.ip, data=data))
275 clss = '1'277 clss = '1'
276 else:278 else:
277 clss = data279 clss = data
@@ -289,7 +291,7 @@
289 """291 """
290 if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']:292 if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']:
291 count = PJLINK_ERST_DATA['DATA_LENGTH']293 count = PJLINK_ERST_DATA['DATA_LENGTH']
292 log.warning("{ip}) Invalid error status response '{data}': length != {count}".format(ip=self.ip,294 log.warning('{ip}) Invalid error status response "{data}": length != {count}'.format(ip=self.ip,
293 data=data,295 data=data,
294 count=count))296 count=count))
295 return297 return
@@ -297,7 +299,7 @@
297 datacheck = int(data)299 datacheck = int(data)
298 except ValueError:300 except ValueError:
299 # Bad data - ignore301 # Bad data - ignore
300 log.warning("({ip}) Invalid error status response '{data}'".format(ip=self.ip, data=data))302 log.warning('({ip}) Invalid error status response "{data}"'.format(ip=self.ip, data=data))
301 return303 return
302 if datacheck == 0:304 if datacheck == 0:
303 self.projector_errors = None305 self.projector_errors = None
@@ -430,6 +432,51 @@
430 log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name))432 log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name))
431 return433 return
432434
435 def process_pjlink(self, data):
436 """
437 Process initial socket connection to terminal.
438
439 :param data: Initial packet with authentication scheme
440 """
441 log.debug('({ip}) Processing PJLINK command'.format(ip=self.ip))
442 chk = data.split(' ')
443 if len(chk[0]) != 1:
444 # Invalid - after splitting, first field should be 1 character, either '0' or '1' only
445 log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=self.ip))
446 return self.disconnect_from_host()
447 elif chk[0] == '0':
448 # Normal connection no authentication
449 if len(chk) > 1:
450 # Invalid data - there should be nothing after a normal authentication scheme
451 log.error('({ip}) Normal connection with extra information - aborting'.format(ip=self.ip))
452 return self.disconnect_from_host()
453 elif self.pin:
454 log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=self.ip))
455 return self.disconnect_from_host()
456 else:
457 data_hash = None
458 elif chk[0] == '1':
459 if len(chk) < 2:
460 # Not enough information for authenticated connection
461 log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=self.ip))
462 return self.disconnect_from_host()
463 elif not self.pin:
464 log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=self.ip))
465 return self.disconnect_from_host()
466 else:
467 data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')),
468 encoding='ascii')
469 # Passed basic checks, so start connection
470 self.readyRead.connect(self.get_socket)
471 if not self.no_poll:
472 log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.ip))
473 self.timer.setInterval(2000) # Set 2 seconds for initial information
474 self.timer.start()
475 self.change_status(S_CONNECTED)
476 log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=self.ip))
477 # Since this is an initial connection, make it a priority just in case
478 return self.send_command(cmd="CLSS", salt=data_hash, priority=True)
479
433 def process_powr(self, data):480 def process_powr(self, data):
434 """481 """
435 Power status. See PJLink specification for format.482 Power status. See PJLink specification for format.
@@ -450,7 +497,7 @@
450 self.send_command('INST')497 self.send_command('INST')
451 else:498 else:
452 # Log unknown status response499 # Log unknown status response
453 log.warning('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data))500 log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.ip, data=data))
454 return501 return
455502
456 def process_rfil(self, data):503 def process_rfil(self, data):
@@ -460,9 +507,9 @@
460 if self.model_filter is None:507 if self.model_filter is None:
461 self.model_filter = data508 self.model_filter = data
462 else:509 else:
463 log.warning("({ip}) Filter model already set".format(ip=self.ip))510 log.warning('({ip}) Filter model already set'.format(ip=self.ip))
464 log.warning("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter))511 log.warning('({ip}) Saved model: "{old}"'.format(ip=self.ip, old=self.model_filter))
465 log.warning("({ip}) New model: '{new}'".format(ip=self.ip, new=data))512 log.warning('({ip}) New model: "{new}"'.format(ip=self.ip, new=data))
466513
467 def process_rlmp(self, data):514 def process_rlmp(self, data):
468 """515 """
@@ -471,9 +518,9 @@
471 if self.model_lamp is None:518 if self.model_lamp is None:
472 self.model_lamp = data519 self.model_lamp = data
473 else:520 else:
474 log.warning("({ip}) Lamp model already set".format(ip=self.ip))521 log.warning('({ip}) Lamp model already set'.format(ip=self.ip))
475 log.warning("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp))522 log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.ip, old=self.model_lamp))
476 log.warning("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data))523 log.warning('({ip}) New lamp: "{new}"'.format(ip=self.ip, new=data))
477524
478 def process_snum(self, data):525 def process_snum(self, data):
479 """526 """
@@ -482,16 +529,16 @@
482 :param data: Serial number from projector.529 :param data: Serial number from projector.
483 """530 """
484 if self.serial_no is None:531 if self.serial_no is None:
485 log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data))532 log.debug('({ip}) Setting projector serial number to "{data}"'.format(ip=self.ip, data=data))
486 self.serial_no = data533 self.serial_no = data
487 self.db_update = False534 self.db_update = False
488 else:535 else:
489 # Compare serial numbers and see if we got the same projector536 # Compare serial numbers and see if we got the same projector
490 if self.serial_no != data:537 if self.serial_no != data:
491 log.warning("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip))538 log.warning('({ip}) Projector serial number does not match saved serial number'.format(ip=self.ip))
492 log.warning("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no))539 log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.serial_no))
493 log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data))540 log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data))
494 log.warning("({ip}) NOT saving serial number".format(ip=self.ip))541 log.warning('({ip}) NOT saving serial number'.format(ip=self.ip))
495 self.serial_no_received = data542 self.serial_no_received = data
496543
497 def process_sver(self, data):544 def process_sver(self, data):
@@ -500,20 +547,20 @@
500 """547 """
501 if len(data) > 32:548 if len(data) > 32:
502 # Defined in specs max version is 32 characters549 # Defined in specs max version is 32 characters
503 log.warning("Invalid software version - too long")550 log.warning('Invalid software version - too long')
504 return551 return
505 elif self.sw_version is None:552 elif self.sw_version is None:
506 log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data))553 log.debug('({ip}) Setting projector software version to "{data}"'.format(ip=self.ip, data=data))
507 self.sw_version = data554 self.sw_version = data
508 self.db_update = True555 self.db_update = True
509 else:556 else:
510 # Compare software version and see if we got the same projector557 # Compare software version and see if we got the same projector
511 if self.serial_no != data:558 if self.serial_no != data:
512 log.warning("({ip}) Projector software version does not match saved "559 log.warning('({ip}) Projector software version does not match saved '
513 "software version".format(ip=self.ip))560 'software version'.format(ip=self.ip))
514 log.warning("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version))561 log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.sw_version))
515 log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data))562 log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data))
516 log.warning("({ip}) Saving new serial number as sw_version_received".format(ip=self.ip))563 log.warning('({ip}) Saving new serial number as sw_version_received'.format(ip=self.ip))
517 self.sw_version_received = data564 self.sw_version_received = data
518565
519566
@@ -540,9 +587,9 @@
540 :param poll_time: Time (in seconds) to poll connected projector587 :param poll_time: Time (in seconds) to poll connected projector
541 :param socket_timeout: Time (in seconds) to abort the connection if no response588 :param socket_timeout: Time (in seconds) to abort the connection if no response
542 """589 """
543 log.debug('PJlink(projector={projector}, args={args} kwargs={kwargs})'.format(projector=projector,590 log.debug('PJlink(projector="{projector}", args="{args}" kwargs="{kwargs}")'.format(projector=projector,
544 args=args,591 args=args,
545 kwargs=kwargs))592 kwargs=kwargs))
546 super().__init__()593 super().__init__()
547 self.entry = projector594 self.entry = projector
548 self.ip = self.entry.ip595 self.ip = self.entry.ip
@@ -573,6 +620,7 @@
573 self.widget = None # QListBox entry620 self.widget = None # QListBox entry
574 self.timer = None # Timer that calls the poll_loop621 self.timer = None # Timer that calls the poll_loop
575 self.send_queue = []622 self.send_queue = []
623 self.priority_queue = []
576 self.send_busy = False624 self.send_busy = False
577 # Socket timer for some possible brain-dead projectors or network cable pulled625 # Socket timer for some possible brain-dead projectors or network cable pulled
578 self.socket_timer = None626 self.socket_timer = None
@@ -586,6 +634,7 @@
586 self.connected.connect(self.check_login)634 self.connected.connect(self.check_login)
587 self.disconnected.connect(self.disconnect_from_host)635 self.disconnected.connect(self.disconnect_from_host)
588 self.error.connect(self.get_error)636 self.error.connect(self.get_error)
637 self.projectorReceivedData.connect(self._send_command)
589638
590 def thread_stopped(self):639 def thread_stopped(self):
591 """640 """
@@ -608,6 +657,10 @@
608 self.projectorReceivedData.disconnect(self._send_command)657 self.projectorReceivedData.disconnect(self._send_command)
609 except TypeError:658 except TypeError:
610 pass659 pass
660 try:
661 self.readyRead.disconnect(self.get_socket) # Set in process_pjlink
662 except TypeError:
663 pass
611 self.disconnect_from_host()664 self.disconnect_from_host()
612 self.deleteLater()665 self.deleteLater()
613 self.i_am_running = False666 self.i_am_running = False
@@ -625,10 +678,10 @@
625 Retrieve information from projector that changes.678 Retrieve information from projector that changes.
626 Normally called by timer().679 Normally called by timer().
627 """680 """
628 if self.state() != self.ConnectedState:681 if self.state() != S_QSOCKET_STATE['ConnectedState']:
629 log.warning("({ip}) poll_loop(): Not connected - returning".format(ip=self.ip))682 log.warning('({ip}) poll_loop(): Not connected - returning'.format(ip=self.ip))
630 return683 return
631 log.debug('({ip}) Updating projector status'.format(ip=self.ip))684 log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.ip))
632 # Reset timer in case we were called from a set command685 # Reset timer in case we were called from a set command
633 if self.timer.interval() < self.poll_time:686 if self.timer.interval() < self.poll_time:
634 # Reset timer to 5 seconds687 # Reset timer to 5 seconds
@@ -640,28 +693,28 @@
640 if self.pjlink_class == '2':693 if self.pjlink_class == '2':
641 check_list.extend(['FILT', 'FREZ'])694 check_list.extend(['FILT', 'FREZ'])
642 for command in check_list:695 for command in check_list:
643 self.send_command(command, queue=True)696 self.send_command(command)
644 # The following commands do not change, so only check them once697 # The following commands do not change, so only check them once
645 if self.power == S_ON and self.source_available is None:698 if self.power == S_ON and self.source_available is None:
646 self.send_command('INST', queue=True)699 self.send_command('INST')
647 if self.other_info is None:700 if self.other_info is None:
648 self.send_command('INFO', queue=True)701 self.send_command('INFO')
649 if self.manufacturer is None:702 if self.manufacturer is None:
650 self.send_command('INF1', queue=True)703 self.send_command('INF1')
651 if self.model is None:704 if self.model is None:
652 self.send_command('INF2', queue=True)705 self.send_command('INF2')
653 if self.pjlink_name is None:706 if self.pjlink_name is None:
654 self.send_command('NAME', queue=True)707 self.send_command('NAME')
655 if self.pjlink_class == '2':708 if self.pjlink_class == '2':
656 # Class 2 specific checks709 # Class 2 specific checks
657 if self.serial_no is None:710 if self.serial_no is None:
658 self.send_command('SNUM', queue=True)711 self.send_command('SNUM')
659 if self.sw_version is None:712 if self.sw_version is None:
660 self.send_command('SVER', queue=True)713 self.send_command('SVER')
661 if self.model_filter is None:714 if self.model_filter is None:
662 self.send_command('RFIL', queue=True)715 self.send_command('RFIL')
663 if self.model_lamp is None:716 if self.model_lamp is None:
664 self.send_command('RLMP', queue=True)717 self.send_command('RLMP')
665718
666 def _get_status(self, status):719 def _get_status(self, status):
667 """720 """
@@ -713,14 +766,12 @@
713 code=status_code,766 code=status_code,
714 message=status_message if msg is None else msg))767 message=status_message if msg is None else msg))
715 self.changeStatus.emit(self.ip, status, message)768 self.changeStatus.emit(self.ip, status, message)
769 self.projectorUpdateIcons.emit()
716770
717 @QtCore.pyqtSlot()771 @QtCore.pyqtSlot()
718 def check_login(self, data=None):772 def check_login(self, data=None):
719 """773 """
720 Processes the initial connection and authentication (if needed).774 Processes the initial connection and convert to a PJLink packet if valid initial connection
721 Starts poll timer if connection is established.
722
723 NOTE: Qt md5 hash function doesn't work with projector authentication. Use the python md5 hash function.
724775
725 :param data: Optional data if called from another routine776 :param data: Optional data if called from another routine
726 """777 """
@@ -733,12 +784,12 @@
733 self.change_status(E_SOCKET_TIMEOUT)784 self.change_status(E_SOCKET_TIMEOUT)
734 return785 return
735 read = self.readLine(self.max_size)786 read = self.readLine(self.max_size)
736 self.readLine(self.max_size) # Clean out the trailing \r\n787 self.readLine(self.max_size) # Clean out any trailing whitespace
737 if read is None:788 if read is None:
738 log.warning('({ip}) read is None - socket error?'.format(ip=self.ip))789 log.warning('({ip}) read is None - socket error?'.format(ip=self.ip))
739 return790 return
740 elif len(read) < 8:791 elif len(read) < 8:
741 log.warning('({ip}) Not enough data read)'.format(ip=self.ip))792 log.warning('({ip}) Not enough data read - skipping'.format(ip=self.ip))
742 return793 return
743 data = decode(read, 'utf-8')794 data = decode(read, 'utf-8')
744 # Possibility of extraneous data on input when reading.795 # Possibility of extraneous data on input when reading.
@@ -750,9 +801,16 @@
750 # PJLink initial login will be:801 # PJLink initial login will be:
751 # 'PJLink 0' - Unauthenticated login - no extra steps required.802 # 'PJLink 0' - Unauthenticated login - no extra steps required.
752 # 'PJLink 1 XXXXXX' Authenticated login - extra processing required.803 # 'PJLink 1 XXXXXX' Authenticated login - extra processing required.
753 if not data.upper().startswith('PJLINK'):804 if not data.startswith('PJLINK'):
754 # Invalid response805 # Invalid initial packet - close socket
806 log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.ip))
755 return self.disconnect_from_host()807 return self.disconnect_from_host()
808 log.debug('({ip}) check_login(): Formatting initial connection prompt to PJLink packet'.format(ip=self.ip))
809 return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX,
810 clss='1',
811 data=data.replace(' ', '=', 1)).encode('utf-8'))
812 # TODO: The below is replaced by process_pjlink() - remove when working properly
813 """
756 if '=' in data:814 if '=' in data:
757 # Processing a login reply815 # Processing a login reply
758 data_check = data.strip().split('=')816 data_check = data.strip().split('=')
@@ -801,18 +859,19 @@
801 log.debug('({ip}) Starting timer'.format(ip=self.ip))859 log.debug('({ip}) Starting timer'.format(ip=self.ip))
802 self.timer.setInterval(2000) # Set 2 seconds for initial information860 self.timer.setInterval(2000) # Set 2 seconds for initial information
803 self.timer.start()861 self.timer.start()
862 """
804863
805 def _trash_buffer(self, msg=None):864 def _trash_buffer(self, msg=None):
806 """865 """
807 Clean out extraneous stuff in the buffer.866 Clean out extraneous stuff in the buffer.
808 """867 """
809 log.warning("({ip}) {message}".format(ip=self.ip, message='Invalid packet' if msg is None else msg))868 log.warning('({ip}) {message}'.format(ip=self.ip, message='Invalid packet' if msg is None else msg))
810 self.send_busy = False869 self.send_busy = False
811 trash_count = 0870 trash_count = 0
812 while self.bytesAvailable() > 0:871 while self.bytesAvailable() > 0:
813 trash = self.read(self.max_size)872 trash = self.read(self.max_size)
814 trash_count += len(trash)873 trash_count += len(trash)
815 log.debug("({ip}) Finished cleaning buffer - {count} bytes dropped".format(ip=self.ip,874 log.debug('({ip}) Finished cleaning buffer - {count} bytes dropped'.format(ip=self.ip,
816 count=trash_count))875 count=trash_count))
817 return876 return
818877
@@ -824,7 +883,7 @@
824 :param data: Data to process. buffer must be formatted as a proper PJLink packet.883 :param data: Data to process. buffer must be formatted as a proper PJLink packet.
825 :param ip: Destination IP for buffer.884 :param ip: Destination IP for buffer.
826 """885 """
827 log.debug("({ip}) get_buffer(data='{buff}' ip='{ip_in}'".format(ip=self.ip, buff=data, ip_in=ip))886 log.debug('({ip}) get_buffer(data="{buff}" ip="{ip_in}"'.format(ip=self.ip, buff=data, ip_in=ip))
828 if ip is None:887 if ip is None:
829 log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.ip))888 log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.ip))
830 return889 return
@@ -842,38 +901,52 @@
842 return901 return
843 # Although we have a packet length limit, go ahead and use a larger buffer902 # Although we have a packet length limit, go ahead and use a larger buffer
844 read = self.readLine(1024)903 read = self.readLine(1024)
845 log.debug("({ip}) get_socket(): '{buff}'".format(ip=self.ip, buff=read))904 log.debug('({ip}) get_socket(): "{buff}"'.format(ip=self.ip, buff=read))
846 if read == -1:905 if read == -1:
847 # No data available906 # No data available
848 log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip))907 log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip))
849 return self.receive_data_signal()908 return self.receive_data_signal()
850 self.socket_timer.stop()909 self.socket_timer.stop()
851 return self.get_data(buff=read, ip=self.ip)910 self.get_data(buff=read, ip=self.ip)
911 return self.receive_data_signal()
852912
853 def get_data(self, buff, ip):913 def get_data(self, buff, ip=None):
854 """914 """
855 Process received data915 Process received data
856916
857 :param buff: Data to process.917 :param buff: Data to process.
858 :param ip: (optional) Destination IP.918 :param ip: (optional) Destination IP.
859 """919 """
860 log.debug("({ip}) get_data(ip='{ip_in}' buffer='{buff}'".format(ip=self.ip, ip_in=ip, buff=buff))920 # Since "self" is not available to options and the "ip" keyword is a "maybe I'll use in the future",
921 # set to default here
922 if ip is None:
923 ip = self.ip
924 log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.ip, ip_in=ip, buff=buff))
861 # NOTE: Class2 has changed to some values being UTF-8925 # NOTE: Class2 has changed to some values being UTF-8
862 data_in = decode(buff, 'utf-8')926 data_in = decode(buff, 'utf-8')
863 data = data_in.strip()927 data = data_in.strip()
864 if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)):928 # Initial packet checks
865 return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix')929 if (len(data) < 7):
930 return self._trash_buffer(msg='get_data(): Invalid packet - length')
866 elif len(data) > self.max_size:931 elif len(data) > self.max_size:
867 return self._trash_buffer(msg='get_data(): Invalid packet - too long')932 return self._trash_buffer(msg='get_data(): Invalid packet - too long')
933 elif not data.startswith(PJLINK_PREFIX):
934 return self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing')
868 elif '=' not in data:935 elif '=' not in data:
869 return self._trash_buffer(msg='get_data(): Invalid packet does not have equal')936 return self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="')
870 log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data))937 log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data))
871 header, data = data.split('=')938 header, data = data.split('=')
939 # At this point, the header should contain:
940 # "PVCCCC"
941 # Where:
942 # P = PJLINK_PREFIX
943 # V = PJLink class or version
944 # C = PJLink command
872 try:945 try:
873 version, cmd = header[1], header[2:]946 version, cmd = header[1], header[2:].upper()
874 except ValueError as e:947 except ValueError as e:
875 self.change_status(E_INVALID_DATA)948 self.change_status(E_INVALID_DATA)
876 log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip()))949 log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in))
877 return self._trash_buffer('get_data(): Expected header + command + data')950 return self._trash_buffer('get_data(): Expected header + command + data')
878 if cmd not in PJLINK_VALID_CMD:951 if cmd not in PJLINK_VALID_CMD:
879 log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd))952 log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd))
@@ -881,6 +954,7 @@
881 if int(self.pjlink_class) < int(version):954 if int(self.pjlink_class) < int(version):
882 log.warning('({ip}) get_data(): Projector returned class reply higher '955 log.warning('({ip}) get_data(): Projector returned class reply higher '
883 'than projector stated class'.format(ip=self.ip))956 'than projector stated class'.format(ip=self.ip))
957 self.send_busy = False
884 return self.process_command(cmd, data)958 return self.process_command(cmd, data)
885959
886 @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)960 @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)
@@ -910,19 +984,18 @@
910 self.reset_information()984 self.reset_information()
911 return985 return
912986
913 def send_command(self, cmd, opts='?', salt=None, queue=False):987 def send_command(self, cmd, opts='?', salt=None, priority=False):
914 """988 """
915 Add command to output queue if not already in queue.989 Add command to output queue if not already in queue.
916990
917 :param cmd: Command to send991 :param cmd: Command to send
918 :param opts: Command option (if any) - defaults to '?' (get information)992 :param opts: Command option (if any) - defaults to '?' (get information)
919 :param salt: Optional salt for md5 hash initial authentication993 :param salt: Optional salt for md5 hash initial authentication
920 :param queue: Option to force add to queue rather than sending directly994 :param priority: Option to send packet now rather than queue it up
921 """995 """
922 if self.state() != self.ConnectedState:996 if self.state() != self.ConnectedState:
923 log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip))997 log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip))
924 self.send_queue = []998 return self.reset_information()
925 return
926 if cmd not in PJLINK_VALID_CMD:999 if cmd not in PJLINK_VALID_CMD:
927 log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip))1000 log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip))
928 return1001 return
@@ -939,28 +1012,26 @@
939 header = PJLINK_HEADER.format(linkclass=cmd_ver[0])1012 header = PJLINK_HEADER.format(linkclass=cmd_ver[0])
940 else:1013 else:
941 # NOTE: Once we get to version 3 then think about looping1014 # NOTE: Once we get to version 3 then think about looping
942 log.error('({ip}): send_command(): PJLink class check issue? aborting'.format(ip=self.ip))1015 log.error('({ip}): send_command(): PJLink class check issue? Aborting'.format(ip=self.ip))
943 return1016 return
944 out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt,1017 out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt,
945 header=header,1018 header=header,
946 command=cmd,1019 command=cmd,
947 options=opts,1020 options=opts,
948 suffix=CR)1021 suffix=CR)
949 if out in self.send_queue:1022 if out in self.priority_queue:
950 # Already there, so don't add1023 log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.ip))
951 log.debug('({ip}) send_command(out="{data}") Already in queue - skipping'.format(ip=self.ip,1024 elif out in self.send_queue:
952 data=out.strip()))1025 log.debug('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.ip))
953 elif not queue and len(self.send_queue) == 0:
954 # Nothing waiting to send, so just send it
955 log.debug('({ip}) send_command(out="{data}") Sending data'.format(ip=self.ip, data=out.strip()))
956 return self._send_command(data=out)
957 else:1026 else:
958 log.debug('({ip}) send_command(out="{data}") adding to queue'.format(ip=self.ip, data=out.strip()))1027 if priority:
959 self.send_queue.append(out)1028 log.debug('({ip}) send_command(): Adding to priority queue'.format(ip=self.ip))
960 self.projectorReceivedData.emit()1029 self.priority_queue.append(out)
961 log.debug('({ip}) send_command(): send_busy is {data}'.format(ip=self.ip, data=self.send_busy))1030 else:
962 if not self.send_busy:1031 log.debug('({ip}) send_command(): Adding to normal queue'.format(ip=self.ip))
963 log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip))1032 self.send_queue.append(out)
1033 if self.priority_queue or self.send_queue:
1034 # May be some initial connection setup so make sure we send data
964 self._send_command()1035 self._send_command()
9651036
966 @QtCore.pyqtSlot()1037 @QtCore.pyqtSlot()
@@ -971,43 +1042,53 @@
971 :param data: Immediate data to send1042 :param data: Immediate data to send
972 :param utf8: Send as UTF-8 string otherwise send as ASCII string1043 :param utf8: Send as UTF-8 string otherwise send as ASCII string
973 """1044 """
974 log.debug('({ip}) _send_string()'.format(ip=self.ip))1045 # Funny looking data check, but it's a quick check for data=None
975 log.debug('({ip}) _send_string(): Connection status: {data}'.format(ip=self.ip, data=self.state()))1046 log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.ip, data=data.strip() if data else data))
1047 log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.ip,
1048 data=S_QSOCKET_STATE[self.state()]))
976 if self.state() != self.ConnectedState:1049 if self.state() != self.ConnectedState:
977 log.debug('({ip}) _send_string() Not connected - abort'.format(ip=self.ip))1050 log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.ip))
978 self.send_queue = []
979 self.send_busy = False1051 self.send_busy = False
980 return1052 return self.disconnect_from_host()
1053 if data and data not in self.priority_queue:
1054 log.debug('({ip}) _send_command(): Priority packet - adding to priority queue'.format(ip=self.ip))
1055 self.priority_queue.append(data)
1056
981 if self.send_busy:1057 if self.send_busy:
982 # Still waiting for response from last command sent1058 # Still waiting for response from last command sent
1059 log.debug('({ip}) _send_command(): Still busy, returning'.format(ip=self.ip))
1060 log.debug('({ip}) _send_command(): Priority queue = {data}'.format(ip=self.ip, data=self.priority_queue))
1061 log.debug('({ip}) _send_command(): Normal queue = {data}'.format(ip=self.ip, data=self.send_queue))
983 return1062 return
984 if data is not None:1063
985 out = data1064 if len(self.priority_queue) != 0:
986 log.debug('({ip}) _send_string(data="{data}")'.format(ip=self.ip, data=out.strip()))1065 out = self.priority_queue.pop(0)
1066 log.debug('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.ip))
987 elif len(self.send_queue) != 0:1067 elif len(self.send_queue) != 0:
988 out = self.send_queue.pop(0)1068 out = self.send_queue.pop(0)
989 log.debug('({ip}) _send_string(queued data="{data}"%s)'.format(ip=self.ip, data=out.strip()))1069 log.debug('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.ip))
990 else:1070 else:
991 # No data to send1071 # No data to send
992 log.debug('({ip}) _send_string(): No data to send'.format(ip=self.ip))1072 log.debug('({ip}) _send_command(): No data to send'.format(ip=self.ip))
993 self.send_busy = False1073 self.send_busy = False
994 return1074 return
995 self.send_busy = True1075 self.send_busy = True
996 log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip()))1076 log.debug('({ip}) _send_command(): Sending "{data}"'.format(ip=self.ip, data=out.strip()))
997 log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue))
998 self.socket_timer.start()1077 self.socket_timer.start()
999 sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii')))1078 sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii')))
1000 self.waitForBytesWritten(2000) # 2 seconds should be enough1079 self.waitForBytesWritten(2000) # 2 seconds should be enough
1001 if sent == -1:1080 if sent == -1:
1002 # Network error?1081 # Network error?
1003 log.warning("({ip}) _send_command(): -1 received".format(ip=self.ip))1082 log.warning('({ip}) _send_command(): -1 received - disconnecting from host'.format(ip=self.ip))
1004 self.change_status(E_NETWORK,1083 self.change_status(E_NETWORK,
1005 translate('OpenLP.PJLink', 'Error while sending data to projector'))1084 translate('OpenLP.PJLink', 'Error while sending data to projector'))
1085 self.disconnect_from_host()
10061086
1007 def connect_to_host(self):1087 def connect_to_host(self):
1008 """1088 """
1009 Initiate connection to projector.1089 Initiate connection to projector.
1010 """1090 """
1091 log.debug('{ip}) connect_to_host(): Starting connection'.format(ip=self.ip))
1011 if self.state() == self.ConnectedState:1092 if self.state() == self.ConnectedState:
1012 log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip))1093 log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip))
1013 return1094 return
@@ -1023,22 +1104,19 @@
1023 if abort:1104 if abort:
1024 log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip))1105 log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip))
1025 else:1106 else:
1026 log.warning('({ip}) disconnect_from_host(): Not connected - returning'.format(ip=self.ip))1107 log.warning('({ip}) disconnect_from_host(): Not connected'.format(ip=self.ip))
1027 self.reset_information()
1028 self.disconnectFromHost()1108 self.disconnectFromHost()
1029 try:1109 try:
1030 self.readyRead.disconnect(self.get_socket)1110 self.readyRead.disconnect(self.get_socket)
1031 except TypeError:1111 except TypeError:
1032 pass1112 pass
1113 log.debug('({ip}) disconnect_from_host() '
1114 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0]))
1033 if abort:1115 if abort:
1034 self.change_status(E_NOT_CONNECTED)1116 self.change_status(E_NOT_CONNECTED)
1035 else:1117 else:
1036 log.debug('({ip}) disconnect_from_host() '1118 self.change_status(S_NOT_CONNECTED)
1037 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0]))
1038 if self.status_connect != E_NOT_CONNECTED:
1039 self.change_status(S_NOT_CONNECTED)
1040 self.reset_information()1119 self.reset_information()
1041 self.projectorUpdateIcons.emit()
10421120
1043 def get_av_mute_status(self):1121 def get_av_mute_status(self):
1044 """1122 """
10451123
=== modified file 'tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py'
--- tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py 2017-11-24 19:08:23 +0000
+++ tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py 2017-12-09 11:19:42 +0000
@@ -23,12 +23,11 @@
23Package to test the openlp.core.projectors.pjlink base package.23Package to test the openlp.core.projectors.pjlink base package.
24"""24"""
25from unittest import TestCase25from unittest import TestCase
26from unittest.mock import patch
2726
28from openlp.core.projectors.db import Projector27from openlp.core.projectors.db import Projector
29from openlp.core.projectors.pjlink import PJLink28from openlp.core.projectors.pjlink import PJLink
3029
31from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA30from tests.resources.projector.data import TEST1_DATA
3231
3332
34class TestPJLinkBugs(TestCase):33class TestPJLinkBugs(TestCase):
@@ -80,43 +79,17 @@
80 """79 """
81 Test bug 1593882 no pin and authenticated request exception80 Test bug 1593882 no pin and authenticated request exception
82 """81 """
83 # GIVEN: Test object and mocks82 # Test now part of test_projector_pjlink_commands_02
84 mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start()83 # Keeping here for bug reference
85 mock_timer = patch.object(self.pjlink_test, 'timer').start()84 pass
86 mock_authentication = patch.object(self.pjlink_test, 'projectorAuthentication').start()
87 mock_ready_read = patch.object(self.pjlink_test, 'waitForReadyRead').start()
88 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
89 pjlink = self.pjlink_test
90 pjlink.pin = None
91 mock_ready_read.return_value = True
92
93 # WHEN: call with authentication request and pin not set
94 pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
95
96 # THEN: 'No Authentication' signal should have been sent
97 mock_authentication.emit.assert_called_with(pjlink.ip)
9885
99 def test_bug_1593883_pjlink_authentication(self):86 def test_bug_1593883_pjlink_authentication(self):
100 """87 """
101 Test bugfix 1593883 pjlink authentication88 Test bugfix 1593883 pjlink authentication and ticket 92187
102 """89 """
103 # GIVEN: Test object and data90 # Test now part of test_projector_pjlink_commands_02
104 mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start()91 # Keeping here for bug reference
105 mock_timer = patch.object(self.pjlink_test, 'timer').start()92 pass
106 mock_send_command = patch.object(self.pjlink_test, 'write').start()
107 mock_state = patch.object(self.pjlink_test, 'state').start()
108 mock_waitForReadyRead = patch.object(self.pjlink_test, 'waitForReadyRead').start()
109 pjlink = self.pjlink_test
110 pjlink.pin = TEST_PIN
111 mock_state.return_value = pjlink.ConnectedState
112 mock_waitForReadyRead.return_value = True
113
114 # WHEN: Athenticated connection is called
115 pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
116
117 # THEN: send_command should have the proper authentication
118 self.assertEqual("{test}".format(test=mock_send_command.call_args),
119 "call(b'{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
12093
121 def test_bug_1734275_process_lamp_nonstandard_reply(self):94 def test_bug_1734275_process_lamp_nonstandard_reply(self):
122 """95 """
12396
=== modified file 'tests/functional/openlp_core/projectors/test_projector_pjlink_base.py'
--- tests/functional/openlp_core/projectors/test_projector_pjlink_base.py 2017-11-24 08:30:37 +0000
+++ tests/functional/openlp_core/projectors/test_projector_pjlink_base.py 2017-12-09 11:19:42 +0000
@@ -25,11 +25,11 @@
25from unittest import TestCase25from unittest import TestCase
26from unittest.mock import call, patch, MagicMock26from unittest.mock import call, patch, MagicMock
2727
28from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED28from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED, S_QSOCKET_STATE
29from openlp.core.projectors.db import Projector29from openlp.core.projectors.db import Projector
30from openlp.core.projectors.pjlink import PJLink30from openlp.core.projectors.pjlink import PJLink
3131
32from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST1_DATA32from tests.resources.projector.data import TEST1_DATA
3333
34pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)34pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
3535
@@ -38,29 +38,17 @@
38 """38 """
39 Tests for the PJLink module39 Tests for the PJLink module
40 """40 """
41 @patch.object(pjlink_test, 'readyRead')41 def setUp(self):
42 @patch.object(pjlink_test, 'send_command')42 '''
43 @patch.object(pjlink_test, 'waitForReadyRead')43 TestPJLinkCommands part 2 initialization
44 @patch('openlp.core.common.qmd5_hash')44 '''
45 def test_authenticated_connection_call(self,45 self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
46 mock_qmd5_hash,46
47 mock_waitForReadyRead,47 def tearDown(self):
48 mock_send_command,48 '''
49 mock_readyRead):49 TestPJLinkCommands part 2 cleanups
50 """50 '''
51 Ticket 92187: Fix for projector connect with PJLink authentication exception.51 self.pjlink_test = None
52 """
53 # GIVEN: Test object
54 pjlink = pjlink_test
55
56 # WHEN: Calling check_login with authentication request:
57 pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
58
59 # THEN: Should have called qmd5_hash
60 self.assertTrue(mock_qmd5_hash.called_with(TEST_SALT,
61 "Connection request should have been called with TEST_SALT"))
62 self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
63 "Connection request should have been called with TEST_PIN"))
6452
65 @patch.object(pjlink_test, 'change_status')53 @patch.object(pjlink_test, 'change_status')
66 def test_status_change(self, mock_change_status):54 def test_status_change(self, mock_change_status):
@@ -110,18 +98,18 @@
110 # THEN: poll_loop should exit without calling any other method98 # THEN: poll_loop should exit without calling any other method
111 self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method')99 self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method')
112100
113 @patch.object(pjlink_test, 'send_command')101 def test_poll_loop_start(self):
114 def test_poll_loop_start(self, mock_send_command):
115 """102 """
116 Test PJLink.poll_loop makes correct calls103 Test PJLink.poll_loop makes correct calls
117 """104 """
118 # GIVEN: test object and test data105 # GIVEN: Mocks and test data
119 pjlink = pjlink_test106 mock_state = patch.object(self.pjlink_test, 'state').start()
120 pjlink.state = MagicMock()107 mock_state.return_value = S_QSOCKET_STATE['ConnectedState']
121 pjlink.timer = MagicMock()108 mock_timer = patch.object(self.pjlink_test, 'timer').start()
122 pjlink.timer.interval = MagicMock()109 mock_timer.interval.return_value = 10
123 pjlink.timer.setInterval = MagicMock()110 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
124 pjlink.timer.start = MagicMock()111
112 pjlink = self.pjlink_test
125 pjlink.poll_time = 20113 pjlink.poll_time = 20
126 pjlink.power = S_ON114 pjlink.power = S_ON
127 pjlink.source_available = None115 pjlink.source_available = None
@@ -130,19 +118,17 @@
130 pjlink.model = None118 pjlink.model = None
131 pjlink.pjlink_name = None119 pjlink.pjlink_name = None
132 pjlink.ConnectedState = S_CONNECTED120 pjlink.ConnectedState = S_CONNECTED
133 pjlink.timer.interval.return_value = 10
134 pjlink.state.return_value = S_CONNECTED
135 call_list = [121 call_list = [
136 call('POWR', queue=True),122 call('POWR'),
137 call('ERST', queue=True),123 call('ERST'),
138 call('LAMP', queue=True),124 call('LAMP'),
139 call('AVMT', queue=True),125 call('AVMT'),
140 call('INPT', queue=True),126 call('INPT'),
141 call('INST', queue=True),127 call('INST'),
142 call('INFO', queue=True),128 call('INFO'),
143 call('INF1', queue=True),129 call('INF1'),
144 call('INF2', queue=True),130 call('INF2'),
145 call('NAME', queue=True),131 call('NAME'),
146 ]132 ]
147133
148 # WHEN: PJLink.poll_loop is called134 # WHEN: PJLink.poll_loop is called
@@ -150,8 +136,8 @@
150136
151 # THEN: proper calls were made to retrieve projector data137 # THEN: proper calls were made to retrieve projector data
152 # First, call to update the timer with the next interval138 # First, call to update the timer with the next interval
153 self.assertTrue(pjlink.timer.setInterval.called, 'Should have updated the timer')139 self.assertTrue(mock_timer.setInterval.called)
154 # Next, should have called the timer to start140 # Next, should have called the timer to start
155 self.assertTrue(pjlink.timer.start.called, 'Should have started the timer')141 self.assertTrue(mock_timer.start.called, 'Should have started the timer')
156 # Finally, should have called send_command with a list of projetctor status checks142 # Finally, should have called send_command with a list of projetctor status checks
157 mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries')143 mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries')
158144
=== modified file 'tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py'
--- tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2017-11-16 23:53:53 +0000
+++ tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2017-12-09 11:19:42 +0000
@@ -46,6 +46,18 @@
46 """46 """
47 Tests for the PJLink module command routing47 Tests for the PJLink module command routing
48 """48 """
49 def setUp(self):
50 '''
51 TestPJLinkCommands part 2 initialization
52 '''
53 self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
54
55 def tearDown(self):
56 '''
57 TestPJLinkCommands part 2 cleanups
58 '''
59 self.pjlink_test = None
60
49 @patch.object(openlp.core.projectors.pjlink, 'log')61 @patch.object(openlp.core.projectors.pjlink, 'log')
50 def test_process_command_call_clss(self, mock_log):62 def test_process_command_call_clss(self, mock_log):
51 """63 """
@@ -163,21 +175,20 @@
163 mock_change_status.assert_called_once_with(E_AUTHENTICATION)175 mock_change_status.assert_called_once_with(E_AUTHENTICATION)
164 mock_log.error.assert_called_with(log_text)176 mock_log.error.assert_called_with(log_text)
165177
166 @patch.object(openlp.core.projectors.pjlink, 'log')178 def test_process_command_future(self):
167 def test_process_command_future(self, mock_log):
168 """179 """
169 Test command valid but no method to process yet180 Test command valid but no method to process yet
170 """181 """
171 # GIVEN: Test object182 # GIVEN: Initial mocks and data
172 pjlink = pjlink_test183 mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
173 log_text = "(127.0.0.1) Unable to process command='CLSS' (Future option)"184 mock_functions = patch.object(self.pjlink_test, 'pjlink_functions').start()
174 mock_log.reset_mock()185 mock_functions.return_value = []
175 # Remove a valid command so we can test valid command but not available yet186
176 pjlink.pjlink_functions.pop('CLSS')187 pjlink = self.pjlink_test
188 log_text = '(111.111.111.111) Unable to process command="CLSS" (Future option?)'
177189
178 # WHEN: process_command called with an unknown command190 # WHEN: process_command called with an unknown command
179 with patch.object(pjlink, 'pjlink_functions') as mock_functions:191 pjlink.process_command(cmd='CLSS', data='DONT CARE')
180 pjlink.process_command(cmd='CLSS', data='DONT CARE')
181192
182 # THEN: Error should be logged and no command called193 # THEN: Error should be logged and no command called
183 self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')194 self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
@@ -196,29 +207,26 @@
196207
197 # WHEN: process_command called with an unknown command208 # WHEN: process_command called with an unknown command
198 pjlink.process_command(cmd='Unknown', data='Dont Care')209 pjlink.process_command(cmd='Unknown', data='Dont Care')
199 log_text = "(127.0.0.1) Ignoring command='Unknown' (Invalid/Unknown)"210 log_text = '(127.0.0.1) Ignoring command="Unknown" (Invalid/Unknown)'
200211
201 # THEN: Error should be logged and no command called212 # THEN: Error should be logged and no command called
202 self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')213 self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
203 mock_log.error.assert_called_once_with(log_text)214 mock_log.error.assert_called_once_with(log_text)
204215
205 @patch.object(pjlink_test, 'pjlink_functions')216 def test_process_command_ok(self):
206 @patch.object(openlp.core.projectors.pjlink, 'log')
207 def test_process_command_ok(self, mock_log, mock_functions):
208 """217 """
209 Test command returned success218 Test command returned success
210 """219 """
211 # GIVEN: Test object220 # GIVEN: Initial mocks and data
212 pjlink = pjlink_test221 mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
213 mock_functions.reset_mock()222 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
214 mock_log.reset_mock()223
215224 pjlink = self.pjlink_test
216 # WHEN: process_command called with an unknown command225 log_text = '(111.111.111.111) Command "POWR" returned OK'
217 pjlink.process_command(cmd='CLSS', data='OK')226
218 log_text = '(127.0.0.1) Command "CLSS" returned OK'227 # WHEN: process_command called with a command that returns OK
219228 pjlink.process_command(cmd='POWR', data='OK')
220 # THEN: Error should be logged and no command called229
221 self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')230 # THEN: Appropriate calls should have been made
222 self.assertEqual(mock_log.debug.call_count, 2, 'log.debug() should have been called twice')
223 # Although we called it twice, only the last log entry is saved
224 mock_log.debug.assert_called_with(log_text)231 mock_log.debug.assert_called_with(log_text)
232 mock_send_command.assert_called_once_with(cmd='POWR')
225233
=== renamed file 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py' => 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py'
--- tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py 2017-11-24 08:30:37 +0000
+++ tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py 2017-12-09 11:19:42 +0000
@@ -47,7 +47,7 @@
4747
48class TestPJLinkCommands(TestCase):48class TestPJLinkCommands(TestCase):
49 """49 """
50 Tests for the PJLink module50 Tests for the PJLinkCommands class part 1
51 """51 """
52 @patch.object(pjlink_test, 'changeStatus')52 @patch.object(pjlink_test, 'changeStatus')
53 @patch.object(openlp.core.projectors.pjlink, 'log')53 @patch.object(openlp.core.projectors.pjlink, 'log')
@@ -580,7 +580,7 @@
580580
581 # WHEN: Process invalid reply581 # WHEN: Process invalid reply
582 pjlink.process_clss('Z')582 pjlink.process_clss('Z')
583 log_text = "(127.0.0.1) NAN clss version reply 'Z' - defaulting to class '1'"583 log_text = '(127.0.0.1) NAN CLSS version reply "Z" - defaulting to class "1"'
584584
585 # THEN: Projector class should be set with default value585 # THEN: Projector class should be set with default value
586 self.assertEqual(pjlink.pjlink_class, '1',586 self.assertEqual(pjlink.pjlink_class, '1',
@@ -597,7 +597,7 @@
597597
598 # WHEN: Process invalid reply598 # WHEN: Process invalid reply
599 pjlink.process_clss('Invalid')599 pjlink.process_clss('Invalid')
600 log_text = "(127.0.0.1) No numbers found in class version reply 'Invalid' - defaulting to class '1'"600 log_text = '(127.0.0.1) No numbers found in class version reply "Invalid" - defaulting to class "1"'
601601
602 # THEN: Projector class should be set with default value602 # THEN: Projector class should be set with default value
603 self.assertEqual(pjlink.pjlink_class, '1',603 self.assertEqual(pjlink.pjlink_class, '1',
@@ -627,7 +627,7 @@
627 # GIVEN: Test object627 # GIVEN: Test object
628 pjlink = pjlink_test628 pjlink = pjlink_test
629 pjlink.projector_errors = None629 pjlink.projector_errors = None
630 log_text = "127.0.0.1) Invalid error status response '11111111': length != 6"630 log_text = '127.0.0.1) Invalid error status response "11111111": length != 6'
631631
632 # WHEN: process_erst called with invalid data (too many values632 # WHEN: process_erst called with invalid data (too many values
633 pjlink.process_erst('11111111')633 pjlink.process_erst('11111111')
@@ -645,7 +645,7 @@
645 # GIVEN: Test object645 # GIVEN: Test object
646 pjlink = pjlink_test646 pjlink = pjlink_test
647 pjlink.projector_errors = None647 pjlink.projector_errors = None
648 log_text = "(127.0.0.1) Invalid error status response '1111Z1'"648 log_text = '(127.0.0.1) Invalid error status response "1111Z1"'
649649
650 # WHEN: process_erst called with invalid data (too many values650 # WHEN: process_erst called with invalid data (too many values
651 pjlink.process_erst('1111Z1')651 pjlink.process_erst('1111Z1')
@@ -671,8 +671,8 @@
671 # THEN: PJLink instance errors should match chk_value671 # THEN: PJLink instance errors should match chk_value
672 for chk in pjlink.projector_errors:672 for chk in pjlink.projector_errors:
673 self.assertEqual(pjlink.projector_errors[chk], chk_string,673 self.assertEqual(pjlink.projector_errors[chk], chk_string,
674 "projector_errors['{chk}'] should have been set to {err}".format(chk=chk,674 'projector_errors["{chk}"] should have been set to "{err}"'.format(chk=chk,
675 err=chk_string))675 err=chk_string))
676676
677 def test_projector_process_erst_all_error(self):677 def test_projector_process_erst_all_error(self):
678 """678 """
@@ -690,8 +690,8 @@
690 # THEN: PJLink instance errors should match chk_value690 # THEN: PJLink instance errors should match chk_value
691 for chk in pjlink.projector_errors:691 for chk in pjlink.projector_errors:
692 self.assertEqual(pjlink.projector_errors[chk], chk_string,692 self.assertEqual(pjlink.projector_errors[chk], chk_string,
693 "projector_errors['{chk}'] should have been set to {err}".format(chk=chk,693 'projector_errors["{chk}"] should have been set to "{err}"'.format(chk=chk,
694 err=chk_string))694 err=chk_string))
695695
696 def test_projector_process_erst_warn_cover_only(self):696 def test_projector_process_erst_warn_cover_only(self):
697 """697 """
@@ -744,9 +744,9 @@
744 pjlink = pjlink_test744 pjlink = pjlink_test
745 pjlink.source_available = []745 pjlink.source_available = []
746 test_data = '21 10 30 31 11 20'746 test_data = '21 10 30 31 11 20'
747 test_saved = ['10', '11', '20', '21', '30', '31']747 test_saved = ["10", "11", "20", "21", "30", "31"]
748 log_data = '(127.0.0.1) Setting projector sources_available to ' \748 log_data = "(127.0.0.1) Setting projector sources_available to " \
749 '"[\'10\', \'11\', \'20\', \'21\', \'30\', \'31\']"'749 "\"['10', '11', '20', '21', '30', '31']\""
750 mock_UpdateIcons.reset_mock()750 mock_UpdateIcons.reset_mock()
751 mock_log.reset_mock()751 mock_log.reset_mock()
752752
@@ -1021,7 +1021,7 @@
1021 pjlink.sw_version = None1021 pjlink.sw_version = None
1022 pjlink.sw_version_received = None1022 pjlink.sw_version_received = None
1023 test_data = 'Test 1 Subtest 1'1023 test_data = 'Test 1 Subtest 1'
1024 test_log = "(127.0.0.1) Setting projector software version to 'Test 1 Subtest 1'"1024 test_log = '(127.0.0.1) Setting projector software version to "Test 1 Subtest 1"'
1025 mock_log.reset_mock()1025 mock_log.reset_mock()
10261026
1027 # WHEN: process_sver called with invalid data1027 # WHEN: process_sver called with invalid data
10281028
=== added file 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py'
--- tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py 2017-12-09 11:19:42 +0000
@@ -0,0 +1,198 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2015 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23Package to test the openlp.core.projectors.pjlink commands package.
24"""
25from unittest import TestCase
26from unittest.mock import patch, call
27
28import openlp.core.projectors.pjlink
29from openlp.core.projectors.constants import S_CONNECTED
30from openlp.core.projectors.db import Projector
31from openlp.core.projectors.pjlink import PJLink
32
33from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT, TEST1_DATA
34
35
36class TestPJLinkCommands(TestCase):
37 """
38 Tests for the PJLinkCommands class part 2
39 """
40 def setUp(self):
41 '''
42 TestPJLinkCommands part 2 initialization
43 '''
44 self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
45
46 def tearDown(self):
47 '''
48 TestPJLinkCommands part 2 cleanups
49 '''
50 self.pjlink_test = None
51
52 def test_process_pjlink_normal(self):
53 """
54 Test initial connection prompt with no authentication
55 """
56 # GIVEN: Initial mocks and data
57 mock_log = patch.object(openlp.core.projectors.pjlink, "log").start()
58 mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
59 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
60 mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start()
61 mock_change_status = patch.object(self.pjlink_test, 'change_status').start()
62 pjlink = self.pjlink_test
63 pjlink.pin = None
64 log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ]
65
66 # WHEN: process_pjlink called with no authentication required
67 pjlink.process_pjlink(data="0")
68
69 # THEN: proper processing should have occured
70 mock_log.debug.has_calls(log_check)
71 mock_disconnect_from_host.assert_not_called()
72 self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once')
73 mock_change_status.assert_called_once_with(S_CONNECTED)
74 mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=None)
75
76 def test_process_pjlink_authenticate(self):
77 """
78 Test initial connection prompt with authentication
79 """
80 # GIVEN: Initial mocks and data
81 mock_log = patch.object(openlp.core.projectors.pjlink, "log").start()
82 mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
83 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
84 mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start()
85 mock_change_status = patch.object(self.pjlink_test, 'change_status').start()
86 pjlink = self.pjlink_test
87 pjlink.pin = TEST_PIN
88 log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ]
89
90 # WHEN: process_pjlink called with no authentication required
91 pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT))
92
93 # THEN: proper processing should have occured
94 mock_log.debug.has_calls(log_check)
95 mock_disconnect_from_host.assert_not_called()
96 self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once')
97 mock_change_status.assert_called_once_with(S_CONNECTED)
98 mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=TEST_HASH)
99
100 def test_process_pjlink_normal_pin_set_error(self):
101 """
102 Test process_pjlinnk called with no authentication but pin is set
103 """
104 # GIVEN: Initial mocks and data
105 # GIVEN: Initial mocks and data
106 mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
107 mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
108 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
109 pjlink = self.pjlink_test
110 pjlink.pin = TEST_PIN
111 log_check = [call('(111.111.111.111) Normal connection but PIN set - aborting'), ]
112
113 # WHEN: process_pjlink called with invalid authentication scheme
114 pjlink.process_pjlink(data='0')
115
116 # THEN: Proper calls should be made
117 mock_log.error.assert_has_calls(log_check)
118 self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
119 mock_send_command.assert_not_called()
120
121 def test_process_pjlink_normal_with_salt_error(self):
122 """
123 Test process_pjlinnk called with no authentication but pin is set
124 """
125 # GIVEN: Initial mocks and data
126 # GIVEN: Initial mocks and data
127 mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
128 mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
129 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
130 pjlink = self.pjlink_test
131 pjlink.pin = TEST_PIN
132 log_check = [call('(111.111.111.111) Normal connection with extra information - aborting'), ]
133
134 # WHEN: process_pjlink called with invalid authentication scheme
135 pjlink.process_pjlink(data='0 {salt}'.format(salt=TEST_SALT))
136
137 # THEN: Proper calls should be made
138 mock_log.error.assert_has_calls(log_check)
139 self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
140 mock_send_command.assert_not_called()
141
142 def test_process_pjlink_invalid_authentication_scheme_length_error(self):
143 """
144 Test initial connection prompt with authentication scheme longer than 1 character
145 """
146 # GIVEN: Initial mocks and data
147 mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
148 mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
149 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
150 pjlink = self.pjlink_test
151 log_check = [call('(111.111.111.111) Invalid initial authentication scheme - aborting'), ]
152
153 # WHEN: process_pjlink called with invalid authentication scheme
154 pjlink.process_pjlink(data='01')
155
156 # THEN: socket should be closed and invalid data logged
157 mock_log.error.assert_has_calls(log_check)
158 self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
159 mock_send_command.assert_not_called()
160
161 def test_process_pjlink_invalid_authentication_data_length_error(self):
162 """
163 Test initial connection prompt with authentication no salt
164 """
165 # GIVEN: Initial mocks and data
166 mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
167 mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
168 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
169 log_check = [call('(111.111.111.111) Authenticated connection but not enough info - aborting'), ]
170 pjlink = self.pjlink_test
171
172 # WHEN: process_pjlink called with no salt
173 pjlink.process_pjlink(data='1')
174
175 # THEN: socket should be closed and invalid data logged
176 mock_log.error.assert_has_calls(log_check)
177 self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
178 mock_send_command.assert_not_called()
179
180 def test_process_pjlink_authenticate_pin_not_set_error(self):
181 """
182 Test process_pjlink authentication but pin not set
183 """
184 # GIVEN: Initial mocks and data
185 mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
186 mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
187 mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
188 log_check = [call('(111.111.111.111) Authenticate connection but no PIN - aborting'), ]
189 pjlink = self.pjlink_test
190 pjlink.pin = None
191
192 # WHEN: process_pjlink called with no salt
193 pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT))
194
195 # THEN: socket should be closed and invalid data logged
196 mock_log.error.assert_has_calls(log_check)
197 self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
198 mock_send_command.assert_not_called()