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

Proposed by Ken Roberts
Status: Superseded
Proposed branch: lp:~alisonken1/openlp/pjlink2-r
Merge into: lp:openlp
Diff against target: 840 lines (+254/-128)
8 files modified
openlp/core/common/settings.py (+1/-0)
openlp/core/projectors/manager.py (+25/-2)
openlp/core/projectors/pjlink.py (+88/-82)
openlp/core/projectors/tab.py (+8/-0)
tests/openlp_core/projectors/test_projector_pjlink_base_01.py (+2/-12)
tests/openlp_core/projectors/test_projector_pjlink_base_02.py (+101/-0)
tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py (+14/-4)
tests/openlp_core/projectors/test_projector_pjlink_udp.py (+15/-28)
To merge this branch: bzr merge lp:~alisonken1/openlp/pjlink2-r
Reviewer Review Type Date Requested Status
Phill Needs Fixing
Tim Bentley Needs Fixing
Review via email: mp+344795@code.launchpad.net

This proposal has been superseded by a proposal from 2018-04-28.

Commit message

PJLink2 update R

Description of the change

--------------------------------------------------------------------------------
lp:~alisonken1/openlp/pjlink2-r (revision 2819)
https://ci.openlp.io/job/Branch-01-Pull/2509/ [SUCCESS]
https://ci.openlp.io/job/Branch-02a-Linux-Tests/2410/ [SUCCESS]
https://ci.openlp.io/job/Branch-02b-macOS-Tests/196/ [FAILURE]
https://ci.openlp.io/job/Branch-03a-Build-Source/106/ [SUCCESS]
https://ci.openlp.io/job/Branch-03b-Build-macOS/99/ [SUCCESS]
https://ci.openlp.io/job/Branch-04a-Code-Analysis/1568/ [SUCCESS]
https://ci.openlp.io/job/Branch-04b-Test-Coverage/1381/ [SUCCESS]
https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/310/ [FAILURE]
--------------------------------------------------------------------------------

- Convert UDP packet to send pyqtSignal and let individual projectors decide who it's for
- Remove unneccessary *args **kwargs from commands
- Remove projector_list and projector block search since switching to signals
- pass on ACKN, and SRCH commands until I think about it some more
- Cleanup extraneous args on process_* commands since switching to signals
- Add UDP socket listener(s) for PJLink2 UDP options
- Added process_lkup method code
- Add projector configure option to connect when LKUP packet received
- Fix _send_queue to ignore call if no data to send
- Rename tests/openlp_core/projectors/test_projector_pjlink_base.py to test_projector_pjlink_base_01.py
- Added tests/openlp_core/projectors/test_projector_pjlink_base_02.py
- Added test for PJLink._send_command
- Removed unused test_projector_pjlink_base_02.pjlink_test from module
- Cleanup tests/openlp_core/projectors/test_projector_pjlink_base_01 imports
- Fix test_projector_pjlink_cmd_routing.test_get_data_unknown_command

To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) wrote :

A question in the code

In future can you put the test results at the bottom of the request as it makes the history easier to read.

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

Ran out of time to complete a review, but a few in lines to start.

review: Needs Fixing
Revision history for this message
Ken Roberts (alisonken1) wrote :
Download full text (7.6 KiB)

On Sat, Apr 28, 2018 at 12:15 AM, Phill <email address hidden> wrote:
> Review: Needs Fixing
>
> Ran out of time to complete a review, but a few in lines to start.
>
> Diff comments:
>
>>
>> === modified file 'openlp/core/projectors/manager.py'
>> --- openlp/core/projectors/manager.py 2018-04-20 06:04:43 +0000
>> +++ openlp/core/projectors/manager.py 2018-04-28 02:41:38 +0000
>> if read_size < 0:
>> log.warning('(UDP) No data (-1)')
>> return
>> - if read_size < 1:
>> + elif read_size < 1:
>
> given the < 0 condition above, this condition will only be True when read_size == 0, which to me is a bit readable / obvious at a quick glance.

-1 indicates a socket problem - which is distinct from no data (0 or
empty packet)

