Merge lp:~alisonken1/openlp/pjlink2-m into lp:openlp
- pjlink2-m
- Merge into trunk
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 |
Related bugs: |
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.
- Split pjlink.
- Added QAbstractSocket connect enum to constants
- Minor code cleanups for connection and command processing
- Updated packet queueing
- Fix get_object_
- Fix tests in test_projector_
- Fix tests in test_projector_
- Added tests for process_pjlink method
- Updated test_projector_
- Some OLP style cleanups
-------
lp:~alisonken1/openlp/pjlink2-m (revision 2796)
https:/
https:/
https:/
https:/
https:/
https:/
https:/
Phill (phill-ridout) wrote : Posted in a previous version of this proposal | # |
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal | # |
See inline
Phill (phill-ridout) wrote : | # |
Great. Thanks for those changes :-)
Tim Bentley (trb143) : | # |
Preview Diff
1 | === modified file 'openlp/core/projectors/__init__.py' | |||
2 | --- openlp/core/projectors/__init__.py 2017-11-16 23:53:53 +0000 | |||
3 | +++ openlp/core/projectors/__init__.py 2017-12-09 11:19:42 +0000 | |||
4 | @@ -25,8 +25,6 @@ | |||
5 | 25 | Initialization for the openlp.core.projectors modules. | 25 | Initialization for the openlp.core.projectors modules. |
6 | 26 | """ | 26 | """ |
7 | 27 | 27 | ||
8 | 28 | from openlp.core.projectors.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING | ||
9 | 29 | |||
10 | 30 | 28 | ||
11 | 31 | class DialogSourceStyle(object): | 29 | class DialogSourceStyle(object): |
12 | 32 | """ | 30 | """ |
13 | 33 | 31 | ||
14 | === modified file 'openlp/core/projectors/constants.py' | |||
15 | --- openlp/core/projectors/constants.py 2017-11-10 11:59:38 +0000 | |||
16 | +++ openlp/core/projectors/constants.py 2017-12-09 11:19:42 +0000 | |||
17 | @@ -144,6 +144,24 @@ | |||
18 | 144 | } | 144 | } |
19 | 145 | } | 145 | } |
20 | 146 | 146 | ||
21 | 147 | # QAbstractSocketState enums converted to string | ||
22 | 148 | S_QSOCKET_STATE = { | ||
23 | 149 | 0: 'QSocketState - UnconnectedState', | ||
24 | 150 | 1: 'QSocketState - HostLookupState', | ||
25 | 151 | 2: 'QSocketState - ConnectingState', | ||
26 | 152 | 3: 'QSocketState - ConnectedState', | ||
27 | 153 | 4: 'QSocketState - BoundState', | ||
28 | 154 | 5: 'QSocketState - ListeningState (internal use only)', | ||
29 | 155 | 6: 'QSocketState - ClosingState', | ||
30 | 156 | 'UnconnectedState': 0, | ||
31 | 157 | 'HostLookupState': 1, | ||
32 | 158 | 'ConnectingState': 2, | ||
33 | 159 | 'ConnectedState': 3, | ||
34 | 160 | 'BoundState': 4, | ||
35 | 161 | 'ListeningState': 5, | ||
36 | 162 | 'ClosingState': 6 | ||
37 | 163 | } | ||
38 | 164 | |||
39 | 147 | # Error and status codes | 165 | # Error and status codes |
40 | 148 | S_OK = E_OK = 0 # E_OK included since I sometimes forget | 166 | S_OK = E_OK = 0 # E_OK included since I sometimes forget |
41 | 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. |
42 | 150 | 168 | ||
43 | === modified file 'openlp/core/projectors/db.py' | |||
44 | --- openlp/core/projectors/db.py 2017-11-10 11:59:38 +0000 | |||
45 | +++ openlp/core/projectors/db.py 2017-12-09 11:19:42 +0000 | |||
46 | @@ -415,7 +415,7 @@ | |||
47 | 415 | for key in projector.source_available: | 415 | for key in projector.source_available: |
48 | 416 | item = self.get_object_filtered(ProjectorSource, | 416 | item = self.get_object_filtered(ProjectorSource, |
49 | 417 | and_(ProjectorSource.code == key, | 417 | and_(ProjectorSource.code == key, |
51 | 418 | ProjectorSource.projector_id == projector.dbid)) | 418 | ProjectorSource.projector_id == projector.id)) |
52 | 419 | if item is None: | 419 | if item is None: |
53 | 420 | source_dict[key] = PJLINK_DEFAULT_CODES[key] | 420 | source_dict[key] = PJLINK_DEFAULT_CODES[key] |
54 | 421 | else: | 421 | else: |
55 | 422 | 422 | ||
56 | === modified file 'openlp/core/projectors/pjlink.py' | |||
57 | --- openlp/core/projectors/pjlink.py 2017-11-24 19:08:23 +0000 | |||
58 | +++ openlp/core/projectors/pjlink.py 2017-12-09 11:19:42 +0000 | |||
59 | @@ -58,8 +58,7 @@ | |||
60 | 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, \ |
61 | 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, \ |
62 | 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, \ |
65 | 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 |
64 | 62 | S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS | ||
66 | 63 | 62 | ||
67 | 64 | log = logging.getLogger(__name__) | 63 | log = logging.getLogger(__name__) |
68 | 65 | log.debug('pjlink loaded') | 64 | log.debug('pjlink loaded') |
69 | @@ -111,7 +110,7 @@ | |||
70 | 111 | """ | 110 | """ |
71 | 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)) |
72 | 113 | super().__init__() | 112 | super().__init__() |
74 | 114 | # Map command to function | 113 | # Map PJLink command to method |
75 | 115 | self.pjlink_functions = { | 114 | self.pjlink_functions = { |
76 | 116 | 'AVMT': self.process_avmt, | 115 | 'AVMT': self.process_avmt, |
77 | 117 | 'CLSS': self.process_clss, | 116 | 'CLSS': self.process_clss, |
78 | @@ -123,7 +122,9 @@ | |||
79 | 123 | 'INST': self.process_inst, | 122 | 'INST': self.process_inst, |
80 | 124 | 'LAMP': self.process_lamp, | 123 | 'LAMP': self.process_lamp, |
81 | 125 | 'NAME': self.process_name, | 124 | 'NAME': self.process_name, |
83 | 126 | 'PJLINK': self.check_login, | 125 | 'PJLINK': self.process_pjlink, |
84 | 126 | # TODO: Part of check_login refactor - remove when done | ||
85 | 127 | # 'PJLINK': self.check_login, | ||
86 | 127 | 'POWR': self.process_powr, | 128 | 'POWR': self.process_powr, |
87 | 128 | 'SNUM': self.process_snum, | 129 | 'SNUM': self.process_snum, |
88 | 129 | 'SVER': self.process_sver, | 130 | 'SVER': self.process_sver, |
89 | @@ -135,7 +136,8 @@ | |||
90 | 135 | """ | 136 | """ |
91 | 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. |
92 | 137 | """ | 138 | """ |
94 | 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, |
95 | 140 | state=S_QSOCKET_STATE[self.state()])) | ||
96 | 139 | self.fan = None # ERST | 141 | self.fan = None # ERST |
97 | 140 | self.filter_time = None # FILT | 142 | self.filter_time = None # FILT |
98 | 141 | self.lamp = None # LAMP | 143 | self.lamp = None # LAMP |
99 | @@ -165,6 +167,7 @@ | |||
100 | 165 | self.socket_timer.stop() | 167 | self.socket_timer.stop() |
101 | 166 | self.send_busy = False | 168 | self.send_busy = False |
102 | 167 | self.send_queue = [] | 169 | self.send_queue = [] |
103 | 170 | self.priority_queue = [] | ||
104 | 168 | 171 | ||
105 | 169 | def process_command(self, cmd, data): | 172 | def process_command(self, cmd, data): |
106 | 170 | """ | 173 | """ |
107 | @@ -176,18 +179,19 @@ | |||
108 | 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, |
109 | 177 | cmd=cmd, | 180 | cmd=cmd, |
110 | 178 | data=data)) | 181 | data=data)) |
111 | 182 | # cmd should already be in uppercase, but data may be in mixed-case. | ||
112 | 183 | # Due to some replies should stay as mixed-case, validate using separate uppercase check | ||
113 | 184 | _data = data.upper() | ||
114 | 179 | # Check if we have a future command not available yet | 185 | # Check if we have a future command not available yet |
119 | 180 | _cmd = cmd.upper() | 186 | if cmd not in PJLINK_VALID_CMD: |
120 | 181 | _data = data.upper() | 187 | log.error('({ip}) Ignoring command="{cmd}" (Invalid/Unknown)'.format(ip=self.ip, cmd=cmd)) |
117 | 182 | if _cmd not in PJLINK_VALID_CMD: | ||
118 | 183 | log.error("({ip}) Ignoring command='{cmd}' (Invalid/Unknown)".format(ip=self.ip, cmd=cmd)) | ||
121 | 184 | return | 188 | return |
122 | 185 | elif _data == 'OK': | 189 | elif _data == 'OK': |
123 | 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)) |
128 | 187 | # A command returned successfully, no further processing needed | 191 | # A command returned successfully, so do a query on command to verify status |
129 | 188 | return | 192 | return self.send_command(cmd=cmd) |
130 | 189 | elif _cmd not in self.pjlink_functions: | 193 | elif cmd not in self.pjlink_functions: |
131 | 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)) |
132 | 191 | return | 195 | return |
133 | 192 | elif _data in PJLINK_ERRORS: | 196 | elif _data in PJLINK_ERRORS: |
134 | 193 | # Oops - projector error | 197 | # Oops - projector error |
135 | @@ -211,12 +215,10 @@ | |||
136 | 211 | elif _data == PJLINK_ERRORS[E_PROJECTOR]: | 215 | elif _data == PJLINK_ERRORS[E_PROJECTOR]: |
137 | 212 | # Projector/display error | 216 | # Projector/display error |
138 | 213 | self.change_status(E_PROJECTOR) | 217 | self.change_status(E_PROJECTOR) |
139 | 214 | self.receive_data_signal() | ||
140 | 215 | return | 218 | return |
141 | 216 | # Command checks already passed | 219 | # Command checks already passed |
142 | 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)) |
145 | 218 | self.receive_data_signal() | 221 | self.pjlink_functions[cmd](data) |
144 | 219 | self.pjlink_functions[_cmd](data) | ||
146 | 220 | 222 | ||
147 | 221 | def process_avmt(self, data): | 223 | def process_avmt(self, data): |
148 | 222 | """ | 224 | """ |
149 | @@ -259,19 +261,19 @@ | |||
150 | 259 | # : Received: '%1CLSS=Class 1' (Optoma) | 261 | # : Received: '%1CLSS=Class 1' (Optoma) |
151 | 260 | # : Received: '%1CLSS=Version1' (BenQ) | 262 | # : Received: '%1CLSS=Version1' (BenQ) |
152 | 261 | if len(data) > 1: | 263 | if len(data) > 1: |
154 | 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)) |
155 | 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), |
156 | 264 | # AND the different responses that can be received, the semi-permanent way to | 266 | # AND the different responses that can be received, the semi-permanent way to |
157 | 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. |
158 | 266 | try: | 268 | try: |
159 | 267 | clss = re.findall('\d', data)[0] # Should only be the first match | 269 | clss = re.findall('\d', data)[0] # Should only be the first match |
160 | 268 | except IndexError: | 270 | except IndexError: |
163 | 269 | log.error("({ip}) No numbers found in class version reply '{data}' - " | 271 | log.error('({ip}) No numbers found in class version reply "{data}" - ' |
164 | 270 | "defaulting to class '1'".format(ip=self.ip, data=data)) | 272 | 'defaulting to class "1"'.format(ip=self.ip, data=data)) |
165 | 271 | clss = '1' | 273 | clss = '1' |
166 | 272 | elif not data.isdigit(): | 274 | elif not data.isdigit(): |
169 | 273 | log.error("({ip}) NAN clss version reply '{data}' - " | 275 | log.error('({ip}) NAN CLSS version reply "{data}" - ' |
170 | 274 | "defaulting to class '1'".format(ip=self.ip, data=data)) | 276 | 'defaulting to class "1"'.format(ip=self.ip, data=data)) |
171 | 275 | clss = '1' | 277 | clss = '1' |
172 | 276 | else: | 278 | else: |
173 | 277 | clss = data | 279 | clss = data |
174 | @@ -289,7 +291,7 @@ | |||
175 | 289 | """ | 291 | """ |
176 | 290 | if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']: | 292 | if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']: |
177 | 291 | count = PJLINK_ERST_DATA['DATA_LENGTH'] | 293 | count = PJLINK_ERST_DATA['DATA_LENGTH'] |
179 | 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, |
180 | 293 | data=data, | 295 | data=data, |
181 | 294 | count=count)) | 296 | count=count)) |
182 | 295 | return | 297 | return |
183 | @@ -297,7 +299,7 @@ | |||
184 | 297 | datacheck = int(data) | 299 | datacheck = int(data) |
185 | 298 | except ValueError: | 300 | except ValueError: |
186 | 299 | # Bad data - ignore | 301 | # Bad data - ignore |
188 | 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)) |
189 | 301 | return | 303 | return |
190 | 302 | if datacheck == 0: | 304 | if datacheck == 0: |
191 | 303 | self.projector_errors = None | 305 | self.projector_errors = None |
192 | @@ -430,6 +432,51 @@ | |||
193 | 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)) |
194 | 431 | return | 433 | return |
195 | 432 | 434 | ||
196 | 435 | def process_pjlink(self, data): | ||
197 | 436 | """ | ||
198 | 437 | Process initial socket connection to terminal. | ||
199 | 438 | |||
200 | 439 | :param data: Initial packet with authentication scheme | ||
201 | 440 | """ | ||
202 | 441 | log.debug('({ip}) Processing PJLINK command'.format(ip=self.ip)) | ||
203 | 442 | chk = data.split(' ') | ||
204 | 443 | if len(chk[0]) != 1: | ||
205 | 444 | # Invalid - after splitting, first field should be 1 character, either '0' or '1' only | ||
206 | 445 | log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=self.ip)) | ||
207 | 446 | return self.disconnect_from_host() | ||
208 | 447 | elif chk[0] == '0': | ||
209 | 448 | # Normal connection no authentication | ||
210 | 449 | if len(chk) > 1: | ||
211 | 450 | # Invalid data - there should be nothing after a normal authentication scheme | ||
212 | 451 | log.error('({ip}) Normal connection with extra information - aborting'.format(ip=self.ip)) | ||
213 | 452 | return self.disconnect_from_host() | ||
214 | 453 | elif self.pin: | ||
215 | 454 | log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=self.ip)) | ||
216 | 455 | return self.disconnect_from_host() | ||
217 | 456 | else: | ||
218 | 457 | data_hash = None | ||
219 | 458 | elif chk[0] == '1': | ||
220 | 459 | if len(chk) < 2: | ||
221 | 460 | # Not enough information for authenticated connection | ||
222 | 461 | log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=self.ip)) | ||
223 | 462 | return self.disconnect_from_host() | ||
224 | 463 | elif not self.pin: | ||
225 | 464 | log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=self.ip)) | ||
226 | 465 | return self.disconnect_from_host() | ||
227 | 466 | else: | ||
228 | 467 | data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')), | ||
229 | 468 | encoding='ascii') | ||
230 | 469 | # Passed basic checks, so start connection | ||
231 | 470 | self.readyRead.connect(self.get_socket) | ||
232 | 471 | if not self.no_poll: | ||
233 | 472 | log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.ip)) | ||
234 | 473 | self.timer.setInterval(2000) # Set 2 seconds for initial information | ||
235 | 474 | self.timer.start() | ||
236 | 475 | self.change_status(S_CONNECTED) | ||
237 | 476 | log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=self.ip)) | ||
238 | 477 | # Since this is an initial connection, make it a priority just in case | ||
239 | 478 | return self.send_command(cmd="CLSS", salt=data_hash, priority=True) | ||
240 | 479 | |||
241 | 433 | def process_powr(self, data): | 480 | def process_powr(self, data): |
242 | 434 | """ | 481 | """ |
243 | 435 | Power status. See PJLink specification for format. | 482 | Power status. See PJLink specification for format. |
244 | @@ -450,7 +497,7 @@ | |||
245 | 450 | self.send_command('INST') | 497 | self.send_command('INST') |
246 | 451 | else: | 498 | else: |
247 | 452 | # Log unknown status response | 499 | # Log unknown status response |
249 | 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)) |
250 | 454 | return | 501 | return |
251 | 455 | 502 | ||
252 | 456 | def process_rfil(self, data): | 503 | def process_rfil(self, data): |
253 | @@ -460,9 +507,9 @@ | |||
254 | 460 | if self.model_filter is None: | 507 | if self.model_filter is None: |
255 | 461 | self.model_filter = data | 508 | self.model_filter = data |
256 | 462 | else: | 509 | else: |
260 | 463 | log.warning("({ip}) Filter model already set".format(ip=self.ip)) | 510 | log.warning('({ip}) Filter model already set'.format(ip=self.ip)) |
261 | 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)) |
262 | 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)) |
263 | 466 | 513 | ||
264 | 467 | def process_rlmp(self, data): | 514 | def process_rlmp(self, data): |
265 | 468 | """ | 515 | """ |
266 | @@ -471,9 +518,9 @@ | |||
267 | 471 | if self.model_lamp is None: | 518 | if self.model_lamp is None: |
268 | 472 | self.model_lamp = data | 519 | self.model_lamp = data |
269 | 473 | else: | 520 | else: |
273 | 474 | log.warning("({ip}) Lamp model already set".format(ip=self.ip)) | 521 | log.warning('({ip}) Lamp model already set'.format(ip=self.ip)) |
274 | 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)) |
275 | 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)) |
276 | 477 | 524 | ||
277 | 478 | def process_snum(self, data): | 525 | def process_snum(self, data): |
278 | 479 | """ | 526 | """ |
279 | @@ -482,16 +529,16 @@ | |||
280 | 482 | :param data: Serial number from projector. | 529 | :param data: Serial number from projector. |
281 | 483 | """ | 530 | """ |
282 | 484 | if self.serial_no is None: | 531 | if self.serial_no is None: |
284 | 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)) |
285 | 486 | self.serial_no = data | 533 | self.serial_no = data |
286 | 487 | self.db_update = False | 534 | self.db_update = False |
287 | 488 | else: | 535 | else: |
288 | 489 | # Compare serial numbers and see if we got the same projector | 536 | # Compare serial numbers and see if we got the same projector |
289 | 490 | if self.serial_no != data: | 537 | if self.serial_no != data: |
294 | 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)) |
295 | 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)) |
296 | 493 | log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data)) | 540 | log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data)) |
297 | 494 | log.warning("({ip}) NOT saving serial number".format(ip=self.ip)) | 541 | log.warning('({ip}) NOT saving serial number'.format(ip=self.ip)) |
298 | 495 | self.serial_no_received = data | 542 | self.serial_no_received = data |
299 | 496 | 543 | ||
300 | 497 | def process_sver(self, data): | 544 | def process_sver(self, data): |
301 | @@ -500,20 +547,20 @@ | |||
302 | 500 | """ | 547 | """ |
303 | 501 | if len(data) > 32: | 548 | if len(data) > 32: |
304 | 502 | # Defined in specs max version is 32 characters | 549 | # Defined in specs max version is 32 characters |
306 | 503 | log.warning("Invalid software version - too long") | 550 | log.warning('Invalid software version - too long') |
307 | 504 | return | 551 | return |
308 | 505 | elif self.sw_version is None: | 552 | elif self.sw_version is None: |
310 | 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)) |
311 | 507 | self.sw_version = data | 554 | self.sw_version = data |
312 | 508 | self.db_update = True | 555 | self.db_update = True |
313 | 509 | else: | 556 | else: |
314 | 510 | # Compare software version and see if we got the same projector | 557 | # Compare software version and see if we got the same projector |
315 | 511 | if self.serial_no != data: | 558 | if self.serial_no != data: |
321 | 512 | log.warning("({ip}) Projector software version does not match saved " | 559 | log.warning('({ip}) Projector software version does not match saved ' |
322 | 513 | "software version".format(ip=self.ip)) | 560 | 'software version'.format(ip=self.ip)) |
323 | 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)) |
324 | 515 | log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data)) | 562 | log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data)) |
325 | 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)) |
326 | 517 | self.sw_version_received = data | 564 | self.sw_version_received = data |
327 | 518 | 565 | ||
328 | 519 | 566 | ||
329 | @@ -540,9 +587,9 @@ | |||
330 | 540 | :param poll_time: Time (in seconds) to poll connected projector | 587 | :param poll_time: Time (in seconds) to poll connected projector |
331 | 541 | :param socket_timeout: Time (in seconds) to abort the connection if no response | 588 | :param socket_timeout: Time (in seconds) to abort the connection if no response |
332 | 542 | """ | 589 | """ |
336 | 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, |
337 | 544 | args=args, | 591 | args=args, |
338 | 545 | kwargs=kwargs)) | 592 | kwargs=kwargs)) |
339 | 546 | super().__init__() | 593 | super().__init__() |
340 | 547 | self.entry = projector | 594 | self.entry = projector |
341 | 548 | self.ip = self.entry.ip | 595 | self.ip = self.entry.ip |
342 | @@ -573,6 +620,7 @@ | |||
343 | 573 | self.widget = None # QListBox entry | 620 | self.widget = None # QListBox entry |
344 | 574 | self.timer = None # Timer that calls the poll_loop | 621 | self.timer = None # Timer that calls the poll_loop |
345 | 575 | self.send_queue = [] | 622 | self.send_queue = [] |
346 | 623 | self.priority_queue = [] | ||
347 | 576 | self.send_busy = False | 624 | self.send_busy = False |
348 | 577 | # Socket timer for some possible brain-dead projectors or network cable pulled | 625 | # Socket timer for some possible brain-dead projectors or network cable pulled |
349 | 578 | self.socket_timer = None | 626 | self.socket_timer = None |
350 | @@ -586,6 +634,7 @@ | |||
351 | 586 | self.connected.connect(self.check_login) | 634 | self.connected.connect(self.check_login) |
352 | 587 | self.disconnected.connect(self.disconnect_from_host) | 635 | self.disconnected.connect(self.disconnect_from_host) |
353 | 588 | self.error.connect(self.get_error) | 636 | self.error.connect(self.get_error) |
354 | 637 | self.projectorReceivedData.connect(self._send_command) | ||
355 | 589 | 638 | ||
356 | 590 | def thread_stopped(self): | 639 | def thread_stopped(self): |
357 | 591 | """ | 640 | """ |
358 | @@ -608,6 +657,10 @@ | |||
359 | 608 | self.projectorReceivedData.disconnect(self._send_command) | 657 | self.projectorReceivedData.disconnect(self._send_command) |
360 | 609 | except TypeError: | 658 | except TypeError: |
361 | 610 | pass | 659 | pass |
362 | 660 | try: | ||
363 | 661 | self.readyRead.disconnect(self.get_socket) # Set in process_pjlink | ||
364 | 662 | except TypeError: | ||
365 | 663 | pass | ||
366 | 611 | self.disconnect_from_host() | 664 | self.disconnect_from_host() |
367 | 612 | self.deleteLater() | 665 | self.deleteLater() |
368 | 613 | self.i_am_running = False | 666 | self.i_am_running = False |
369 | @@ -625,10 +678,10 @@ | |||
370 | 625 | Retrieve information from projector that changes. | 678 | Retrieve information from projector that changes. |
371 | 626 | Normally called by timer(). | 679 | Normally called by timer(). |
372 | 627 | """ | 680 | """ |
375 | 628 | if self.state() != self.ConnectedState: | 681 | if self.state() != S_QSOCKET_STATE['ConnectedState']: |
376 | 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)) |
377 | 630 | return | 683 | return |
379 | 631 | log.debug('({ip}) Updating projector status'.format(ip=self.ip)) | 684 | log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.ip)) |
380 | 632 | # Reset timer in case we were called from a set command | 685 | # Reset timer in case we were called from a set command |
381 | 633 | if self.timer.interval() < self.poll_time: | 686 | if self.timer.interval() < self.poll_time: |
382 | 634 | # Reset timer to 5 seconds | 687 | # Reset timer to 5 seconds |
383 | @@ -640,28 +693,28 @@ | |||
384 | 640 | if self.pjlink_class == '2': | 693 | if self.pjlink_class == '2': |
385 | 641 | check_list.extend(['FILT', 'FREZ']) | 694 | check_list.extend(['FILT', 'FREZ']) |
386 | 642 | for command in check_list: | 695 | for command in check_list: |
388 | 643 | self.send_command(command, queue=True) | 696 | self.send_command(command) |
389 | 644 | # The following commands do not change, so only check them once | 697 | # The following commands do not change, so only check them once |
390 | 645 | if self.power == S_ON and self.source_available is None: | 698 | if self.power == S_ON and self.source_available is None: |
392 | 646 | self.send_command('INST', queue=True) | 699 | self.send_command('INST') |
393 | 647 | if self.other_info is None: | 700 | if self.other_info is None: |
395 | 648 | self.send_command('INFO', queue=True) | 701 | self.send_command('INFO') |
396 | 649 | if self.manufacturer is None: | 702 | if self.manufacturer is None: |
398 | 650 | self.send_command('INF1', queue=True) | 703 | self.send_command('INF1') |
399 | 651 | if self.model is None: | 704 | if self.model is None: |
401 | 652 | self.send_command('INF2', queue=True) | 705 | self.send_command('INF2') |
402 | 653 | if self.pjlink_name is None: | 706 | if self.pjlink_name is None: |
404 | 654 | self.send_command('NAME', queue=True) | 707 | self.send_command('NAME') |
405 | 655 | if self.pjlink_class == '2': | 708 | if self.pjlink_class == '2': |
406 | 656 | # Class 2 specific checks | 709 | # Class 2 specific checks |
407 | 657 | if self.serial_no is None: | 710 | if self.serial_no is None: |
409 | 658 | self.send_command('SNUM', queue=True) | 711 | self.send_command('SNUM') |
410 | 659 | if self.sw_version is None: | 712 | if self.sw_version is None: |
412 | 660 | self.send_command('SVER', queue=True) | 713 | self.send_command('SVER') |
413 | 661 | if self.model_filter is None: | 714 | if self.model_filter is None: |
415 | 662 | self.send_command('RFIL', queue=True) | 715 | self.send_command('RFIL') |
416 | 663 | if self.model_lamp is None: | 716 | if self.model_lamp is None: |
418 | 664 | self.send_command('RLMP', queue=True) | 717 | self.send_command('RLMP') |
419 | 665 | 718 | ||
420 | 666 | def _get_status(self, status): | 719 | def _get_status(self, status): |
421 | 667 | """ | 720 | """ |
422 | @@ -713,14 +766,12 @@ | |||
423 | 713 | code=status_code, | 766 | code=status_code, |
424 | 714 | message=status_message if msg is None else msg)) | 767 | message=status_message if msg is None else msg)) |
425 | 715 | self.changeStatus.emit(self.ip, status, message) | 768 | self.changeStatus.emit(self.ip, status, message) |
426 | 769 | self.projectorUpdateIcons.emit() | ||
427 | 716 | 770 | ||
428 | 717 | @QtCore.pyqtSlot() | 771 | @QtCore.pyqtSlot() |
429 | 718 | def check_login(self, data=None): | 772 | def check_login(self, data=None): |
430 | 719 | """ | 773 | """ |
435 | 720 | Processes the initial connection and authentication (if needed). | 774 | Processes the initial connection and convert to a PJLink packet if valid initial connection |
432 | 721 | Starts poll timer if connection is established. | ||
433 | 722 | |||
434 | 723 | NOTE: Qt md5 hash function doesn't work with projector authentication. Use the python md5 hash function. | ||
436 | 724 | 775 | ||
437 | 725 | :param data: Optional data if called from another routine | 776 | :param data: Optional data if called from another routine |
438 | 726 | """ | 777 | """ |
439 | @@ -733,12 +784,12 @@ | |||
440 | 733 | self.change_status(E_SOCKET_TIMEOUT) | 784 | self.change_status(E_SOCKET_TIMEOUT) |
441 | 734 | return | 785 | return |
442 | 735 | read = self.readLine(self.max_size) | 786 | read = self.readLine(self.max_size) |
444 | 736 | self.readLine(self.max_size) # Clean out the trailing \r\n | 787 | self.readLine(self.max_size) # Clean out any trailing whitespace |
445 | 737 | if read is None: | 788 | if read is None: |
446 | 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)) |
447 | 739 | return | 790 | return |
448 | 740 | elif len(read) < 8: | 791 | elif len(read) < 8: |
450 | 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)) |
451 | 742 | return | 793 | return |
452 | 743 | data = decode(read, 'utf-8') | 794 | data = decode(read, 'utf-8') |
453 | 744 | # Possibility of extraneous data on input when reading. | 795 | # Possibility of extraneous data on input when reading. |
454 | @@ -750,9 +801,16 @@ | |||
455 | 750 | # PJLink initial login will be: | 801 | # PJLink initial login will be: |
456 | 751 | # 'PJLink 0' - Unauthenticated login - no extra steps required. | 802 | # 'PJLink 0' - Unauthenticated login - no extra steps required. |
457 | 752 | # 'PJLink 1 XXXXXX' Authenticated login - extra processing required. | 803 | # 'PJLink 1 XXXXXX' Authenticated login - extra processing required. |
460 | 753 | if not data.upper().startswith('PJLINK'): | 804 | if not data.startswith('PJLINK'): |
461 | 754 | # Invalid response | 805 | # Invalid initial packet - close socket |
462 | 806 | log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.ip)) | ||
463 | 755 | return self.disconnect_from_host() | 807 | return self.disconnect_from_host() |
464 | 808 | log.debug('({ip}) check_login(): Formatting initial connection prompt to PJLink packet'.format(ip=self.ip)) | ||
465 | 809 | return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX, | ||
466 | 810 | clss='1', | ||
467 | 811 | data=data.replace(' ', '=', 1)).encode('utf-8')) | ||
468 | 812 | # TODO: The below is replaced by process_pjlink() - remove when working properly | ||
469 | 813 | """ | ||
470 | 756 | if '=' in data: | 814 | if '=' in data: |
471 | 757 | # Processing a login reply | 815 | # Processing a login reply |
472 | 758 | data_check = data.strip().split('=') | 816 | data_check = data.strip().split('=') |
473 | @@ -801,18 +859,19 @@ | |||
474 | 801 | log.debug('({ip}) Starting timer'.format(ip=self.ip)) | 859 | log.debug('({ip}) Starting timer'.format(ip=self.ip)) |
475 | 802 | self.timer.setInterval(2000) # Set 2 seconds for initial information | 860 | self.timer.setInterval(2000) # Set 2 seconds for initial information |
476 | 803 | self.timer.start() | 861 | self.timer.start() |
477 | 862 | """ | ||
478 | 804 | 863 | ||
479 | 805 | def _trash_buffer(self, msg=None): | 864 | def _trash_buffer(self, msg=None): |
480 | 806 | """ | 865 | """ |
481 | 807 | Clean out extraneous stuff in the buffer. | 866 | Clean out extraneous stuff in the buffer. |
482 | 808 | """ | 867 | """ |
484 | 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)) |
485 | 810 | self.send_busy = False | 869 | self.send_busy = False |
486 | 811 | trash_count = 0 | 870 | trash_count = 0 |
487 | 812 | while self.bytesAvailable() > 0: | 871 | while self.bytesAvailable() > 0: |
488 | 813 | trash = self.read(self.max_size) | 872 | trash = self.read(self.max_size) |
489 | 814 | trash_count += len(trash) | 873 | trash_count += len(trash) |
491 | 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, |
492 | 816 | count=trash_count)) | 875 | count=trash_count)) |
493 | 817 | return | 876 | return |
494 | 818 | 877 | ||
495 | @@ -824,7 +883,7 @@ | |||
496 | 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. |
497 | 825 | :param ip: Destination IP for buffer. | 884 | :param ip: Destination IP for buffer. |
498 | 826 | """ | 885 | """ |
500 | 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)) |
501 | 828 | if ip is None: | 887 | if ip is None: |
502 | 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)) |
503 | 830 | return | 889 | return |
504 | @@ -842,38 +901,52 @@ | |||
505 | 842 | return | 901 | return |
506 | 843 | # Although we have a packet length limit, go ahead and use a larger buffer | 902 | # Although we have a packet length limit, go ahead and use a larger buffer |
507 | 844 | read = self.readLine(1024) | 903 | read = self.readLine(1024) |
509 | 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)) |
510 | 846 | if read == -1: | 905 | if read == -1: |
511 | 847 | # No data available | 906 | # No data available |
512 | 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)) |
513 | 849 | return self.receive_data_signal() | 908 | return self.receive_data_signal() |
514 | 850 | self.socket_timer.stop() | 909 | self.socket_timer.stop() |
516 | 851 | return self.get_data(buff=read, ip=self.ip) | 910 | self.get_data(buff=read, ip=self.ip) |
517 | 911 | return self.receive_data_signal() | ||
518 | 852 | 912 | ||
520 | 853 | def get_data(self, buff, ip): | 913 | def get_data(self, buff, ip=None): |
521 | 854 | """ | 914 | """ |
522 | 855 | Process received data | 915 | Process received data |
523 | 856 | 916 | ||
524 | 857 | :param buff: Data to process. | 917 | :param buff: Data to process. |
525 | 858 | :param ip: (optional) Destination IP. | 918 | :param ip: (optional) Destination IP. |
526 | 859 | """ | 919 | """ |
528 | 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", |
529 | 921 | # set to default here | ||
530 | 922 | if ip is None: | ||
531 | 923 | ip = self.ip | ||
532 | 924 | log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.ip, ip_in=ip, buff=buff)) | ||
533 | 861 | # NOTE: Class2 has changed to some values being UTF-8 | 925 | # NOTE: Class2 has changed to some values being UTF-8 |
534 | 862 | data_in = decode(buff, 'utf-8') | 926 | data_in = decode(buff, 'utf-8') |
535 | 863 | data = data_in.strip() | 927 | data = data_in.strip() |
538 | 864 | if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)): | 928 | # Initial packet checks |
539 | 865 | return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix') | 929 | if (len(data) < 7): |
540 | 930 | return self._trash_buffer(msg='get_data(): Invalid packet - length') | ||
541 | 866 | elif len(data) > self.max_size: | 931 | elif len(data) > self.max_size: |
542 | 867 | return self._trash_buffer(msg='get_data(): Invalid packet - too long') | 932 | return self._trash_buffer(msg='get_data(): Invalid packet - too long') |
543 | 933 | elif not data.startswith(PJLINK_PREFIX): | ||
544 | 934 | return self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing') | ||
545 | 868 | elif '=' not in data: | 935 | elif '=' not in data: |
547 | 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 "="') |
548 | 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)) |
549 | 871 | header, data = data.split('=') | 938 | header, data = data.split('=') |
550 | 939 | # At this point, the header should contain: | ||
551 | 940 | # "PVCCCC" | ||
552 | 941 | # Where: | ||
553 | 942 | # P = PJLINK_PREFIX | ||
554 | 943 | # V = PJLink class or version | ||
555 | 944 | # C = PJLink command | ||
556 | 872 | try: | 945 | try: |
558 | 873 | version, cmd = header[1], header[2:] | 946 | version, cmd = header[1], header[2:].upper() |
559 | 874 | except ValueError as e: | 947 | except ValueError as e: |
560 | 875 | self.change_status(E_INVALID_DATA) | 948 | self.change_status(E_INVALID_DATA) |
562 | 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)) |
563 | 877 | return self._trash_buffer('get_data(): Expected header + command + data') | 950 | return self._trash_buffer('get_data(): Expected header + command + data') |
564 | 878 | if cmd not in PJLINK_VALID_CMD: | 951 | if cmd not in PJLINK_VALID_CMD: |
565 | 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)) |
566 | @@ -881,6 +954,7 @@ | |||
567 | 881 | if int(self.pjlink_class) < int(version): | 954 | if int(self.pjlink_class) < int(version): |
568 | 882 | log.warning('({ip}) get_data(): Projector returned class reply higher ' | 955 | log.warning('({ip}) get_data(): Projector returned class reply higher ' |
569 | 883 | 'than projector stated class'.format(ip=self.ip)) | 956 | 'than projector stated class'.format(ip=self.ip)) |
570 | 957 | self.send_busy = False | ||
571 | 884 | return self.process_command(cmd, data) | 958 | return self.process_command(cmd, data) |
572 | 885 | 959 | ||
573 | 886 | @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError) | 960 | @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError) |
574 | @@ -910,19 +984,18 @@ | |||
575 | 910 | self.reset_information() | 984 | self.reset_information() |
576 | 911 | return | 985 | return |
577 | 912 | 986 | ||
579 | 913 | def send_command(self, cmd, opts='?', salt=None, queue=False): | 987 | def send_command(self, cmd, opts='?', salt=None, priority=False): |
580 | 914 | """ | 988 | """ |
581 | 915 | Add command to output queue if not already in queue. | 989 | Add command to output queue if not already in queue. |
582 | 916 | 990 | ||
583 | 917 | :param cmd: Command to send | 991 | :param cmd: Command to send |
584 | 918 | :param opts: Command option (if any) - defaults to '?' (get information) | 992 | :param opts: Command option (if any) - defaults to '?' (get information) |
585 | 919 | :param salt: Optional salt for md5 hash initial authentication | 993 | :param salt: Optional salt for md5 hash initial authentication |
587 | 920 | :param queue: Option to force add to queue rather than sending directly | 994 | :param priority: Option to send packet now rather than queue it up |
588 | 921 | """ | 995 | """ |
589 | 922 | if self.state() != self.ConnectedState: | 996 | if self.state() != self.ConnectedState: |
590 | 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)) |
593 | 924 | self.send_queue = [] | 998 | return self.reset_information() |
592 | 925 | return | ||
594 | 926 | if cmd not in PJLINK_VALID_CMD: | 999 | if cmd not in PJLINK_VALID_CMD: |
595 | 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)) |
596 | 928 | return | 1001 | return |
597 | @@ -939,28 +1012,26 @@ | |||
598 | 939 | header = PJLINK_HEADER.format(linkclass=cmd_ver[0]) | 1012 | header = PJLINK_HEADER.format(linkclass=cmd_ver[0]) |
599 | 940 | else: | 1013 | else: |
600 | 941 | # NOTE: Once we get to version 3 then think about looping | 1014 | # NOTE: Once we get to version 3 then think about looping |
602 | 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)) |
603 | 943 | return | 1016 | return |
604 | 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, |
605 | 945 | header=header, | 1018 | header=header, |
606 | 946 | command=cmd, | 1019 | command=cmd, |
607 | 947 | options=opts, | 1020 | options=opts, |
608 | 948 | suffix=CR) | 1021 | suffix=CR) |
617 | 949 | if out in self.send_queue: | 1022 | if out in self.priority_queue: |
618 | 950 | # Already there, so don't add | 1023 | log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.ip)) |
619 | 951 | log.debug('({ip}) send_command(out="{data}") Already in queue - skipping'.format(ip=self.ip, | 1024 | elif out in self.send_queue: |
620 | 952 | data=out.strip())) | 1025 | log.debug('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.ip)) |
613 | 953 | elif not queue and len(self.send_queue) == 0: | ||
614 | 954 | # Nothing waiting to send, so just send it | ||
615 | 955 | log.debug('({ip}) send_command(out="{data}") Sending data'.format(ip=self.ip, data=out.strip())) | ||
616 | 956 | return self._send_command(data=out) | ||
621 | 957 | else: | 1026 | else: |
628 | 958 | log.debug('({ip}) send_command(out="{data}") adding to queue'.format(ip=self.ip, data=out.strip())) | 1027 | if priority: |
629 | 959 | self.send_queue.append(out) | 1028 | log.debug('({ip}) send_command(): Adding to priority queue'.format(ip=self.ip)) |
630 | 960 | self.projectorReceivedData.emit() | 1029 | self.priority_queue.append(out) |
631 | 961 | log.debug('({ip}) send_command(): send_busy is {data}'.format(ip=self.ip, data=self.send_busy)) | 1030 | else: |
632 | 962 | if not self.send_busy: | 1031 | log.debug('({ip}) send_command(): Adding to normal queue'.format(ip=self.ip)) |
633 | 963 | log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip)) | 1032 | self.send_queue.append(out) |
634 | 1033 | if self.priority_queue or self.send_queue: | ||
635 | 1034 | # May be some initial connection setup so make sure we send data | ||
636 | 964 | self._send_command() | 1035 | self._send_command() |
637 | 965 | 1036 | ||
638 | 966 | @QtCore.pyqtSlot() | 1037 | @QtCore.pyqtSlot() |
639 | @@ -971,43 +1042,53 @@ | |||
640 | 971 | :param data: Immediate data to send | 1042 | :param data: Immediate data to send |
641 | 972 | :param utf8: Send as UTF-8 string otherwise send as ASCII string | 1043 | :param utf8: Send as UTF-8 string otherwise send as ASCII string |
642 | 973 | """ | 1044 | """ |
645 | 974 | log.debug('({ip}) _send_string()'.format(ip=self.ip)) | 1045 | # Funny looking data check, but it's a quick check for data=None |
646 | 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)) |
647 | 1047 | log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.ip, | ||
648 | 1048 | data=S_QSOCKET_STATE[self.state()])) | ||
649 | 976 | if self.state() != self.ConnectedState: | 1049 | if self.state() != self.ConnectedState: |
652 | 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)) |
651 | 978 | self.send_queue = [] | ||
653 | 979 | self.send_busy = False | 1051 | self.send_busy = False |
655 | 980 | return | 1052 | return self.disconnect_from_host() |
656 | 1053 | if data and data not in self.priority_queue: | ||
657 | 1054 | log.debug('({ip}) _send_command(): Priority packet - adding to priority queue'.format(ip=self.ip)) | ||
658 | 1055 | self.priority_queue.append(data) | ||
659 | 1056 | |||
660 | 981 | if self.send_busy: | 1057 | if self.send_busy: |
661 | 982 | # Still waiting for response from last command sent | 1058 | # Still waiting for response from last command sent |
662 | 1059 | log.debug('({ip}) _send_command(): Still busy, returning'.format(ip=self.ip)) | ||
663 | 1060 | log.debug('({ip}) _send_command(): Priority queue = {data}'.format(ip=self.ip, data=self.priority_queue)) | ||
664 | 1061 | log.debug('({ip}) _send_command(): Normal queue = {data}'.format(ip=self.ip, data=self.send_queue)) | ||
665 | 983 | return | 1062 | return |
669 | 984 | if data is not None: | 1063 | |
670 | 985 | out = data | 1064 | if len(self.priority_queue) != 0: |
671 | 986 | log.debug('({ip}) _send_string(data="{data}")'.format(ip=self.ip, data=out.strip())) | 1065 | out = self.priority_queue.pop(0) |
672 | 1066 | log.debug('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.ip)) | ||
673 | 987 | elif len(self.send_queue) != 0: | 1067 | elif len(self.send_queue) != 0: |
674 | 988 | out = self.send_queue.pop(0) | 1068 | out = self.send_queue.pop(0) |
676 | 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)) |
677 | 990 | else: | 1070 | else: |
678 | 991 | # No data to send | 1071 | # No data to send |
680 | 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)) |
681 | 993 | self.send_busy = False | 1073 | self.send_busy = False |
682 | 994 | return | 1074 | return |
683 | 995 | self.send_busy = True | 1075 | self.send_busy = True |
686 | 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())) |
685 | 997 | log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue)) | ||
687 | 998 | self.socket_timer.start() | 1077 | self.socket_timer.start() |
688 | 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'))) |
689 | 1000 | self.waitForBytesWritten(2000) # 2 seconds should be enough | 1079 | self.waitForBytesWritten(2000) # 2 seconds should be enough |
690 | 1001 | if sent == -1: | 1080 | if sent == -1: |
691 | 1002 | # Network error? | 1081 | # Network error? |
693 | 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)) |
694 | 1004 | self.change_status(E_NETWORK, | 1083 | self.change_status(E_NETWORK, |
695 | 1005 | translate('OpenLP.PJLink', 'Error while sending data to projector')) | 1084 | translate('OpenLP.PJLink', 'Error while sending data to projector')) |
696 | 1085 | self.disconnect_from_host() | ||
697 | 1006 | 1086 | ||
698 | 1007 | def connect_to_host(self): | 1087 | def connect_to_host(self): |
699 | 1008 | """ | 1088 | """ |
700 | 1009 | Initiate connection to projector. | 1089 | Initiate connection to projector. |
701 | 1010 | """ | 1090 | """ |
702 | 1091 | log.debug('{ip}) connect_to_host(): Starting connection'.format(ip=self.ip)) | ||
703 | 1011 | if self.state() == self.ConnectedState: | 1092 | if self.state() == self.ConnectedState: |
704 | 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)) |
705 | 1013 | return | 1094 | return |
706 | @@ -1023,22 +1104,19 @@ | |||
707 | 1023 | if abort: | 1104 | if abort: |
708 | 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)) |
709 | 1025 | else: | 1106 | else: |
712 | 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)) |
711 | 1027 | self.reset_information() | ||
713 | 1028 | self.disconnectFromHost() | 1108 | self.disconnectFromHost() |
714 | 1029 | try: | 1109 | try: |
715 | 1030 | self.readyRead.disconnect(self.get_socket) | 1110 | self.readyRead.disconnect(self.get_socket) |
716 | 1031 | except TypeError: | 1111 | except TypeError: |
717 | 1032 | pass | 1112 | pass |
718 | 1113 | log.debug('({ip}) disconnect_from_host() ' | ||
719 | 1114 | 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0])) | ||
720 | 1033 | if abort: | 1115 | if abort: |
721 | 1034 | self.change_status(E_NOT_CONNECTED) | 1116 | self.change_status(E_NOT_CONNECTED) |
722 | 1035 | else: | 1117 | else: |
727 | 1036 | log.debug('({ip}) disconnect_from_host() ' | 1118 | self.change_status(S_NOT_CONNECTED) |
724 | 1037 | 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0])) | ||
725 | 1038 | if self.status_connect != E_NOT_CONNECTED: | ||
726 | 1039 | self.change_status(S_NOT_CONNECTED) | ||
728 | 1040 | self.reset_information() | 1119 | self.reset_information() |
729 | 1041 | self.projectorUpdateIcons.emit() | ||
730 | 1042 | 1120 | ||
731 | 1043 | def get_av_mute_status(self): | 1121 | def get_av_mute_status(self): |
732 | 1044 | """ | 1122 | """ |
733 | 1045 | 1123 | ||
734 | === modified file 'tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py' | |||
735 | --- tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py 2017-11-24 19:08:23 +0000 | |||
736 | +++ tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py 2017-12-09 11:19:42 +0000 | |||
737 | @@ -23,12 +23,11 @@ | |||
738 | 23 | Package to test the openlp.core.projectors.pjlink base package. | 23 | Package to test the openlp.core.projectors.pjlink base package. |
739 | 24 | """ | 24 | """ |
740 | 25 | from unittest import TestCase | 25 | from unittest import TestCase |
741 | 26 | from unittest.mock import patch | ||
742 | 27 | 26 | ||
743 | 28 | from openlp.core.projectors.db import Projector | 27 | from openlp.core.projectors.db import Projector |
744 | 29 | from openlp.core.projectors.pjlink import PJLink | 28 | from openlp.core.projectors.pjlink import PJLink |
745 | 30 | 29 | ||
747 | 31 | from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA | 30 | from tests.resources.projector.data import TEST1_DATA |
748 | 32 | 31 | ||
749 | 33 | 32 | ||
750 | 34 | class TestPJLinkBugs(TestCase): | 33 | class TestPJLinkBugs(TestCase): |
751 | @@ -80,43 +79,17 @@ | |||
752 | 80 | """ | 79 | """ |
753 | 81 | Test bug 1593882 no pin and authenticated request exception | 80 | Test bug 1593882 no pin and authenticated request exception |
754 | 82 | """ | 81 | """ |
770 | 83 | # GIVEN: Test object and mocks | 82 | # Test now part of test_projector_pjlink_commands_02 |
771 | 84 | mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() | 83 | # Keeping here for bug reference |
772 | 85 | mock_timer = patch.object(self.pjlink_test, 'timer').start() | 84 | pass |
758 | 86 | mock_authentication = patch.object(self.pjlink_test, 'projectorAuthentication').start() | ||
759 | 87 | mock_ready_read = patch.object(self.pjlink_test, 'waitForReadyRead').start() | ||
760 | 88 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() | ||
761 | 89 | pjlink = self.pjlink_test | ||
762 | 90 | pjlink.pin = None | ||
763 | 91 | mock_ready_read.return_value = True | ||
764 | 92 | |||
765 | 93 | # WHEN: call with authentication request and pin not set | ||
766 | 94 | pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) | ||
767 | 95 | |||
768 | 96 | # THEN: 'No Authentication' signal should have been sent | ||
769 | 97 | mock_authentication.emit.assert_called_with(pjlink.ip) | ||
773 | 98 | 85 | ||
774 | 99 | def test_bug_1593883_pjlink_authentication(self): | 86 | def test_bug_1593883_pjlink_authentication(self): |
775 | 100 | """ | 87 | """ |
777 | 101 | Test bugfix 1593883 pjlink authentication | 88 | Test bugfix 1593883 pjlink authentication and ticket 92187 |
778 | 102 | """ | 89 | """ |
796 | 103 | # GIVEN: Test object and data | 90 | # Test now part of test_projector_pjlink_commands_02 |
797 | 104 | mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() | 91 | # Keeping here for bug reference |
798 | 105 | mock_timer = patch.object(self.pjlink_test, 'timer').start() | 92 | pass |
782 | 106 | mock_send_command = patch.object(self.pjlink_test, 'write').start() | ||
783 | 107 | mock_state = patch.object(self.pjlink_test, 'state').start() | ||
784 | 108 | mock_waitForReadyRead = patch.object(self.pjlink_test, 'waitForReadyRead').start() | ||
785 | 109 | pjlink = self.pjlink_test | ||
786 | 110 | pjlink.pin = TEST_PIN | ||
787 | 111 | mock_state.return_value = pjlink.ConnectedState | ||
788 | 112 | mock_waitForReadyRead.return_value = True | ||
789 | 113 | |||
790 | 114 | # WHEN: Athenticated connection is called | ||
791 | 115 | pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) | ||
792 | 116 | |||
793 | 117 | # THEN: send_command should have the proper authentication | ||
794 | 118 | self.assertEqual("{test}".format(test=mock_send_command.call_args), | ||
795 | 119 | "call(b'{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) | ||
799 | 120 | 93 | ||
800 | 121 | def test_bug_1734275_process_lamp_nonstandard_reply(self): | 94 | def test_bug_1734275_process_lamp_nonstandard_reply(self): |
801 | 122 | """ | 95 | """ |
802 | 123 | 96 | ||
803 | === modified file 'tests/functional/openlp_core/projectors/test_projector_pjlink_base.py' | |||
804 | --- tests/functional/openlp_core/projectors/test_projector_pjlink_base.py 2017-11-24 08:30:37 +0000 | |||
805 | +++ tests/functional/openlp_core/projectors/test_projector_pjlink_base.py 2017-12-09 11:19:42 +0000 | |||
806 | @@ -25,11 +25,11 @@ | |||
807 | 25 | from unittest import TestCase | 25 | from unittest import TestCase |
808 | 26 | from unittest.mock import call, patch, MagicMock | 26 | from unittest.mock import call, patch, MagicMock |
809 | 27 | 27 | ||
811 | 28 | from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED | 28 | from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED, S_QSOCKET_STATE |
812 | 29 | from openlp.core.projectors.db import Projector | 29 | from openlp.core.projectors.db import Projector |
813 | 30 | from openlp.core.projectors.pjlink import PJLink | 30 | from openlp.core.projectors.pjlink import PJLink |
814 | 31 | 31 | ||
816 | 32 | from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST1_DATA | 32 | from tests.resources.projector.data import TEST1_DATA |
817 | 33 | 33 | ||
818 | 34 | pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) | 34 | pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) |
819 | 35 | 35 | ||
820 | @@ -38,29 +38,17 @@ | |||
821 | 38 | """ | 38 | """ |
822 | 39 | Tests for the PJLink module | 39 | Tests for the PJLink module |
823 | 40 | """ | 40 | """ |
847 | 41 | @patch.object(pjlink_test, 'readyRead') | 41 | def setUp(self): |
848 | 42 | @patch.object(pjlink_test, 'send_command') | 42 | ''' |
849 | 43 | @patch.object(pjlink_test, 'waitForReadyRead') | 43 | TestPJLinkCommands part 2 initialization |
850 | 44 | @patch('openlp.core.common.qmd5_hash') | 44 | ''' |
851 | 45 | def test_authenticated_connection_call(self, | 45 | self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) |
852 | 46 | mock_qmd5_hash, | 46 | |
853 | 47 | mock_waitForReadyRead, | 47 | def tearDown(self): |
854 | 48 | mock_send_command, | 48 | ''' |
855 | 49 | mock_readyRead): | 49 | TestPJLinkCommands part 2 cleanups |
856 | 50 | """ | 50 | ''' |
857 | 51 | Ticket 92187: Fix for projector connect with PJLink authentication exception. | 51 | self.pjlink_test = None |
835 | 52 | """ | ||
836 | 53 | # GIVEN: Test object | ||
837 | 54 | pjlink = pjlink_test | ||
838 | 55 | |||
839 | 56 | # WHEN: Calling check_login with authentication request: | ||
840 | 57 | pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) | ||
841 | 58 | |||
842 | 59 | # THEN: Should have called qmd5_hash | ||
843 | 60 | self.assertTrue(mock_qmd5_hash.called_with(TEST_SALT, | ||
844 | 61 | "Connection request should have been called with TEST_SALT")) | ||
845 | 62 | self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN, | ||
846 | 63 | "Connection request should have been called with TEST_PIN")) | ||
858 | 64 | 52 | ||
859 | 65 | @patch.object(pjlink_test, 'change_status') | 53 | @patch.object(pjlink_test, 'change_status') |
860 | 66 | def test_status_change(self, mock_change_status): | 54 | def test_status_change(self, mock_change_status): |
861 | @@ -110,18 +98,18 @@ | |||
862 | 110 | # THEN: poll_loop should exit without calling any other method | 98 | # THEN: poll_loop should exit without calling any other method |
863 | 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') |
864 | 112 | 100 | ||
867 | 113 | @patch.object(pjlink_test, 'send_command') | 101 | def test_poll_loop_start(self): |
866 | 114 | def test_poll_loop_start(self, mock_send_command): | ||
868 | 115 | """ | 102 | """ |
869 | 116 | Test PJLink.poll_loop makes correct calls | 103 | Test PJLink.poll_loop makes correct calls |
870 | 117 | """ | 104 | """ |
878 | 118 | # GIVEN: test object and test data | 105 | # GIVEN: Mocks and test data |
879 | 119 | pjlink = pjlink_test | 106 | mock_state = patch.object(self.pjlink_test, 'state').start() |
880 | 120 | pjlink.state = MagicMock() | 107 | mock_state.return_value = S_QSOCKET_STATE['ConnectedState'] |
881 | 121 | pjlink.timer = MagicMock() | 108 | mock_timer = patch.object(self.pjlink_test, 'timer').start() |
882 | 122 | pjlink.timer.interval = MagicMock() | 109 | mock_timer.interval.return_value = 10 |
883 | 123 | pjlink.timer.setInterval = MagicMock() | 110 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
884 | 124 | pjlink.timer.start = MagicMock() | 111 | |
885 | 112 | pjlink = self.pjlink_test | ||
886 | 125 | pjlink.poll_time = 20 | 113 | pjlink.poll_time = 20 |
887 | 126 | pjlink.power = S_ON | 114 | pjlink.power = S_ON |
888 | 127 | pjlink.source_available = None | 115 | pjlink.source_available = None |
889 | @@ -130,19 +118,17 @@ | |||
890 | 130 | pjlink.model = None | 118 | pjlink.model = None |
891 | 131 | pjlink.pjlink_name = None | 119 | pjlink.pjlink_name = None |
892 | 132 | pjlink.ConnectedState = S_CONNECTED | 120 | pjlink.ConnectedState = S_CONNECTED |
893 | 133 | pjlink.timer.interval.return_value = 10 | ||
894 | 134 | pjlink.state.return_value = S_CONNECTED | ||
895 | 135 | call_list = [ | 121 | call_list = [ |
906 | 136 | call('POWR', queue=True), | 122 | call('POWR'), |
907 | 137 | call('ERST', queue=True), | 123 | call('ERST'), |
908 | 138 | call('LAMP', queue=True), | 124 | call('LAMP'), |
909 | 139 | call('AVMT', queue=True), | 125 | call('AVMT'), |
910 | 140 | call('INPT', queue=True), | 126 | call('INPT'), |
911 | 141 | call('INST', queue=True), | 127 | call('INST'), |
912 | 142 | call('INFO', queue=True), | 128 | call('INFO'), |
913 | 143 | call('INF1', queue=True), | 129 | call('INF1'), |
914 | 144 | call('INF2', queue=True), | 130 | call('INF2'), |
915 | 145 | call('NAME', queue=True), | 131 | call('NAME'), |
916 | 146 | ] | 132 | ] |
917 | 147 | 133 | ||
918 | 148 | # WHEN: PJLink.poll_loop is called | 134 | # WHEN: PJLink.poll_loop is called |
919 | @@ -150,8 +136,8 @@ | |||
920 | 150 | 136 | ||
921 | 151 | # THEN: proper calls were made to retrieve projector data | 137 | # THEN: proper calls were made to retrieve projector data |
922 | 152 | # First, call to update the timer with the next interval | 138 | # First, call to update the timer with the next interval |
924 | 153 | self.assertTrue(pjlink.timer.setInterval.called, 'Should have updated the timer') | 139 | self.assertTrue(mock_timer.setInterval.called) |
925 | 154 | # Next, should have called the timer to start | 140 | # Next, should have called the timer to start |
927 | 155 | self.assertTrue(pjlink.timer.start.called, 'Should have started the timer') | 141 | self.assertTrue(mock_timer.start.called, 'Should have started the timer') |
928 | 156 | # Finally, should have called send_command with a list of projetctor status checks | 142 | # Finally, should have called send_command with a list of projetctor status checks |
929 | 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') |
930 | 158 | 144 | ||
931 | === modified file 'tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py' | |||
932 | --- tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2017-11-16 23:53:53 +0000 | |||
933 | +++ tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2017-12-09 11:19:42 +0000 | |||
934 | @@ -46,6 +46,18 @@ | |||
935 | 46 | """ | 46 | """ |
936 | 47 | Tests for the PJLink module command routing | 47 | Tests for the PJLink module command routing |
937 | 48 | """ | 48 | """ |
938 | 49 | def setUp(self): | ||
939 | 50 | ''' | ||
940 | 51 | TestPJLinkCommands part 2 initialization | ||
941 | 52 | ''' | ||
942 | 53 | self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) | ||
943 | 54 | |||
944 | 55 | def tearDown(self): | ||
945 | 56 | ''' | ||
946 | 57 | TestPJLinkCommands part 2 cleanups | ||
947 | 58 | ''' | ||
948 | 59 | self.pjlink_test = None | ||
949 | 60 | |||
950 | 49 | @patch.object(openlp.core.projectors.pjlink, 'log') | 61 | @patch.object(openlp.core.projectors.pjlink, 'log') |
951 | 50 | def test_process_command_call_clss(self, mock_log): | 62 | def test_process_command_call_clss(self, mock_log): |
952 | 51 | """ | 63 | """ |
953 | @@ -163,21 +175,20 @@ | |||
954 | 163 | mock_change_status.assert_called_once_with(E_AUTHENTICATION) | 175 | mock_change_status.assert_called_once_with(E_AUTHENTICATION) |
955 | 164 | mock_log.error.assert_called_with(log_text) | 176 | mock_log.error.assert_called_with(log_text) |
956 | 165 | 177 | ||
959 | 166 | @patch.object(openlp.core.projectors.pjlink, 'log') | 178 | def test_process_command_future(self): |
958 | 167 | def test_process_command_future(self, mock_log): | ||
960 | 168 | """ | 179 | """ |
961 | 169 | Test command valid but no method to process yet | 180 | Test command valid but no method to process yet |
962 | 170 | """ | 181 | """ |
969 | 171 | # GIVEN: Test object | 182 | # GIVEN: Initial mocks and data |
970 | 172 | pjlink = pjlink_test | 183 | mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() |
971 | 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() |
972 | 174 | mock_log.reset_mock() | 185 | mock_functions.return_value = [] |
973 | 175 | # Remove a valid command so we can test valid command but not available yet | 186 | |
974 | 176 | pjlink.pjlink_functions.pop('CLSS') | 187 | pjlink = self.pjlink_test |
975 | 188 | log_text = '(111.111.111.111) Unable to process command="CLSS" (Future option?)' | ||
976 | 177 | 189 | ||
977 | 178 | # WHEN: process_command called with an unknown command | 190 | # WHEN: process_command called with an unknown command |
980 | 179 | with patch.object(pjlink, 'pjlink_functions') as mock_functions: | 191 | pjlink.process_command(cmd='CLSS', data='DONT CARE') |
979 | 180 | pjlink.process_command(cmd='CLSS', data='DONT CARE') | ||
981 | 181 | 192 | ||
982 | 182 | # THEN: Error should be logged and no command called | 193 | # THEN: Error should be logged and no command called |
983 | 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') |
984 | @@ -196,29 +207,26 @@ | |||
985 | 196 | 207 | ||
986 | 197 | # WHEN: process_command called with an unknown command | 208 | # WHEN: process_command called with an unknown command |
987 | 198 | pjlink.process_command(cmd='Unknown', data='Dont Care') | 209 | pjlink.process_command(cmd='Unknown', data='Dont Care') |
989 | 199 | log_text = "(127.0.0.1) Ignoring command='Unknown' (Invalid/Unknown)" | 210 | log_text = '(127.0.0.1) Ignoring command="Unknown" (Invalid/Unknown)' |
990 | 200 | 211 | ||
991 | 201 | # THEN: Error should be logged and no command called | 212 | # THEN: Error should be logged and no command called |
992 | 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') |
993 | 203 | mock_log.error.assert_called_once_with(log_text) | 214 | mock_log.error.assert_called_once_with(log_text) |
994 | 204 | 215 | ||
998 | 205 | @patch.object(pjlink_test, 'pjlink_functions') | 216 | def test_process_command_ok(self): |
996 | 206 | @patch.object(openlp.core.projectors.pjlink, 'log') | ||
997 | 207 | def test_process_command_ok(self, mock_log, mock_functions): | ||
999 | 208 | """ | 217 | """ |
1000 | 209 | Test command returned success | 218 | Test command returned success |
1001 | 210 | """ | 219 | """ |
1015 | 211 | # GIVEN: Test object | 220 | # GIVEN: Initial mocks and data |
1016 | 212 | pjlink = pjlink_test | 221 | mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() |
1017 | 213 | mock_functions.reset_mock() | 222 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() |
1018 | 214 | mock_log.reset_mock() | 223 | |
1019 | 215 | 224 | pjlink = self.pjlink_test | |
1020 | 216 | # WHEN: process_command called with an unknown command | 225 | log_text = '(111.111.111.111) Command "POWR" returned OK' |
1021 | 217 | pjlink.process_command(cmd='CLSS', data='OK') | 226 | |
1022 | 218 | log_text = '(127.0.0.1) Command "CLSS" returned OK' | 227 | # WHEN: process_command called with a command that returns OK |
1023 | 219 | 228 | pjlink.process_command(cmd='POWR', data='OK') | |
1024 | 220 | # THEN: Error should be logged and no command called | 229 | |
1025 | 221 | self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') | 230 | # THEN: Appropriate calls should have been made |
1013 | 222 | self.assertEqual(mock_log.debug.call_count, 2, 'log.debug() should have been called twice') | ||
1014 | 223 | # Although we called it twice, only the last log entry is saved | ||
1026 | 224 | mock_log.debug.assert_called_with(log_text) | 231 | mock_log.debug.assert_called_with(log_text) |
1027 | 232 | mock_send_command.assert_called_once_with(cmd='POWR') | ||
1028 | 225 | 233 | ||
1029 | === renamed file 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py' => 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py' | |||
1030 | --- tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py 2017-11-24 08:30:37 +0000 | |||
1031 | +++ tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py 2017-12-09 11:19:42 +0000 | |||
1032 | @@ -47,7 +47,7 @@ | |||
1033 | 47 | 47 | ||
1034 | 48 | class TestPJLinkCommands(TestCase): | 48 | class TestPJLinkCommands(TestCase): |
1035 | 49 | """ | 49 | """ |
1037 | 50 | Tests for the PJLink module | 50 | Tests for the PJLinkCommands class part 1 |
1038 | 51 | """ | 51 | """ |
1039 | 52 | @patch.object(pjlink_test, 'changeStatus') | 52 | @patch.object(pjlink_test, 'changeStatus') |
1040 | 53 | @patch.object(openlp.core.projectors.pjlink, 'log') | 53 | @patch.object(openlp.core.projectors.pjlink, 'log') |
1041 | @@ -580,7 +580,7 @@ | |||
1042 | 580 | 580 | ||
1043 | 581 | # WHEN: Process invalid reply | 581 | # WHEN: Process invalid reply |
1044 | 582 | pjlink.process_clss('Z') | 582 | pjlink.process_clss('Z') |
1046 | 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"' |
1047 | 584 | 584 | ||
1048 | 585 | # THEN: Projector class should be set with default value | 585 | # THEN: Projector class should be set with default value |
1049 | 586 | self.assertEqual(pjlink.pjlink_class, '1', | 586 | self.assertEqual(pjlink.pjlink_class, '1', |
1050 | @@ -597,7 +597,7 @@ | |||
1051 | 597 | 597 | ||
1052 | 598 | # WHEN: Process invalid reply | 598 | # WHEN: Process invalid reply |
1053 | 599 | pjlink.process_clss('Invalid') | 599 | pjlink.process_clss('Invalid') |
1055 | 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"' |
1056 | 601 | 601 | ||
1057 | 602 | # THEN: Projector class should be set with default value | 602 | # THEN: Projector class should be set with default value |
1058 | 603 | self.assertEqual(pjlink.pjlink_class, '1', | 603 | self.assertEqual(pjlink.pjlink_class, '1', |
1059 | @@ -627,7 +627,7 @@ | |||
1060 | 627 | # GIVEN: Test object | 627 | # GIVEN: Test object |
1061 | 628 | pjlink = pjlink_test | 628 | pjlink = pjlink_test |
1062 | 629 | pjlink.projector_errors = None | 629 | pjlink.projector_errors = None |
1064 | 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' |
1065 | 631 | 631 | ||
1066 | 632 | # WHEN: process_erst called with invalid data (too many values | 632 | # WHEN: process_erst called with invalid data (too many values |
1067 | 633 | pjlink.process_erst('11111111') | 633 | pjlink.process_erst('11111111') |
1068 | @@ -645,7 +645,7 @@ | |||
1069 | 645 | # GIVEN: Test object | 645 | # GIVEN: Test object |
1070 | 646 | pjlink = pjlink_test | 646 | pjlink = pjlink_test |
1071 | 647 | pjlink.projector_errors = None | 647 | pjlink.projector_errors = None |
1073 | 648 | log_text = "(127.0.0.1) Invalid error status response '1111Z1'" | 648 | log_text = '(127.0.0.1) Invalid error status response "1111Z1"' |
1074 | 649 | 649 | ||
1075 | 650 | # WHEN: process_erst called with invalid data (too many values | 650 | # WHEN: process_erst called with invalid data (too many values |
1076 | 651 | pjlink.process_erst('1111Z1') | 651 | pjlink.process_erst('1111Z1') |
1077 | @@ -671,8 +671,8 @@ | |||
1078 | 671 | # THEN: PJLink instance errors should match chk_value | 671 | # THEN: PJLink instance errors should match chk_value |
1079 | 672 | for chk in pjlink.projector_errors: | 672 | for chk in pjlink.projector_errors: |
1080 | 673 | self.assertEqual(pjlink.projector_errors[chk], chk_string, | 673 | self.assertEqual(pjlink.projector_errors[chk], chk_string, |
1083 | 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, |
1084 | 675 | err=chk_string)) | 675 | err=chk_string)) |
1085 | 676 | 676 | ||
1086 | 677 | def test_projector_process_erst_all_error(self): | 677 | def test_projector_process_erst_all_error(self): |
1087 | 678 | """ | 678 | """ |
1088 | @@ -690,8 +690,8 @@ | |||
1089 | 690 | # THEN: PJLink instance errors should match chk_value | 690 | # THEN: PJLink instance errors should match chk_value |
1090 | 691 | for chk in pjlink.projector_errors: | 691 | for chk in pjlink.projector_errors: |
1091 | 692 | self.assertEqual(pjlink.projector_errors[chk], chk_string, | 692 | self.assertEqual(pjlink.projector_errors[chk], chk_string, |
1094 | 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, |
1095 | 694 | err=chk_string)) | 694 | err=chk_string)) |
1096 | 695 | 695 | ||
1097 | 696 | def test_projector_process_erst_warn_cover_only(self): | 696 | def test_projector_process_erst_warn_cover_only(self): |
1098 | 697 | """ | 697 | """ |
1099 | @@ -744,9 +744,9 @@ | |||
1100 | 744 | pjlink = pjlink_test | 744 | pjlink = pjlink_test |
1101 | 745 | pjlink.source_available = [] | 745 | pjlink.source_available = [] |
1102 | 746 | test_data = '21 10 30 31 11 20' | 746 | test_data = '21 10 30 31 11 20' |
1106 | 747 | test_saved = ['10', '11', '20', '21', '30', '31'] | 747 | test_saved = ["10", "11", "20", "21", "30", "31"] |
1107 | 748 | log_data = '(127.0.0.1) Setting projector sources_available to ' \ | 748 | log_data = "(127.0.0.1) Setting projector sources_available to " \ |
1108 | 749 | '"[\'10\', \'11\', \'20\', \'21\', \'30\', \'31\']"' | 749 | "\"['10', '11', '20', '21', '30', '31']\"" |
1109 | 750 | mock_UpdateIcons.reset_mock() | 750 | mock_UpdateIcons.reset_mock() |
1110 | 751 | mock_log.reset_mock() | 751 | mock_log.reset_mock() |
1111 | 752 | 752 | ||
1112 | @@ -1021,7 +1021,7 @@ | |||
1113 | 1021 | pjlink.sw_version = None | 1021 | pjlink.sw_version = None |
1114 | 1022 | pjlink.sw_version_received = None | 1022 | pjlink.sw_version_received = None |
1115 | 1023 | test_data = 'Test 1 Subtest 1' | 1023 | test_data = 'Test 1 Subtest 1' |
1117 | 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"' |
1118 | 1025 | mock_log.reset_mock() | 1025 | mock_log.reset_mock() |
1119 | 1026 | 1026 | ||
1120 | 1027 | # WHEN: process_sver called with invalid data | 1027 | # WHEN: process_sver called with invalid data |
1121 | 1028 | 1028 | ||
1122 | === added file 'tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py' | |||
1123 | --- tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py 1970-01-01 00:00:00 +0000 | |||
1124 | +++ tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py 2017-12-09 11:19:42 +0000 | |||
1125 | @@ -0,0 +1,198 @@ | |||
1126 | 1 | # -*- coding: utf-8 -*- | ||
1127 | 2 | # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 | ||
1128 | 3 | |||
1129 | 4 | ############################################################################### | ||
1130 | 5 | # OpenLP - Open Source Lyrics Projection # | ||
1131 | 6 | # --------------------------------------------------------------------------- # | ||
1132 | 7 | # Copyright (c) 2008-2015 OpenLP Developers # | ||
1133 | 8 | # --------------------------------------------------------------------------- # | ||
1134 | 9 | # This program is free software; you can redistribute it and/or modify it # | ||
1135 | 10 | # under the terms of the GNU General Public License as published by the Free # | ||
1136 | 11 | # Software Foundation; version 2 of the License. # | ||
1137 | 12 | # # | ||
1138 | 13 | # This program is distributed in the hope that it will be useful, but WITHOUT # | ||
1139 | 14 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # | ||
1140 | 15 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # | ||
1141 | 16 | # more details. # | ||
1142 | 17 | # # | ||
1143 | 18 | # You should have received a copy of the GNU General Public License along # | ||
1144 | 19 | # with this program; if not, write to the Free Software Foundation, Inc., 59 # | ||
1145 | 20 | # Temple Place, Suite 330, Boston, MA 02111-1307 USA # | ||
1146 | 21 | ############################################################################### | ||
1147 | 22 | """ | ||
1148 | 23 | Package to test the openlp.core.projectors.pjlink commands package. | ||
1149 | 24 | """ | ||
1150 | 25 | from unittest import TestCase | ||
1151 | 26 | from unittest.mock import patch, call | ||
1152 | 27 | |||
1153 | 28 | import openlp.core.projectors.pjlink | ||
1154 | 29 | from openlp.core.projectors.constants import S_CONNECTED | ||
1155 | 30 | from openlp.core.projectors.db import Projector | ||
1156 | 31 | from openlp.core.projectors.pjlink import PJLink | ||
1157 | 32 | |||
1158 | 33 | from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT, TEST1_DATA | ||
1159 | 34 | |||
1160 | 35 | |||
1161 | 36 | class TestPJLinkCommands(TestCase): | ||
1162 | 37 | """ | ||
1163 | 38 | Tests for the PJLinkCommands class part 2 | ||
1164 | 39 | """ | ||
1165 | 40 | def setUp(self): | ||
1166 | 41 | ''' | ||
1167 | 42 | TestPJLinkCommands part 2 initialization | ||
1168 | 43 | ''' | ||
1169 | 44 | self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) | ||
1170 | 45 | |||
1171 | 46 | def tearDown(self): | ||
1172 | 47 | ''' | ||
1173 | 48 | TestPJLinkCommands part 2 cleanups | ||
1174 | 49 | ''' | ||
1175 | 50 | self.pjlink_test = None | ||
1176 | 51 | |||
1177 | 52 | def test_process_pjlink_normal(self): | ||
1178 | 53 | """ | ||
1179 | 54 | Test initial connection prompt with no authentication | ||
1180 | 55 | """ | ||
1181 | 56 | # GIVEN: Initial mocks and data | ||
1182 | 57 | mock_log = patch.object(openlp.core.projectors.pjlink, "log").start() | ||
1183 | 58 | mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() | ||
1184 | 59 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() | ||
1185 | 60 | mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start() | ||
1186 | 61 | mock_change_status = patch.object(self.pjlink_test, 'change_status').start() | ||
1187 | 62 | pjlink = self.pjlink_test | ||
1188 | 63 | pjlink.pin = None | ||
1189 | 64 | log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ] | ||
1190 | 65 | |||
1191 | 66 | # WHEN: process_pjlink called with no authentication required | ||
1192 | 67 | pjlink.process_pjlink(data="0") | ||
1193 | 68 | |||
1194 | 69 | # THEN: proper processing should have occured | ||
1195 | 70 | mock_log.debug.has_calls(log_check) | ||
1196 | 71 | mock_disconnect_from_host.assert_not_called() | ||
1197 | 72 | self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once') | ||
1198 | 73 | mock_change_status.assert_called_once_with(S_CONNECTED) | ||
1199 | 74 | mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=None) | ||
1200 | 75 | |||
1201 | 76 | def test_process_pjlink_authenticate(self): | ||
1202 | 77 | """ | ||
1203 | 78 | Test initial connection prompt with authentication | ||
1204 | 79 | """ | ||
1205 | 80 | # GIVEN: Initial mocks and data | ||
1206 | 81 | mock_log = patch.object(openlp.core.projectors.pjlink, "log").start() | ||
1207 | 82 | mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() | ||
1208 | 83 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() | ||
1209 | 84 | mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start() | ||
1210 | 85 | mock_change_status = patch.object(self.pjlink_test, 'change_status').start() | ||
1211 | 86 | pjlink = self.pjlink_test | ||
1212 | 87 | pjlink.pin = TEST_PIN | ||
1213 | 88 | log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ] | ||
1214 | 89 | |||
1215 | 90 | # WHEN: process_pjlink called with no authentication required | ||
1216 | 91 | pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT)) | ||
1217 | 92 | |||
1218 | 93 | # THEN: proper processing should have occured | ||
1219 | 94 | mock_log.debug.has_calls(log_check) | ||
1220 | 95 | mock_disconnect_from_host.assert_not_called() | ||
1221 | 96 | self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once') | ||
1222 | 97 | mock_change_status.assert_called_once_with(S_CONNECTED) | ||
1223 | 98 | mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=TEST_HASH) | ||
1224 | 99 | |||
1225 | 100 | def test_process_pjlink_normal_pin_set_error(self): | ||
1226 | 101 | """ | ||
1227 | 102 | Test process_pjlinnk called with no authentication but pin is set | ||
1228 | 103 | """ | ||
1229 | 104 | # GIVEN: Initial mocks and data | ||
1230 | 105 | # GIVEN: Initial mocks and data | ||
1231 | 106 | mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() | ||
1232 | 107 | mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() | ||
1233 | 108 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() | ||
1234 | 109 | pjlink = self.pjlink_test | ||
1235 | 110 | pjlink.pin = TEST_PIN | ||
1236 | 111 | log_check = [call('(111.111.111.111) Normal connection but PIN set - aborting'), ] | ||
1237 | 112 | |||
1238 | 113 | # WHEN: process_pjlink called with invalid authentication scheme | ||
1239 | 114 | pjlink.process_pjlink(data='0') | ||
1240 | 115 | |||
1241 | 116 | # THEN: Proper calls should be made | ||
1242 | 117 | mock_log.error.assert_has_calls(log_check) | ||
1243 | 118 | self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') | ||
1244 | 119 | mock_send_command.assert_not_called() | ||
1245 | 120 | |||
1246 | 121 | def test_process_pjlink_normal_with_salt_error(self): | ||
1247 | 122 | """ | ||
1248 | 123 | Test process_pjlinnk called with no authentication but pin is set | ||
1249 | 124 | """ | ||
1250 | 125 | # GIVEN: Initial mocks and data | ||
1251 | 126 | # GIVEN: Initial mocks and data | ||
1252 | 127 | mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() | ||
1253 | 128 | mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() | ||
1254 | 129 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() | ||
1255 | 130 | pjlink = self.pjlink_test | ||
1256 | 131 | pjlink.pin = TEST_PIN | ||
1257 | 132 | log_check = [call('(111.111.111.111) Normal connection with extra information - aborting'), ] | ||
1258 | 133 | |||
1259 | 134 | # WHEN: process_pjlink called with invalid authentication scheme | ||
1260 | 135 | pjlink.process_pjlink(data='0 {salt}'.format(salt=TEST_SALT)) | ||
1261 | 136 | |||
1262 | 137 | # THEN: Proper calls should be made | ||
1263 | 138 | mock_log.error.assert_has_calls(log_check) | ||
1264 | 139 | self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') | ||
1265 | 140 | mock_send_command.assert_not_called() | ||
1266 | 141 | |||
1267 | 142 | def test_process_pjlink_invalid_authentication_scheme_length_error(self): | ||
1268 | 143 | """ | ||
1269 | 144 | Test initial connection prompt with authentication scheme longer than 1 character | ||
1270 | 145 | """ | ||
1271 | 146 | # GIVEN: Initial mocks and data | ||
1272 | 147 | mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() | ||
1273 | 148 | mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() | ||
1274 | 149 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() | ||
1275 | 150 | pjlink = self.pjlink_test | ||
1276 | 151 | log_check = [call('(111.111.111.111) Invalid initial authentication scheme - aborting'), ] | ||
1277 | 152 | |||
1278 | 153 | # WHEN: process_pjlink called with invalid authentication scheme | ||
1279 | 154 | pjlink.process_pjlink(data='01') | ||
1280 | 155 | |||
1281 | 156 | # THEN: socket should be closed and invalid data logged | ||
1282 | 157 | mock_log.error.assert_has_calls(log_check) | ||
1283 | 158 | self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') | ||
1284 | 159 | mock_send_command.assert_not_called() | ||
1285 | 160 | |||
1286 | 161 | def test_process_pjlink_invalid_authentication_data_length_error(self): | ||
1287 | 162 | """ | ||
1288 | 163 | Test initial connection prompt with authentication no salt | ||
1289 | 164 | """ | ||
1290 | 165 | # GIVEN: Initial mocks and data | ||
1291 | 166 | mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() | ||
1292 | 167 | mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() | ||
1293 | 168 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() | ||
1294 | 169 | log_check = [call('(111.111.111.111) Authenticated connection but not enough info - aborting'), ] | ||
1295 | 170 | pjlink = self.pjlink_test | ||
1296 | 171 | |||
1297 | 172 | # WHEN: process_pjlink called with no salt | ||
1298 | 173 | pjlink.process_pjlink(data='1') | ||
1299 | 174 | |||
1300 | 175 | # THEN: socket should be closed and invalid data logged | ||
1301 | 176 | mock_log.error.assert_has_calls(log_check) | ||
1302 | 177 | self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') | ||
1303 | 178 | mock_send_command.assert_not_called() | ||
1304 | 179 | |||
1305 | 180 | def test_process_pjlink_authenticate_pin_not_set_error(self): | ||
1306 | 181 | """ | ||
1307 | 182 | Test process_pjlink authentication but pin not set | ||
1308 | 183 | """ | ||
1309 | 184 | # GIVEN: Initial mocks and data | ||
1310 | 185 | mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() | ||
1311 | 186 | mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() | ||
1312 | 187 | mock_send_command = patch.object(self.pjlink_test, 'send_command').start() | ||
1313 | 188 | log_check = [call('(111.111.111.111) Authenticate connection but no PIN - aborting'), ] | ||
1314 | 189 | pjlink = self.pjlink_test | ||
1315 | 190 | pjlink.pin = None | ||
1316 | 191 | |||
1317 | 192 | # WHEN: process_pjlink called with no salt | ||
1318 | 193 | pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT)) | ||
1319 | 194 | |||
1320 | 195 | # THEN: socket should be closed and invalid data logged | ||
1321 | 196 | mock_log.error.assert_has_calls(log_check) | ||
1322 | 197 | self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') | ||
1323 | 198 | mock_send_command.assert_not_called() |
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.