>> @@ -654,10 +651,10 @@
>> :param host: IP address of sending host
>> :param port: Port received on
>> """
>> - log.warning('(UDP) SRCH packet received from {host} - ignoring'.format(host=host))
>> + log.warning("({ip}) I don't do SRCH packets - ignoring".format(ip=self.ip))
>
> Why this change? the original is more understandable to me!

This method is only called from within a projector instance - not the
UDP monitor.
However, just noticed it's using the wrong option for {ip} - should be
ip=self.entry.name

>
>> return
>>
>> - def process_sver(self, data, *args, **kwargs):
>> + def process_sver(self, data):
>> """
>> Software version of projector
>> """
>> @@ -1181,7 +1187,7 @@
>> try:
>> self.readyRead.disconnect(self.get_socket)
>> except TypeError:
>> - pass
>> + log.debug('({ip}) disconnect_from_host(): TypeError detected'.format(ip=self.entry.name))
>
> You're just disconnecting a slot here right? Do we need to log that?

Since this is in the projector instance, I would like to know if
there's a type issue - if so, then I would like to see it in the log.

>
>> log.debug('({ip}) disconnect_from_host() '
>> 'Current status {data}'.format(ip=self.entry.name, data=self._get_status(self.status_connect)[0]))
>> if abort:
>
>
> --
> https://code.launchpad.net/~alisonken1/openlp/pjlink2-r/+merge/344795
> You are the owner of lp:~alisonken1/openlp/pjlink2-r.

--
- Ken
Registered Linux user 296561
Slackin' since 1993
Slackware Linux (http://www.slackware.com)
OpenLP - Church Projection Software
Empower Your Church http://openlp.org

On Sat, Apr 28, 2018 at 12:15 AM, Phill <email address hidden> wrote:
> Review: Needs Fixing
>
> Ran out of time to complete a review, but a few in lines to start.
>
> Diff comments:
>
>>
>> === modified file 'openlp/core/projectors/manager.py'
>> --- openlp/core/projectors/manager.py 2018-04-20 06:04:43 +0000
>> +++ openlp/core/projectors/manager.py 2018-04-28 02:41:38 +0000
>> @@ -513,6 +519,18 @@
>> projector.socket_timer.timeout.disconnect(projector.link.socket_abort)
>> except (AttributeError, TypeError):
>> pass
>> + # Disconnect signals from projector being deleted
>> + try:
>> + self.pjlink_udp.data_received.di...

Read more...

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

See inlines please.

Revision history for this message
Phill (phill-ridout) :
review: Needs Fixing
lp:~alisonken1/openlp/pjlink2-r updated
2819. By Ken Roberts

PJLink2 update R

2820. By Ken Roberts

Fix settings calls

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/common/settings.py'
2--- openlp/core/common/settings.py 2018-02-02 21:33:41 +0000
3+++ openlp/core/common/settings.py 2018-04-28 07:12:35 +0000
4@@ -199,6 +199,7 @@
5 'projector/db database': '',
6 'projector/enable': True,
7 'projector/connect on start': False,
8+ 'projector/connect when LKUP received': True, # PJLink v2: Projector sends LKUP command after it powers up
9 'projector/last directory import': None,
10 'projector/last directory export': None,
11 'projector/poll time': 20, # PJLink timeout is 30 seconds
12
13=== modified file 'openlp/core/projectors/manager.py'
14--- openlp/core/projectors/manager.py 2018-04-20 06:04:43 +0000
15+++ openlp/core/projectors/manager.py 2018-04-28 07:12:35 +0000
16@@ -37,7 +37,7 @@
17 from openlp.core.projectors import DialogSourceStyle
18 from openlp.core.projectors.constants import E_AUTHENTICATION, E_ERROR, E_NETWORK, E_NOT_CONNECTED, \
19 E_UNKNOWN_SOCKET_ERROR, S_CONNECTED, S_CONNECTING, S_COOLDOWN, S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, \
20- S_STANDBY, S_WARMUP, STATUS_CODE, STATUS_MSG, QSOCKET_STATE
21+ S_STANDBY, S_WARMUP, PJLINK_PORT, STATUS_CODE, STATUS_MSG, QSOCKET_STATE
22
23 from openlp.core.projectors.db import ProjectorDB
24 from openlp.core.projectors.editform import ProjectorEditForm
25@@ -294,6 +294,9 @@
26 self.projectordb = projectordb
27 self.projector_list = []
28 self.source_select_form = None
29+ # Dictionary of PJLinkUDP objects to listen for UDP broadcasts from PJLink 2+ projectors.
30+ # Key is port number that projectors use
31+ self.pjlink_udp = {}
32
33 def bootstrap_initialise(self):
34 """
35@@ -307,12 +310,15 @@
36 else:
37 log.debug('Using existing ProjectorDB() instance')
38 self.get_settings()
39- self.pjlink_udp = PJLinkUDP(self.projector_list)
40
41 def bootstrap_post_set_up(self):
42 """
43 Post-initialize setups.
44 """
45+ # Default PJLink port UDP socket
46+ log.debug('Creating PJLinkUDP listener for default port {port}'.format(port=PJLINK_PORT))
47+ self.pjlink_udp = {PJLINK_PORT: PJLinkUDP(port=PJLINK_PORT)}
48+ self.pjlink_udp[PJLINK_PORT].bind(PJLINK_PORT)
49 # Set 1.5 second delay before loading all projectors
50 if self.autostart:
51 log.debug('Delaying 1.5 seconds before loading all projectors')
52@@ -513,6 +519,14 @@
53 projector.socket_timer.timeout.disconnect(projector.link.socket_abort)
54 except (AttributeError, TypeError):
55 pass
56+ # Disconnect signals from projector being deleted
57+ if self.pjlink_udp[projector.port]:
58+ try:
59+ self.pjlink_udp[projector.port].data_received.disconnect(projector.get_buffer)
60+ except (AttributeError, TypeError):
61+ pass
62+
63+ # Rebuild projector list
64 new_list = []
65 for item in self.projector_list:
66 if item.link.db_item.id == projector.link.db_item.id:
67@@ -726,6 +740,15 @@
68 item.link.projectorAuthentication.connect(self.authentication_error)
69 item.link.projectorNoAuthentication.connect(self.no_authentication_error)
70 item.link.projectorUpdateIcons.connect(self.update_icons)
71+ # Connect UDP signal to projector instances with same port
72+ if item.link.port not in self.pjlink_udp:
73+ log.debug('Adding new PJLinkUDP listener fo port {port}'.format(port=item.link.port))
74+ self.pjlink_udp[item.link.port] = PJLinkUDP(port=item.link.port)
75+ self.pjlink_udp[item.link.port].bind(item.link.port)
76+ log.debug('Connecting PJLinkUDP port {port} signal to "{item}"'.format(port=item.link.port,
77+ item=item.link.name))
78+ self.pjlink_udp[item.link.port].data_received.connect(item.link.get_buffer)
79+
80 self.projector_list.append(item)
81 if start:
82 item.link.connect_to_host()
83
84=== modified file 'openlp/core/projectors/pjlink.py'
85--- openlp/core/projectors/pjlink.py 2018-04-20 06:04:43 +0000
86+++ openlp/core/projectors/pjlink.py 2018-04-28 07:12:35 +0000
87@@ -54,6 +54,7 @@
88
89 from openlp.core.common import qmd5_hash
90 from openlp.core.common.i18n import translate
91+from openlp.core.common.settings import Settings
92 from openlp.core.projectors.constants import CONNECTION_ERRORS, PJLINK_CLASS, PJLINK_DEFAULT_CODES, PJLINK_ERRORS, \
93 PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PREFIX, PJLINK_PORT, PJLINK_POWR_STATUS, \
94 PJLINK_SUFFIX, PJLINK_VALID_CMD, PROJECTOR_STATE, STATUS_CODE, STATUS_MSG, QSOCKET_STATE, \
95@@ -78,25 +79,27 @@
96 """
97 Socket service for PJLink UDP socket.
98 """
99- def __init__(self, projector_list, port=PJLINK_PORT):
100+
101+ data_received = QtCore.pyqtSignal(QtNetwork.QHostAddress, int, str, name='udp_data') # host, port, data
102+
103+ def __init__(self, port=PJLINK_PORT):
104 """
105 Socket services for PJLink UDP packets.
106
107 Since all UDP packets from any projector will come into the same
108 port, process UDP packets here then route to the appropriate
109 projector instance as needed.
110+
111+ :param port: UDP port to listen on
112 """
113- # Keep track of currently defined projectors so we can route
114- # inbound packets to the correct instance
115 super().__init__()
116- self.projector_list = projector_list
117 self.port = port
118 # Local defines
119 self.search_active = False
120 self.search_time = 30000 # 30 seconds for allowed time
121 self.search_timer = QtCore.QTimer()
122 self.readyRead.connect(self.get_datagram)
123- log.debug('(UDP) PJLinkUDP() Initialized')
124+ log.debug('(UDP) PJLinkUDP() Initialized for port {port}'.format(port=self.port))
125
126 @QtCore.pyqtSlot()
127 def get_datagram(self):
128@@ -108,22 +111,20 @@
129 if read_size < 0:
130 log.warning('(UDP) No data (-1)')
131 return
132- if read_size < 1:
133+ elif read_size < 1:
134 log.warning('(UDP) get_datagram() called when pending data size is 0')
135 return
136- data, peer_address, peer_port = self.readDatagram(self.pendingDatagramSize())
137+ elif read_size > PJLINK_MAX_PACKET:
138+ log.warning('(UDP) UDP Packet too large ({size} bytes)- ignoring'.format(size=read_size))
139+ return
140+ data_in, peer_host, peer_port = self.readDatagram(read_size)
141+ data = data_in.decode('utf-8') if isinstance(data_in, bytes) else data_in
142 log.debug('(UDP) {size} bytes received from {adx} on port {port}'.format(size=len(data),
143- adx=peer_address,
144- port=peer_port))
145+ adx=peer_host.toString(),
146+ port=self.port))
147 log.debug('(UDP) packet "{data}"'.format(data=data))
148- # Send to appropriate instance to process packet
149- log.debug('(UDP) Checking projector list for ip {host} to process'.format(host=peer_address))
150- for projector in self.projector_list:
151- if peer_address == projector.ip:
152- # Dispatch packet to appropriate remote instance
153- log.debug('(UDP) Dispatching packet to {host}'.format(host=projector.entry.name))
154- return projector.get_data(buff=data, ip=peer_address, host=peer_address, port=peer_port)
155- log.warning('(UDP) Could not find projector with ip {ip} to process packet'.format(ip=peer_address))
156+ log.debug('(UDP) Sending data_received signal to projectors')
157+ self.data_received.emit(peer_host, self.localPort(), data)
158 return
159
160 def search_start(self):
161@@ -131,7 +132,6 @@
162 Start search for projectors on local network
163 """
164 self.search_active = True
165- self.ackn_list = {}
166 # TODO: Send SRCH packet here
167 self.search_timer.singleShot(self.search_time, self.search_stop)
168
169@@ -240,7 +240,7 @@
170 for cmd in self.pjlink_functions:
171 self.pjlink_functions[cmd]["version"] = PJLINK_VALID_CMD[cmd]['default']
172
173- def process_command(self, cmd, data, *args, **kwargs):
174+ def process_command(self, cmd, data):
175 """
176 Verifies any return error code. Calls the appropriate command handler.
177
178@@ -272,25 +272,18 @@
179 return self.change_status(status=E_AUTHENTICATION)
180 # Command checks already passed
181 log.debug('({ip}) Calling function for {cmd}'.format(ip=self.entry.name, cmd=cmd))
182- self.pjlink_functions[cmd]["method"](data=data, *args, **kwargs)
183+ self.pjlink_functions[cmd]["method"](data=data)
184
185- def process_ackn(self, data, host, port):
186+ def process_ackn(self, data):
187 """
188 Process the ACKN command.
189
190 :param data: Data in packet
191- :param host: IP address of sending host
192- :param port: Port received on
193 """
194- log.debug('({ip}) Processing ACKN packet'.format(ip=self.entry.name))
195- if host not in self.ackn_list:
196- log.debug('({ip}) Adding {host} to ACKN list'.format(ip=self.entry.name, host=host))
197- self.ackn_list[host] = {'data': data,
198- 'port': port}
199- else:
200- log.warning('({ip}) Host {host} already replied - ignoring'.format(ip=self.entry.name, host=host))
201+ # TODO: Have to rethink this one
202+ pass
203
204- def process_avmt(self, data, *args, **kwargs):
205+ def process_avmt(self, data):
206 """
207 Process shutter and speaker status. See PJLink specification for format.
208 Update self.mute (audio) and self.shutter (video shutter).
209@@ -319,7 +312,7 @@
210 self.projectorUpdateIcons.emit()
211 return
212
213- def process_clss(self, data, *args, **kwargs):
214+ def process_clss(self, data):
215 """
216 PJLink class that this projector supports. See PJLink specification for format.
217 Updates self.class.
218@@ -365,7 +358,7 @@
219
220 return
221
222- def process_erst(self, data, *args, **kwargs):
223+ def process_erst(self, data):
224 """
225 Error status. See PJLink Specifications for format.
226 Updates self.projector_errors
227@@ -417,7 +410,7 @@
228 PJLINK_ERST_STATUS[other]
229 return
230
231- def process_inf1(self, data, *args, **kwargs):
232+ def process_inf1(self, data):
233 """
234 Manufacturer name set in projector.
235 Updates self.manufacturer
236@@ -429,7 +422,7 @@
237 data=self.manufacturer))
238 return
239
240- def process_inf2(self, data, *args, **kwargs):
241+ def process_inf2(self, data):
242 """
243 Projector Model set in projector.
244 Updates self.model.
245@@ -440,7 +433,7 @@
246 log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.entry.name, data=self.model))
247 return
248
249- def process_info(self, data, *args, **kwargs):
250+ def process_info(self, data):
251 """
252 Any extra info set in projector.
253 Updates self.other_info.
254@@ -451,7 +444,7 @@
255 log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.entry.name, data=self.other_info))
256 return
257
258- def process_inpt(self, data, *args, **kwargs):
259+ def process_inpt(self, data):
260 """
261 Current source input selected. See PJLink specification for format.
262 Update self.source
263@@ -473,7 +466,7 @@
264 log.debug('({ip}) Setting data source to "{data}"'.format(ip=self.entry.name, data=self.source))
265 return
266
267- def process_inst(self, data, *args, **kwargs):
268+ def process_inst(self, data):
269 """
270 Available source inputs. See PJLink specification for format.
271 Updates self.source_available
272@@ -490,7 +483,7 @@
273 data=self.source_available))
274 return
275
276- def process_lamp(self, data, *args, **kwargs):
277+ def process_lamp(self, data):
278 """
279 Lamp(s) status. See PJLink Specifications for format.
280 Data may have more than 1 lamp to process.
281@@ -516,18 +509,22 @@
282 self.lamp = lamps
283 return
284
285- def process_lkup(self, data, host, port):
286+ def process_lkup(self, data):
287 """
288 Process reply indicating remote is available for connection
289
290 :param data: Data packet from remote
291- :param host: Remote IP address
292- :param port: Local port packet received on
293 """
294- # TODO: Check if autoconnect is enabled and connect?
295- pass
296+ log.debug('({ip}) Processing LKUP command'.format(ip=self.entry.name))
297+ settings = Settings()
298+ settings.beginGroup(self.settings_section)
299+ autostart = settings.value('connect when LKUP received')
300+ settings.endGroup()
301+ del settings
302+ if autostart:
303+ self.connect_to_host()
304
305- def process_name(self, data, *args, **kwargs):
306+ def process_name(self, data):
307 """
308 Projector name set in projector.
309 Updates self.pjlink_name
310@@ -538,7 +535,7 @@
311 log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.entry.name, data=self.pjlink_name))
312 return
313
314- def process_pjlink(self, data, *args, **kwargs):
315+ def process_pjlink(self, data):
316 """
317 Process initial socket connection to terminal.
318
319@@ -579,7 +576,7 @@
320 # Since this is an initial connection, make it a priority just in case
321 return self.send_command(cmd="CLSS", salt=data_hash, priority=True)
322
323- def process_powr(self, data, *args, **kwargs):
324+ def process_powr(self, data):
325 """
326 Power status. See PJLink specification for format.
327 Update self.power with status. Update icons if change from previous setting.
328@@ -602,7 +599,7 @@
329 log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.entry.name, data=data))
330 return
331
332- def process_rfil(self, data, *args, **kwargs):
333+ def process_rfil(self, data):
334 """
335 Process replacement filter type
336 """
337@@ -613,7 +610,7 @@
338 log.warning('({ip}) Saved model: "{old}"'.format(ip=self.entry.name, old=self.model_filter))
339 log.warning('({ip}) New model: "{new}"'.format(ip=self.entry.name, new=data))
340
341- def process_rlmp(self, data, *args, **kwargs):
342+ def process_rlmp(self, data):
343 """
344 Process replacement lamp type
345 """
346@@ -624,7 +621,7 @@
347 log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.entry.name, old=self.model_lamp))
348 log.warning('({ip}) New lamp: "{new}"'.format(ip=self.entry.name, new=data))
349
350- def process_snum(self, data, *args, **kwargs):
351+ def process_snum(self, data):
352 """
353 Serial number of projector.
354
355@@ -644,7 +641,7 @@
356 log.warning('({ip}) NOT saving serial number'.format(ip=self.entry.name))
357 self.serial_no_received = data
358
359- def process_srch(self, data, host, port):
360+ def process_srch(self, data):
361 """
362 Process the SRCH command.
363
364@@ -654,10 +651,10 @@
365 :param host: IP address of sending host
366 :param port: Port received on
367 """
368- log.warning('(UDP) SRCH packet received from {host} - ignoring'.format(host=host))
369+ log.warning("({ip}) I don't do SRCH packets - ignoring".format(ip=self.ip))
370 return
371
372- def process_sver(self, data, *args, **kwargs):
373+ def process_sver(self, data):
374 """
375 Software version of projector
376 """
377@@ -705,14 +702,16 @@
378 args=args,
379 kwargs=kwargs))
380 super().__init__()
381+ self.settings_section = 'projector'
382 self.entry = projector
383 self.ip = self.entry.ip
384+ self.qhost = QtNetwork.QHostAddress(self.ip)
385 self.location = self.entry.location
386 self.mac_adx = self.entry.mac_adx
387 self.name = self.entry.name
388 self.notes = self.entry.notes
389 self.pin = self.entry.pin
390- self.port = self.entry.port
391+ self.port = int(self.entry.port)
392 self.pjlink_class = PJLINK_CLASS if self.entry.pjlink_class is None else self.entry.pjlink_class
393 self.ackn_list = {} # Replies from online projectors (Class 2 option)
394 self.db_update = False # Use to check if db needs to be updated prior to exiting
395@@ -928,19 +927,21 @@
396 count=trash_count))
397 return
398
399- @QtCore.pyqtSlot(str, str)
400- def get_buffer(self, data, ip):
401+ @QtCore.pyqtSlot(QtNetwork.QHostAddress, int, str, name='udp_data') # host, port, data
402+ def get_buffer(self, host, port, data):
403 """
404 Get data from somewhere other than TCP socket
405
406+ :param host: QHostAddress of sender
407+ :param port: Destination port
408 :param data: Data to process. buffer must be formatted as a proper PJLink packet.
409- :param ip: Destination IP for buffer.
410 """
411- log.debug('({ip}) get_buffer(data="{buff}" ip="{ip_in}"'.format(ip=self.entry.name, buff=data, ip_in=ip))
412- if ip is None:
413- log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.entry.name))
414- return
415- return self.get_data(buff=data, ip=ip)
416+ if (port == int(self.port)) and (host.isEqual(self.qhost)):
417+ log.debug('({ip}) Received data from {host}'.format(ip=self.entry.name, host=host.toString()))
418+ log.debug('({ip}) get_buffer(data="{buff}")'.format(ip=self.entry.name, buff=data))
419+ return self.get_data(buff=data)
420+ else:
421+ log.debug('({ip}) Ignoring data for {host} - not me'.format(ip=self.entry.name, host=host.toString()))
422
423 @QtCore.pyqtSlot()
424 def get_socket(self):
425@@ -960,20 +961,16 @@
426 log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.entry.name))
427 return self.receive_data_signal()
428 self.socket_timer.stop()
429- return self.get_data(buff=read, ip=self.ip)
430+ return self.get_data(buff=read)
431
432- def get_data(self, buff, ip=None, *args, **kwargs):
433+ def get_data(self, buff, *args, **kwargs):
434 """
435 Process received data
436
437 :param buff: Data to process.
438- :param ip: (optional) Destination IP.
439 """
440- # Since "self" is not available to options and the "ip" keyword is a "maybe I'll use in the future",
441- # set to default here
442- if ip is None:
443- ip = self.ip
444- log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.entry.name, ip_in=ip, buff=buff))
445+ log.debug('({ip}) get_data(buffer="{buff}"'.format(ip=self.entry.name, buff=buff))
446+ ignore_class = 'ignore_class' in kwargs
447 # NOTE: Class2 has changed to some values being UTF-8
448 if isinstance(buff, bytes):
449 data_in = decode(buff, 'utf-8')
450@@ -990,7 +987,9 @@
451 elif not data.startswith(PJLINK_PREFIX):
452 self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing')
453 return self.receive_data_signal()
454- elif data[6] != '=':
455+ elif data[6] != '=' and data[8] != '=':
456+ # data[6] = standard command packet
457+ # data[8] = initial PJLink connection (after mangling)
458 self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="')
459 return self.receive_data_signal()
460 log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.entry.name, data=data))
461@@ -1020,16 +1019,16 @@
462 return self.receive_data_signal()
463 '''
464 if cmd not in PJLINK_VALID_CMD:
465- self._trash_buffer('get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.entry.name,
466- data=cmd))
467+ self._trash_buffer('get_data(): Invalid packet - unknown command "{data}"'.format(data=cmd))
468 return self.receive_data_signal()
469 elif version not in PJLINK_VALID_CMD[cmd]['version']:
470 self._trash_buffer(msg='get_data() Command reply version does not match a valid command version')
471 return self.receive_data_signal()
472 elif int(self.pjlink_class) < int(version):
473- log.warning('({ip}) get_data(): Projector returned class reply higher '
474- 'than projector stated class'.format(ip=self.entry.name))
475- self.process_command(cmd, data, *args, **kwargs)
476+ if not ignore_class:
477+ log.warning('({ip}) get_data(): Projector returned class reply higher '
478+ 'than projector stated class'.format(ip=self.entry.name))
479+ self.process_command(cmd, data)
480 return self.receive_data_signal()
481
482 @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)
483@@ -1107,11 +1106,18 @@
484 """
485 Socket interface to send data. If data=None, then check queue.
486
487- :param data: Immediate data to send
488+ :param data: Immediate data to send (Optional)
489 :param utf8: Send as UTF-8 string otherwise send as ASCII string
490 """
491- # Funny looking data check, but it's a quick check for data=None
492- log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.entry.name, data=data.strip() if data else data))
493+ if not data and not self.priority_queue and not self.send_queue:
494+ log.debug('({ip}) _send_command(): Nothing to send - returning'.format(ip=self.entry.name))
495+ return
496+ log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.entry.name,
497+ data=data.strip() if data else data))
498+ log.debug('({ip}) _send_command(): priority_queue: {queue}'.format(ip=self.entry.name,
499+ queue=self.priority_queue))
500+ log.debug('({ip}) _send_command(): send_queue: {queue}'.format(ip=self.entry.name,
501+ queue=self.send_queue))
502 conn_state = STATUS_CODE[QSOCKET_STATE[self.state()]]
503 log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.entry.name,
504 data=conn_state))
505@@ -1149,9 +1155,9 @@
506 self.waitForBytesWritten(2000) # 2 seconds should be enough
507 if sent == -1:
508 # Network error?
509- log.warning('({ip}) _send_command(): -1 received - disconnecting from host'.format(ip=self.entry.name))
510 self.change_status(E_NETWORK,
511 translate('OpenLP.PJLink', 'Error while sending data to projector'))
512+ log.warning('({ip}) _send_command(): -1 received - disconnecting from host'.format(ip=self.entry.name))
513 self.disconnect_from_host()
514
515 def connect_to_host(self):
516@@ -1164,7 +1170,7 @@
517 return
518 self.error_status = S_OK
519 self.change_status(S_CONNECTING)
520- self.connectToHost(self.ip, self.port if isinstance(self.port, int) else int(self.port))
521+ self.connectToHost(self.ip, self.port)
522
523 @QtCore.pyqtSlot()
524 def disconnect_from_host(self, abort=False):
525@@ -1181,7 +1187,7 @@
526 try:
527 self.readyRead.disconnect(self.get_socket)
528 except TypeError:
529- pass
530+ log.debug('({ip}) disconnect_from_host(): TypeError detected'.format(ip=self.entry.name))
531 log.debug('({ip}) disconnect_from_host() '
532 'Current status {data}'.format(ip=self.entry.name, data=self._get_status(self.status_connect)[0]))
533 if abort:
534
535=== modified file 'openlp/core/projectors/tab.py'
536--- openlp/core/projectors/tab.py 2017-12-29 09:15:48 +0000
537+++ openlp/core/projectors/tab.py 2018-04-28 07:12:35 +0000
538@@ -89,6 +89,10 @@
539 self.connect_box_layout.addRow(self.dialog_type_label, self.dialog_type_combo_box)
540 self.left_layout.addStretch()
541 self.dialog_type_combo_box.activated.connect(self.on_dialog_type_combo_box_changed)
542+ # Connect on LKUP packet received (PJLink v2+ only)
543+ self.connect_on_linkup = QtWidgets.QCheckBox(self.connect_box)
544+ self.connect_on_linkup.setObjectName('connect_on_linkup')
545+ self.connect_box_layout.addRow(self.connect_on_linkup)
546
547 def retranslateUi(self):
548 """
549@@ -109,6 +113,8 @@
550 translate('OpenLP.ProjectorTab', 'Tabbed dialog box'))
551 self.dialog_type_combo_box.setItemText(DialogSourceStyle.Single,
552 translate('OpenLP.ProjectorTab', 'Single dialog box'))
553+ self.connect_on_linkup.setText(
554+ translate('OpenLP.ProjectorTab', 'Connect to projector when LINKUP received (v2 only)'))
555
556 def load(self):
557 """
558@@ -120,6 +126,7 @@
559 self.socket_timeout_spin_box.setValue(settings.value('socket timeout'))
560 self.socket_poll_spin_box.setValue(settings.value('poll time'))
561 self.dialog_type_combo_box.setCurrentIndex(settings.value('source dialog type'))
562+ self.connect_on_linkup.setChecked(settings.value('connect when LKUP received'))
563 settings.endGroup()
564
565 def save(self):
566@@ -132,6 +139,7 @@
567 settings.setValue('socket timeout', self.socket_timeout_spin_box.value())
568 settings.setValue('poll time', self.socket_poll_spin_box.value())
569 settings.setValue('source dialog type', self.dialog_type_combo_box.currentIndex())
570+ settings.setValue('connect when LKUP received', self.connect_on_linkup.isChecked())
571 settings.endGroup()
572
573 def on_dialog_type_combo_box_changed(self):
574
575=== renamed file 'tests/openlp_core/projectors/test_projector_pjlink_base.py' => 'tests/openlp_core/projectors/test_projector_pjlink_base_01.py'
576--- tests/openlp_core/projectors/test_projector_pjlink_base.py 2018-01-13 05:41:42 +0000
577+++ tests/openlp_core/projectors/test_projector_pjlink_base_01.py 2018-04-28 07:12:35 +0000
578@@ -27,23 +27,13 @@
579
580 import openlp.core.projectors.pjlink
581 from openlp.core.projectors.constants import \
582- E_NOT_CONNECTED, \
583- E_PARAMETER, \
584- E_UNKNOWN_SOCKET_ERROR, \
585- STATUS_CODE, \
586- STATUS_MSG, \
587- S_CONNECTED, \
588- S_CONNECTING, \
589- S_NOT_CONNECTED, \
590- S_OK, \
591- S_ON, \
592+ E_NOT_CONNECTED, E_PARAMETER, E_UNKNOWN_SOCKET_ERROR, STATUS_CODE, STATUS_MSG, \
593+ S_CONNECTED, S_CONNECTING, S_NOT_CONNECTED, S_OK, S_ON, \
594 QSOCKET_STATE
595 from openlp.core.projectors.db import Projector
596 from openlp.core.projectors.pjlink import PJLink
597 from tests.resources.projector.data import TEST1_DATA
598
599-pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
600-
601
602 class TestPJLinkBase(TestCase):
603 """
604
605=== added file 'tests/openlp_core/projectors/test_projector_pjlink_base_02.py'
606--- tests/openlp_core/projectors/test_projector_pjlink_base_02.py 1970-01-01 00:00:00 +0000
607+++ tests/openlp_core/projectors/test_projector_pjlink_base_02.py 2018-04-28 07:12:35 +0000
608@@ -0,0 +1,101 @@
609+# -*- coding: utf-8 -*-
610+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
611+
612+###############################################################################
613+# OpenLP - Open Source Lyrics Projection #
614+# --------------------------------------------------------------------------- #
615+# Copyright (c) 2008-2015 OpenLP Developers #
616+# --------------------------------------------------------------------------- #
617+# This program is free software; you can redistribute it and/or modify it #
618+# under the terms of the GNU General Public License as published by the Free #
619+# Software Foundation; version 2 of the License. #
620+# #
621+# This program is distributed in the hope that it will be useful, but WITHOUT #
622+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
623+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
624+# more details. #
625+# #
626+# You should have received a copy of the GNU General Public License along #
627+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
628+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
629+###############################################################################
630+"""
631+Package to test the openlp.core.projectors.pjlink base package.
632+"""
633+from unittest import TestCase
634+from unittest.mock import call, patch
635+
636+import openlp.core.projectors.pjlink
637+
638+from openlp.core.projectors.constants import S_NOT_CONNECTED
639+from openlp.core.projectors.db import Projector
640+from openlp.core.projectors.pjlink import PJLink
641+from tests.resources.projector.data import TEST1_DATA
642+
643+
644+class TestPJLinkBase(TestCase):
645+ """
646+ Tests for the PJLink module
647+ """
648+ @patch.object(openlp.core.projectors.pjlink.PJLink, 'state')
649+ @patch.object(openlp.core.projectors.pjlink.PJLink, 'reset_information')
650+ @patch.object(openlp.core.projectors.pjlink.PJLink, '_send_command')
651+ @patch.object(openlp.core.projectors.pjlink, 'log')
652+ def test_send_command_no_data(self, mock_log, mock_send_command, mock_reset, mock_state):
653+ """
654+ Test _send_command with no data to send
655+ """
656+ # GIVEN: Test object
657+ log_warning_calls = [call('({ip}) send_command(): Not connected - returning'.format(ip=TEST1_DATA['name']))]
658+
659+ log_debug_calls = [call('PJlink(projector="< Projector(id="None", ip="111.111.111.111", port="1111", '
660+ 'mac_adx="11:11:11:11:11:11", pin="1111", name="___TEST_ONE___", '
661+ 'location="location one", notes="notes one", pjlink_name="None", '
662+ 'pjlink_class="None", manufacturer="None", model="None", '
663+ 'serial_no="Serial Number 1", other="None", sources="None", source_list="[]", '
664+ 'model_filter="Filter type 1", model_lamp="Lamp type 1", '
665+ 'sw_version="Version 1") >", args="()" kwargs="{\'no_poll\': True}")'),
666+ call('PJlinkCommands(args=() kwargs={})')]
667+ mock_state.return_value = S_NOT_CONNECTED
668+ pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
669+ pjlink.send_queue = []
670+ pjlink.priority_queue = []
671+
672+ # WHEN: _send_command called with no data and queue's empty
673+ pjlink.send_command(cmd='DONTCARE')
674+
675+ # THEN:
676+ mock_log.debug.assert_has_calls(log_debug_calls)
677+ mock_log.warning.assert_has_calls(log_warning_calls)
678+ assert mock_reset.called is True
679+ assert mock_reset.called is True
680+
681+ @patch.object(openlp.core.projectors.pjlink, 'log')
682+ def test_local_send_command_no_data(self, mock_log):
683+ """
684+ Test _send_command with no data to send
685+ """
686+ # GIVEN: Test object
687+ log_debug_calls = [call('PJlink(projector="< Projector(id="None", ip="111.111.111.111", port="1111", '
688+ 'mac_adx="11:11:11:11:11:11", pin="1111", name="___TEST_ONE___", '
689+ 'location="location one", notes="notes one", pjlink_name="None", '
690+ 'pjlink_class="None", manufacturer="None", model="None", '
691+ 'serial_no="Serial Number 1", other="None", sources="None", source_list="[]", '
692+ 'model_filter="Filter type 1", model_lamp="Lamp type 1", '
693+ 'sw_version="Version 1") >", args="()" kwargs="{\'no_poll\': True}")'),
694+ call('PJlinkCommands(args=() kwargs={})'),
695+ call('(___TEST_ONE___) reset_information() connect status is S_NOT_CONNECTED'),
696+ call('(___TEST_ONE___) _send_command(): Nothing to send - returning')]
697+
698+ pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
699+ pjlink.send_queue = []
700+ pjlink.priority_queue = []
701+
702+ # WHEN: _send_command called with no data and queue's emtpy
703+ # Patch here since pjlink does not have socket_timer until after instantiation
704+ with patch.object(pjlink, 'socket_timer') as mock_timer:
705+ pjlink._send_command(data=None, utf8=False)
706+
707+ # THEN:
708+ mock_log.debug.assert_has_calls(log_debug_calls)
709+ assert mock_timer.called is False
710
711=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py'
712--- tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2018-04-20 06:04:43 +0000
713+++ tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2018-04-28 07:12:35 +0000
714@@ -49,13 +49,23 @@
715 pjlink.pjlink_functions = MagicMock()
716 log_warning_text = [call('({ip}) get_data(): Invalid packet - '
717 'unknown command "UNKN"'.format(ip=pjlink.name))]
718- log_debug_text = [call('(___TEST_ONE___) get_data(ip="111.111.111.111" buffer="%1UNKN=Huh?"'),
719+ log_debug_text = [call('PJlink(projector="< Projector(id="None", ip="111.111.111.111", port="1111", '
720+ 'mac_adx="11:11:11:11:11:11", pin="1111", name="___TEST_ONE___", '
721+ 'location="location one", notes="notes one", pjlink_name="None", '
722+ 'pjlink_class="None", manufacturer="None", model="None", serial_no="Serial Number 1", '
723+ 'other="None", sources="None", source_list="[]", model_filter="Filter type 1", '
724+ 'model_lamp="Lamp type 1", sw_version="Version 1") >", '
725+ 'args="()" kwargs="{\'no_poll\': True}")'),
726+ call('PJlinkCommands(args=() kwargs={})'),
727+ call('(___TEST_ONE___) reset_information() connect status is S_NOT_CONNECTED'),
728+ call('(___TEST_ONE___) get_data(buffer="%1UNKN=Huh?"'),
729 call('(___TEST_ONE___) get_data(): Checking new data "%1UNKN=Huh?"'),
730 call('(___TEST_ONE___) get_data() header="%1UNKN" data="Huh?"'),
731 call('(___TEST_ONE___) get_data() version="1" cmd="UNKN"'),
732- call('(___TEST_ONE___) Cleaning buffer - msg = "get_data(): '
733- 'Invalid packet - unknown command "UNKN""'),
734- call('(___TEST_ONE___) Finished cleaning buffer - 0 bytes dropped')]
735+ call('(___TEST_ONE___) Cleaning buffer - msg = "get_data(): Invalid packet - '
736+ 'unknown command "UNKN""'),
737+ call('(___TEST_ONE___) Finished cleaning buffer - 0 bytes dropped'),
738+ call('(___TEST_ONE___) _send_command(): Nothing to send - returning')]
739
740 # WHEN: get_data called with an unknown command
741 pjlink.get_data(buff='{prefix}1UNKN=Huh?'.format(prefix=PJLINK_PREFIX))
742
743=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_udp.py'
744--- tests/openlp_core/projectors/test_projector_pjlink_udp.py 2018-04-20 06:04:43 +0000
745+++ tests/openlp_core/projectors/test_projector_pjlink_udp.py 2018-04-28 07:12:35 +0000
746@@ -30,37 +30,23 @@
747 import openlp.core.projectors.pjlink
748 from openlp.core.projectors.constants import PJLINK_PORT
749
750-from openlp.core.projectors.db import Projector
751-from openlp.core.projectors.pjlink import PJLinkUDP, PJLink
752-from tests.resources.projector.data import TEST1_DATA, TEST2_DATA
753+from openlp.core.projectors.pjlink import PJLinkUDP
754+from tests.resources.projector.data import TEST1_DATA
755
756
757 class TestPJLinkBase(TestCase):
758 """
759 Tests for the PJLinkUDP class
760 """
761- def setUp(self):
762- """
763- Setup generic test conditions
764- """
765- self.test_list = [PJLink(projector=Projector(**TEST1_DATA)),
766- PJLink(projector=Projector(**TEST2_DATA))]
767-
768- def tearDown(self):
769- """
770- Close generic test condidtions
771- """
772- self.test_list = None
773-
774 @patch.object(openlp.core.projectors.pjlink, 'log')
775 def test_get_datagram_data_negative_zero_length(self, mock_log):
776 """
777 Test get_datagram when pendingDatagramSize = 0
778 """
779 # GIVEN: Test setup
780- pjlink_udp = PJLinkUDP(projector_list=self.test_list)
781- log_warn_calls = [call('(UDP) No data (-1)')]
782- log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
783+ pjlink_udp = PJLinkUDP()
784+ log_warning_calls = [call('(UDP) No data (-1)')]
785+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'),
786 call('(UDP) get_datagram() - Receiving data')]
787 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
788 patch.object(pjlink_udp, 'readDatagram') as mock_read:
789@@ -71,7 +57,7 @@
790 pjlink_udp.get_datagram()
791
792 # THEN: Log entries should be made and method returns
793- mock_log.warning.assert_has_calls(log_warn_calls)
794+ mock_log.warning.assert_has_calls(log_warning_calls)
795 mock_log.debug.assert_has_calls(log_debug_calls)
796
797 @patch.object(openlp.core.projectors.pjlink, 'log')
798@@ -80,9 +66,10 @@
799 Test get_datagram when data length = 0
800 """
801 # GIVEN: Test setup
802- pjlink_udp = PJLinkUDP(projector_list=self.test_list)
803- log_warn_calls = [call('(UDP) get_datagram() called when pending data size is 0')]
804- log_debug_calls = [call('(UDP) get_datagram() - Receiving data')]
805+ pjlink_udp = PJLinkUDP()
806+ log_warning_calls = [call('(UDP) get_datagram() called when pending data size is 0')]
807+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'),
808+ call('(UDP) get_datagram() - Receiving data')]
809 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
810 patch.object(pjlink_udp, 'readDatagram') as mock_read:
811 mock_datagram.return_value = 0
812@@ -92,7 +79,7 @@
813 pjlink_udp.get_datagram()
814
815 # THEN: Log entries should be made and method returns
816- mock_log.warning.assert_has_calls(log_warn_calls)
817+ mock_log.warning.assert_has_calls(log_warning_calls)
818 mock_log.debug.assert_has_calls(log_debug_calls)
819
820 @patch.object(openlp.core.projectors.pjlink, 'log')
821@@ -101,9 +88,9 @@
822 Test get_datagram when pendingDatagramSize = 0
823 """
824 # GIVEN: Test setup
825- pjlink_udp = PJLinkUDP(projector_list=self.test_list)
826- log_warn_calls = [call('(UDP) get_datagram() called when pending data size is 0')]
827- log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
828+ pjlink_udp = PJLinkUDP()
829+ log_warning_calls = [call('(UDP) get_datagram() called when pending data size is 0')]
830+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'),
831 call('(UDP) get_datagram() - Receiving data')]
832 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram:
833 mock_datagram.return_value = 0
834@@ -112,5 +99,5 @@
835 pjlink_udp.get_datagram()
836
837 # THEN: Log entries should be made and method returns
838- mock_log.warning.assert_has_calls(log_warn_calls)
839+ mock_log.warning.assert_has_calls(log_warning_calls)
840 mock_log.debug.assert_has_calls(log_debug_calls)