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

Proposed by Ken Roberts
Status: Merged
Merged at revision: 2818
Proposed branch: lp:~alisonken1/openlp/pjlink2-q
Merge into: lp:openlp
Diff against target: 1482 lines (+437/-501)
8 files modified
openlp/core/projectors/constants.py (+53/-26)
openlp/core/projectors/editform.py (+42/-27)
openlp/core/projectors/manager.py (+3/-18)
openlp/core/projectors/pjlink.py (+158/-149)
tests/openlp_core/projectors/test_projector_db.py (+35/-2)
tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py (+21/-21)
tests/openlp_core/projectors/test_projector_pjlink_commands_02.py (+115/-4)
tests/openlp_core/projectors/test_projector_pjlink_udp.py (+10/-254)
To merge this branch: bzr merge lp:~alisonken1/openlp/pjlink2-q
Reviewer Review Type Date Requested Status
Tim Bentley Approve
Phill Approve
Review via email: mp+343669@code.launchpad.net

This proposal supersedes a proposal from 2018-04-20.

Commit message

PJLink2 update Q

Description of the change

PJLink2 update Q

--------------------------------------------------------------------------------

lp:~alisonken1/openlp/pjlink2-q (revision 2818)
https://ci.openlp.io/job/Branch-01-Pull/2506/ [SUCCESS]
https://ci.openlp.io/job/Branch-02a-Linux-Tests/2407/ [SUCCESS]
https://ci.openlp.io/job/Branch-02b-macOS-Tests/193/ [FAILURE]
https://ci.openlp.io/job/Branch-03a-Build-Source/105/ [SUCCESS]
https://ci.openlp.io/job/Branch-03b-Build-macOS/98/ [SUCCESS]
https://ci.openlp.io/job/Branch-04a-Code-Analysis/1567/ [SUCCESS]
https://ci.openlp.io/job/Branch-04b-Test-Coverage/1380/ [SUCCESS]
https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/309/ [FAILURE]

----------------------------------------------------------------------------------

- Fix test_projector_db:TestProjectorDBUpdate segfault by
    creating self.main_window steps
- Pep8 on openlp/core/common/__init__.get_local_ip4()
- Collapse import from projector.constants from line-per-item to multi-items-per-line
- Change pjlink_functions to include class version for projector instance in command
- Set projector.editform to only allow editing IP address field for new entry only (used as db key entry)
- Collapse projector.manager imports from entry-per-line to compact import
- projector.pjlink:
    - Merge pjlink_functions_udp into pjlink_functions
    - Change pjlink_functions to add instance-specific version to commands
    - Fix command checks to changed pjlink_funcions
    - Update process_clss to update PJLink version in pjlink_functions
    - Update reset_information to update PJLink version in pjlink_functions
- renamed test_projectorsourceform.py to test_projector_sourceform.py
- renamed test_projectoreditform.py to test_projector_editform.py
- Fix projector tests
- Fix list creation in constants (remove unneeded comma)
- Rename editform ip_text_show label to ip_text_label
- Refactor editform IP address check
- Add TODO on commented code block for breaking incoming packet into parts

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

Sorry will clash with one of my fixes which has been merged

See question

review: Needs Fixing
Revision history for this message
Ken Roberts (alisonken1) wrote : Posted in a previous version of this proposal

> Sorry will clash with one of my fixes which has been merged
>
> See question

>> 'ACKN': {'version': ['2', ],
>> + 'default': '2',

>Why do we have a missing slot?

2 new commands in PJLink are only valid for version 2 and have no version 1 equivalent.
Missing slot is to maintain compatibility with version checker.

>> - def process_clss(self, data):
>> + def process_clss(self, data, *args, **kwargs):

>why args
Some commands take host,port arguments but not all of them do.
Since there is a common entry point, need to keep availability of consuming extra args/kwargs without causing exception due to extra parameters.
(Yes, there should be no args passed only keyword args, but better safe than sorry)

Revision history for this message
Ken Roberts (alisonken1) wrote : Posted in a previous version of this proposal

which fix is clashing? I tried a merge from trunk and it merged fine.

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

Looks OK but projectors is not my strong point.

review: Approve
Revision history for this message
Phill (phill-ridout) wrote : Posted in a previous version of this proposal

A few issues / questions. See in line.

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

Looks good to me. Thanks!

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'openlp/core/projectors/constants.py'
--- openlp/core/projectors/constants.py 2018-01-03 00:35:14 +0000
+++ openlp/core/projectors/constants.py 2018-04-20 06:16:23 +0000
@@ -154,110 +154,137 @@
154 S_INFO154 S_INFO
155]155]
156156
157# NOTE: Changed format to account for some commands are both class 1 and 2157# NOTE: Changed format to account for some commands are both class 1 and 2.
158# Make sure the sequence of 'version' is lowest-to-highest.
158PJLINK_VALID_CMD = {159PJLINK_VALID_CMD = {
159 'ACKN': {'version': ['2', ],160 'ACKN': {'version': ['2'],
161 'default': '2',
160 'description': translate('OpenLP.PJLinkConstants',162 'description': translate('OpenLP.PJLinkConstants',
161 'Acknowledge a PJLink SRCH command - returns MAC address.')163 'Acknowledge a PJLink SRCH command - returns MAC address.')
162 },164 },
163 'AVMT': {'version': ['1', ],165 'AVMT': {'version': ['1'],
166 'default': '1',
164 'description': translate('OpenLP.PJLinkConstants',167 'description': translate('OpenLP.PJLinkConstants',
165 'Blank/unblank video and/or mute audio.')168 'Blank/unblank video and/or mute audio.')
166 },169 },
167 'CLSS': {'version': ['1', ],170 'CLSS': {'version': ['1'],
171 'default': '1',
168 'description': translate('OpenLP.PJLinkConstants',172 'description': translate('OpenLP.PJLinkConstants',
169 'Query projector PJLink class support.')173 'Query projector PJLink class support.')
170 },174 },
171 'ERST': {'version': ['1', '2'],175 'ERST': {'version': ['1', '2'],
176 'default': '1',
172 'description': translate('OpenLP.PJLinkConstants',177 'description': translate('OpenLP.PJLinkConstants',
173 'Query error status from projector. '178 'Query error status from projector. '
174 'Returns fan/lamp/temp/cover/filter/other error status.')179 'Returns fan/lamp/temp/cover/filter/other error status.')
175 },180 },
176 'FILT': {'version': ['2', ],181 'FILT': {'version': ['2'],
182 'default': '1',
177 'description': translate('OpenLP.PJLinkConstants',183 'description': translate('OpenLP.PJLinkConstants',
178 'Query number of hours on filter.')184 'Query number of hours on filter.')
179 },185 },
180 'FREZ': {'version': ['2', ],186 'FREZ': {'version': ['2'],
187 'default': '1',
181 'description': translate('OpenLP.PJLinkConstants',188 'description': translate('OpenLP.PJLinkConstants',
182 'Freeze or unfreeze current image being projected.')189 'Freeze or unfreeze current image being projected.')
183 },190 },
184 'INF1': {'version': ['1', ],191 'INF1': {'version': ['1'],
192 'default': '1',
185 'description': translate('OpenLP.PJLinkConstants',193 'description': translate('OpenLP.PJLinkConstants',
186 'Query projector manufacturer name.')194 'Query projector manufacturer name.')
187 },195 },
188 'INF2': {'version': ['1', ],196 'INF2': {'version': ['1'],
197 'default': '1',
189 'description': translate('OpenLP.PJLinkConstants',198 'description': translate('OpenLP.PJLinkConstants',
190 'Query projector product name.')199 'Query projector product name.')
191 },200 },
192 'INFO': {'version': ['1', ],201 'INFO': {'version': ['1'],
202 'default': '1',
193 'description': translate('OpenLP.PJLinkConstants',203 'description': translate('OpenLP.PJLinkConstants',
194 'Query projector for other information set by manufacturer.')204 'Query projector for other information set by manufacturer.')
195 },205 },
196 'INNM': {'version': ['2', ],206 'INNM': {'version': ['2'],
207 'default': '2',
197 'description': translate('OpenLP.PJLinkConstants',208 'description': translate('OpenLP.PJLinkConstants',
198 'Query specified input source name')209 'Query specified input source name')
199 },210 },
200 'INPT': {'version': ['1', ],211 'INPT': {'version': ['1'],
212 'default': '1',
201 'description': translate('OpenLP.PJLinkConstants',213 'description': translate('OpenLP.PJLinkConstants',
202 'Switch to specified video source.')214 'Switch to specified video source.')
203 },215 },
204 'INST': {'version': ['1', ],216 'INST': {'version': ['1'],
217 'default': '1',
205 'description': translate('OpenLP.PJLinkConstants',218 'description': translate('OpenLP.PJLinkConstants',
206 'Query available input sources.')219 'Query available input sources.')
207 },220 },
208 'IRES': {'version:': ['2', ],221 'IRES': {'version': ['2'],
222 'default': '2',
209 'description': translate('OpenLP.PJLinkConstants',223 'description': translate('OpenLP.PJLinkConstants',
210 'Query current input resolution.')224 'Query current input resolution.')
211 },225 },
212 'LAMP': {'version': ['1', ],226 'LAMP': {'version': ['1'],
227 'default': '1',
213 'description': translate('OpenLP.PJLinkConstants',228 'description': translate('OpenLP.PJLinkConstants',
214 'Query lamp time and on/off status. Multiple lamps supported.')229 'Query lamp time and on/off status. Multiple lamps supported.')
215 },230 },
216 'LKUP': {'version': ['2', ],231 'LKUP': {'version': ['2'],
232 'default': '2',
217 'description': translate('OpenLP.PJLinkConstants',233 'description': translate('OpenLP.PJLinkConstants',
218 'UDP Status - Projector is now available on network. Includes MAC address.')234 'UDP Status - Projector is now available on network. Includes MAC address.')
219 },235 },
220 'MVOL': {'version': ['2', ],236 'MVOL': {'version': ['2'],
237 'default': '1',
221 'description': translate('OpenLP.PJLinkConstants',238 'description': translate('OpenLP.PJLinkConstants',
222 'Adjust microphone volume by 1 step.')239 'Adjust microphone volume by 1 step.')
223 },240 },
224 'NAME': {'version': ['1', ],241 'NAME': {'version': ['1'],
242 'default': '1',
225 'description': translate('OpenLP.PJLinkConstants',243 'description': translate('OpenLP.PJLinkConstants',
226 'Query customer-set projector name.')244 'Query customer-set projector name.')
227 },245 },
228 'PJLINK': {'version': ['1', ],246 'PJLINK': {'version': ['1'],
247 'default': '1',
229 'description': translate('OpenLP.PJLinkConstants',248 'description': translate('OpenLP.PJLinkConstants',
230 'Initial connection with authentication/no authentication request.')249 'Initial connection with authentication/no authentication request.')
231 },250 },
232 'POWR': {'version': ['1', ],251 'POWR': {'version': ['1'],
252 'default': '1',
233 'description': translate('OpenLP.PJLinkConstants',253 'description': translate('OpenLP.PJLinkConstants',
234 'Turn lamp on or off/standby.')254 'Turn lamp on or off/standby.')
235 },255 },
236 'RFIL': {'version': ['2', ],256 'RFIL': {'version': ['2'],
257 'default': '2',
237 'description': translate('OpenLP.PJLinkConstants',258 'description': translate('OpenLP.PJLinkConstants',
238 'Query replacement air filter model number.')259 'Query replacement air filter model number.')
239 },260 },
240 'RLMP': {'version': ['2', ],261 'RLMP': {'version': ['2'],
262 'default': '2',
241 'description': translate('OpenLP.PJLinkConstants',263 'description': translate('OpenLP.PJLinkConstants',
242 'Query replacement lamp model number.')264 'Query replacement lamp model number.')
243 },265 },
244 'RRES': {'version': ['2', ],266 'RRES': {'version': ['2'],
267 'default': '2',
245 'description': translate('OpenLP.PJLinkConstants',268 'description': translate('OpenLP.PJLinkConstants',
246 'Query recommended resolution.')269 'Query recommended resolution.')
247 },270 },
248 'SNUM': {'version': ['2', ],271 'SNUM': {'version': ['2'],
272 'default': '2',
249 'description': translate('OpenLP.PJLinkConstants',273 'description': translate('OpenLP.PJLinkConstants',
250 'Query projector serial number.')274 'Query projector serial number.')
251 },275 },
252 'SRCH': {'version': ['2', ],276 'SRCH': {'version': ['2'],
277 'default': '2',
253 'description': translate('OpenLP.PJLinkConstants',278 'description': translate('OpenLP.PJLinkConstants',
254 'UDP broadcast search request for available projectors. Reply is ACKN.')279 'UDP broadcast search request for available projectors. Reply is ACKN.')
255 },280 },
256 'SVER': {'version': ['2', ],281 'SVER': {'version': ['2'],
282 'default': '2',
257 'description': translate('OpenLP.PJLinkConstants',283 'description': translate('OpenLP.PJLinkConstants',
258 'Query projector software version number.')284 'Query projector software version number.')
259 },285 },
260 'SVOL': {'version': ['2', ],286 'SVOL': {'version': ['2'],
287 'default': '2',
261 'description': translate('OpenLP.PJLinkConstants',288 'description': translate('OpenLP.PJLinkConstants',
262 'Adjust speaker volume by 1 step.')289 'Adjust speaker volume by 1 step.')
263 }290 }
264291
=== modified file 'openlp/core/projectors/editform.py'
--- openlp/core/projectors/editform.py 2017-12-29 09:15:48 +0000
+++ openlp/core/projectors/editform.py 2018-04-20 06:16:23 +0000
@@ -58,10 +58,15 @@
58 # IP Address58 # IP Address
59 self.ip_label = QtWidgets.QLabel(edit_projector_dialog)59 self.ip_label = QtWidgets.QLabel(edit_projector_dialog)
60 self.ip_label.setObjectName('projector_edit_ip_label')60 self.ip_label.setObjectName('projector_edit_ip_label')
61 self.ip_text = QtWidgets.QLineEdit(edit_projector_dialog)61 self.ip_text_edit = QtWidgets.QLineEdit(edit_projector_dialog)
62 self.ip_text.setObjectName('projector_edit_ip_text')62 self.ip_text_edit.setObjectName('projector_edit_ip_text')
63 self.ip_text_label = QtWidgets.QLabel(edit_projector_dialog)
64 self.ip_text_label.setObjectName('projector_show_ip_text')
63 self.dialog_layout.addWidget(self.ip_label, 0, 0)65 self.dialog_layout.addWidget(self.ip_label, 0, 0)
64 self.dialog_layout.addWidget(self.ip_text, 0, 1)66 # For new projector, use edit widget
67 self.dialog_layout.addWidget(self.ip_text_edit, 0, 1)
68 # For edit projector, use show widget
69 self.dialog_layout.addWidget(self.ip_text_label, 0, 1)
65 # Port number70 # Port number
66 self.port_label = QtWidgets.QLabel(edit_projector_dialog)71 self.port_label = QtWidgets.QLabel(edit_projector_dialog)
67 self.port_label.setObjectName('projector_edit_ip_label')72 self.port_label.setObjectName('projector_edit_ip_label')
@@ -111,8 +116,8 @@
111 title = translate('OpenLP.ProjectorEditForm', 'Edit Projector')116 title = translate('OpenLP.ProjectorEditForm', 'Edit Projector')
112 edit_projector_dialog.setWindowTitle(title)117 edit_projector_dialog.setWindowTitle(title)
113 self.ip_label.setText(translate('OpenLP.ProjectorEditForm', 'IP Address'))118 self.ip_label.setText(translate('OpenLP.ProjectorEditForm', 'IP Address'))
114 self.ip_text.setText(self.projector.ip)119 self.ip_text_edit.setText(self.projector.ip)
115 self.ip_text.setFocus()120 self.ip_text_label.setText(self.projector.ip)
116 self.port_label.setText(translate('OpenLP.ProjectorEditForm', 'Port Number'))121 self.port_label.setText(translate('OpenLP.ProjectorEditForm', 'Port Number'))
117 self.port_text.setText(str(self.projector.port))122 self.port_text.setText(str(self.projector.port))
118 self.pin_label.setText(translate('OpenLP.ProjectorEditForm', 'PIN'))123 self.pin_label.setText(translate('OpenLP.ProjectorEditForm', 'PIN'))
@@ -131,7 +136,7 @@
131 Class to add or edit a projector entry in the database.136 Class to add or edit a projector entry in the database.
132137
133 Fields that are editable:138 Fields that are editable:
134 ip = Column(String(100))139 ip = Column(String(100)) (Only edit for new projector)
135 port = Column(String(8))140 port = Column(String(8))
136 pin = Column(String(20))141 pin = Column(String(20))
137 name = Column(String(20))142 name = Column(String(20))
@@ -154,9 +159,16 @@
154 if projector is None:159 if projector is None:
155 self.projector = Projector()160 self.projector = Projector()
156 self.new_projector = True161 self.new_projector = True
162 self.ip_text_edit.setVisible(True)
163 self.ip_text_edit.setFocus()
164 self.ip_text_label.setVisible(False)
157 else:165 else:
158 self.projector = projector166 self.projector = projector
159 self.new_projector = False167 self.new_projector = False
168 self.ip_text_edit.setVisible(False)
169 self.ip_text_label.setVisible(True)
170 # Since it's already defined, IP address is unchangeable, so focus on port number
171 self.port_text.setFocus()
160 self.retranslateUi(self)172 self.retranslateUi(self)
161 reply = QtWidgets.QDialog.exec(self)173 reply = QtWidgets.QDialog.exec(self)
162 return reply174 return reply
@@ -187,30 +199,32 @@
187 record=record.id)))199 record=record.id)))
188 valid = False200 valid = False
189 return201 return
190 adx = self.ip_text.text()202 if self.new_projector:
191 valid = verify_ip_address(adx)203 # Only validate a new entry - otherwise it's been previously verified
192 if valid:204 adx = self.ip_text_edit.text()
193 ip = self.projectordb.get_projector_by_ip(adx)205 valid = verify_ip_address(adx)
194 if ip is None:206 if valid:
195 valid = True207 # With a valid IP - check if it's already in database so we don't duplicate
196 self.new_projector = True208 ip = self.projectordb.get_projector_by_ip(adx)
197 elif ip.id != self.projector.id:209 if ip is None:
210 valid = True
211 self.new_projector = True
212 elif ip.id != self.projector.id:
213 QtWidgets.QMessageBox.warning(self,
214 translate('OpenLP.ProjectorWizard', 'Duplicate IP Address'),
215 translate('OpenLP.ProjectorWizard',
216 'IP address "{ip}"<br />is already in the database '
217 'as ID {data}.<br /><br />Please Enter a different '
218 'IP address.'.format(ip=adx, data=ip.id)))
219 return
220 else:
198 QtWidgets.QMessageBox.warning(self,221 QtWidgets.QMessageBox.warning(self,
199 translate('OpenLP.ProjectorWizard', 'Duplicate IP Address'),222 translate('OpenLP.ProjectorWizard', 'Invalid IP Address'),
200 translate('OpenLP.ProjectorWizard',223 translate('OpenLP.ProjectorWizard',
201 'IP address "{ip}"<br />is already in the database '224 'IP address "{ip}"<br>is not a valid IP address.'
202 'as ID {data}.<br /><br />Please Enter a different '225 '<br /><br />Please enter a valid IP address.'.format(ip=adx)))
203 'IP address.'.format(ip=adx, data=ip.id)))
204 valid = False226 valid = False
205 return227 return
206 else:
207 QtWidgets.QMessageBox.warning(self,
208 translate('OpenLP.ProjectorWizard', 'Invalid IP Address'),
209 translate('OpenLP.ProjectorWizard',
210 'IP address "{ip}"<br>is not a valid IP address.'
211 '<br /><br />Please enter a valid IP address.'.format(ip=adx)))
212 valid = False
213 return
214 port = int(self.port_text.text())228 port = int(self.port_text.text())
215 if port < 1000 or port > 32767:229 if port < 1000 or port > 32767:
216 QtWidgets.QMessageBox.warning(self,230 QtWidgets.QMessageBox.warning(self,
@@ -223,7 +237,8 @@
223 'Default PJLink port is {port}'.format(port=PJLINK_PORT)))237 'Default PJLink port is {port}'.format(port=PJLINK_PORT)))
224 valid = False238 valid = False
225 if valid:239 if valid:
226 self.projector.ip = self.ip_text.text()240 if self.new_projector:
241 self.projector.ip = self.ip_text_edit.text()
227 self.projector.pin = self.pin_text.text()242 self.projector.pin = self.pin_text.text()
228 self.projector.port = int(self.port_text.text())243 self.projector.port = int(self.port_text.text())
229 self.projector.name = self.name_text.text()244 self.projector.name = self.name_text.text()
230245
=== modified file 'openlp/core/projectors/manager.py'
--- openlp/core/projectors/manager.py 2018-02-11 11:42:13 +0000
+++ openlp/core/projectors/manager.py 2018-04-20 06:16:23 +0000
@@ -35,24 +35,9 @@
35from openlp.core.common.settings import Settings35from openlp.core.common.settings import Settings
36from openlp.core.lib.ui import create_widget_action36from openlp.core.lib.ui import create_widget_action
37from openlp.core.projectors import DialogSourceStyle37from openlp.core.projectors import DialogSourceStyle
38from openlp.core.projectors.constants import \38from openlp.core.projectors.constants import E_AUTHENTICATION, E_ERROR, E_NETWORK, E_NOT_CONNECTED, \
39 E_AUTHENTICATION, \39 E_UNKNOWN_SOCKET_ERROR, S_CONNECTED, S_CONNECTING, S_COOLDOWN, S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, \
40 E_ERROR, \40 S_STANDBY, S_WARMUP, STATUS_CODE, STATUS_MSG, QSOCKET_STATE
41 E_NETWORK, \
42 E_NOT_CONNECTED, \
43 E_UNKNOWN_SOCKET_ERROR, \
44 S_CONNECTED, \
45 S_CONNECTING, \
46 S_COOLDOWN, \
47 S_INITIALIZE, \
48 S_NOT_CONNECTED, \
49 S_OFF, \
50 S_ON, \
51 S_STANDBY, \
52 S_WARMUP, \
53 STATUS_CODE, \
54 STATUS_MSG, \
55 QSOCKET_STATE
5641
57from openlp.core.projectors.db import ProjectorDB42from openlp.core.projectors.db import ProjectorDB
58from openlp.core.projectors.editform import ProjectorEditForm43from openlp.core.projectors.editform import ProjectorEditForm
5944
=== modified file 'openlp/core/projectors/pjlink.py'
--- openlp/core/projectors/pjlink.py 2018-02-11 11:42:13 +0000
+++ openlp/core/projectors/pjlink.py 2018-04-20 06:16:23 +0000
@@ -57,8 +57,7 @@
57from openlp.core.projectors.constants import CONNECTION_ERRORS, PJLINK_CLASS, PJLINK_DEFAULT_CODES, PJLINK_ERRORS, \57from openlp.core.projectors.constants import CONNECTION_ERRORS, PJLINK_CLASS, PJLINK_DEFAULT_CODES, PJLINK_ERRORS, \
58 PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PREFIX, PJLINK_PORT, PJLINK_POWR_STATUS, \58 PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PREFIX, PJLINK_PORT, PJLINK_POWR_STATUS, \
59 PJLINK_SUFFIX, PJLINK_VALID_CMD, PROJECTOR_STATE, STATUS_CODE, STATUS_MSG, QSOCKET_STATE, \59 PJLINK_SUFFIX, PJLINK_VALID_CMD, PROJECTOR_STATE, STATUS_CODE, STATUS_MSG, QSOCKET_STATE, \
60 E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \60 E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_NETWORK, E_NOT_CONNECTED, E_SOCKET_TIMEOUT, \
61 E_SOCKET_TIMEOUT, \
62 S_CONNECTED, S_CONNECTING, S_NOT_CONNECTED, S_OFF, S_OK, S_ON61 S_CONNECTED, S_CONNECTING, S_NOT_CONNECTED, S_OFF, S_OK, S_ON
6362
64log = logging.getLogger(__name__)63log = logging.getLogger(__name__)
@@ -93,22 +92,9 @@
93 self.projector_list = projector_list92 self.projector_list = projector_list
94 self.port = port93 self.port = port
95 # Local defines94 # Local defines
96 self.ackn_list = {} # Replies from online projetors
97 self.search_active = False95 self.search_active = False
98 self.search_time = 30000 # 30 seconds for allowed time96 self.search_time = 30000 # 30 seconds for allowed time
99 self.search_timer = QtCore.QTimer()97 self.search_timer = QtCore.QTimer()
100 # New commands available in PJLink Class 2
101 # ACKN/SRCH is processed here since it's used to find available projectors
102 # Other commands are processed by the individual projector instances
103 self.pjlink_udp_functions = {
104 'ACKN': self.process_ackn, # Class 2, command is 'SRCH'
105 'ERST': None, # Class 1/2
106 'INPT': None, # Class 1/2
107 'LKUP': None, # Class 2 (reply only - no cmd)
108 'POWR': None, # Class 1/2
109 'SRCH': self.process_srch # Class 2 (reply is ACKN)
110 }
111
112 self.readyRead.connect(self.get_datagram)98 self.readyRead.connect(self.get_datagram)
113 log.debug('(UDP) PJLinkUDP() Initialized')99 log.debug('(UDP) PJLinkUDP() Initialized')
114100
@@ -118,88 +104,26 @@
118 Retrieve packet and basic checks104 Retrieve packet and basic checks
119 """105 """
120 log.debug('(UDP) get_datagram() - Receiving data')106 log.debug('(UDP) get_datagram() - Receiving data')
121 read = self.pendingDatagramSize()107 read_size = self.pendingDatagramSize()
122 if read < 0:108 if read_size < 0:
123 log.warn('(UDP) No data (-1)')109 log.warning('(UDP) No data (-1)')
124 return110 return
125 if read < 1:111 if read_size < 1:
126 log.warn('(UDP) get_datagram() called when pending data size is 0')112 log.warning('(UDP) get_datagram() called when pending data size is 0')
127 return113 return
128 data, peer_address, peer_port = self.readDatagram(self.pendingDatagramSize())114 data, peer_address, peer_port = self.readDatagram(self.pendingDatagramSize())
129 log.debug('(UDP) {size} bytes received from {adx} on port {port}'.format(size=len(data),115 log.debug('(UDP) {size} bytes received from {adx} on port {port}'.format(size=len(data),
130 adx=peer_address,116 adx=peer_address,
131 port=peer_port))117 port=peer_port))
132 log.debug('(UDP) packet "{data}"'.format(data=data))118 log.debug('(UDP) packet "{data}"'.format(data=data))
133 if len(data) < 0:119 # Send to appropriate instance to process packet
134 log.warn('(UDP) No data (-1)')120 log.debug('(UDP) Checking projector list for ip {host} to process'.format(host=peer_address))
135 return121 for projector in self.projector_list:
136 elif len(data) < 8:122 if peer_address == projector.ip:
137 # Minimum packet is '%2CCCC='123 # Dispatch packet to appropriate remote instance
138 log.warn('(UDP) Invalid packet - not enough data')124 log.debug('(UDP) Dispatching packet to {host}'.format(host=projector.entry.name))
139 return125 return projector.get_data(buff=data, ip=peer_address, host=peer_address, port=peer_port)
140 elif data is None:126 log.warning('(UDP) Could not find projector with ip {ip} to process packet'.format(ip=peer_address))
141 log.warn('(UDP) No data (None)')
142 return
143 elif len(data) > PJLINK_MAX_PACKET:
144 log.warn('(UDP) Invalid packet - length too long')
145 return
146 elif not data.startswith(PJLINK_PREFIX):
147 log.warn('(UDP) Invalid packet - does not start with PJLINK_PREFIX')
148 return
149 elif data[1] != '2':
150 log.warn('(UDP) Invalid packet - missing/invalid PJLink class version')
151 return
152 elif data[6] != '=':
153 log.warn('(UDP) Invalid packet - separator missing')
154 return
155 # First two characters are header information we don't need at this time
156 cmd, data = data[2:].split('=')
157 if cmd not in self.pjlink_udp_functions:
158 log.warn('(UDP) Invalid packet - not a valid PJLink UDP reply')
159 return
160 if self.pjlink_udp_functions[cmd] is not None:
161 log.debug('(UDP) Processing {cmd} with "{data}"'.format(cmd=cmd, data=data))
162 return self.pjlink_udp_functions[cmd](data=data, host=peer_address, port=peer_port)
163 else:
164 log.debug('(UDP) Checking projector list for ip {host} to process'.format(host=peer_address))
165 for projector in self.projector_list:
166 if peer_address == projector.ip:
167 if cmd not in projector.pjlink_functions:
168 log.error('(UDP) Could not find method to process '
169 '"{cmd}" in {host}'.format(cmd=cmd, host=projector.ip))
170 return
171 log.debug('(UDP) Calling "{cmd}" in {host}'.format(cmd=cmd, host=projector.ip))
172 return projector.pjlink_functions[cmd](data=data)
173 log.warn('(UDP) Could not find projector with ip {ip} to process packet'.format(ip=peer_address))
174 return
175
176 def process_ackn(self, data, host, port):
177 """
178 Process the ACKN command.
179
180 :param data: Data in packet
181 :param host: IP address of sending host
182 :param port: Port received on
183 """
184 log.debug('(UDP) Processing ACKN packet')
185 if host not in self.ackn_list:
186 log.debug('(UDP) Adding {host} to ACKN list'.format(host=host))
187 self.ackn_list[host] = {'data': data,
188 'port': port}
189 else:
190 log.warn('(UDP) Host {host} already replied - ignoring'.format(host=host))
191
192 def process_srch(self, data, host, port):
193 """
194 Process the SRCH command.
195
196 SRCH is processed by terminals so we ignore any packet.
197
198 :param data: Data in packet
199 :param host: IP address of sending host
200 :param port: Port received on
201 """
202 log.debug('(UDP) SRCH packet received - ignoring')
203 return127 return
204128
205 def search_start(self):129 def search_start(self):
@@ -224,6 +148,8 @@
224 """148 """
225 Process replies from PJLink projector.149 Process replies from PJLink projector.
226 """150 """
151 # List of IP addresses and mac addresses found via UDP search command
152 ackn_list = []
227153
228 def __init__(self, *args, **kwargs):154 def __init__(self, *args, **kwargs):
229 """155 """
@@ -231,24 +157,47 @@
231 """157 """
232 log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))158 log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
233 super().__init__()159 super().__init__()
234 # Map PJLink command to method160 # Map PJLink command to method and include pjlink class version for this instance
161 # Default initial pjlink class version is '1'
235 self.pjlink_functions = {162 self.pjlink_functions = {
236 'AVMT': self.process_avmt,163 'ACKN': {"method": self.process_ackn, # Class 2 (command is SRCH)
237 'CLSS': self.process_clss,164 "version": "2"},
238 'ERST': self.process_erst,165 'AVMT': {"method": self.process_avmt,
239 'INFO': self.process_info,166 "version": "1"},
240 'INF1': self.process_inf1,167 'CLSS': {"method": self.process_clss,
241 'INF2': self.process_inf2,168 "version": "1"},
242 'INPT': self.process_inpt,169 'ERST': {"method": self.process_erst,
243 'INST': self.process_inst,170 "version": "1"},
244 'LAMP': self.process_lamp,171 'INFO': {"method": self.process_info,
245 'NAME': self.process_name,172 "version": "1"},
246 'PJLINK': self.process_pjlink,173 'INF1': {"method": self.process_inf1,
247 'POWR': self.process_powr,174 "version": "1"},
248 'SNUM': self.process_snum,175 'INF2': {"method": self.process_inf2,
249 'SVER': self.process_sver,176 "version": "1"},
250 'RFIL': self.process_rfil,177 'INPT': {"method": self.process_inpt,
251 'RLMP': self.process_rlmp178 "version": "1"},
179 'INST': {"method": self.process_inst,
180 "version": "1"},
181 'LAMP': {"method": self.process_lamp,
182 "version": "1"},
183 'LKUP': {"method": self.process_lkup, # Class 2 (reply only - no cmd)
184 "version": "2"},
185 'NAME': {"method": self.process_name,
186 "version": "1"},
187 'PJLINK': {"method": self.process_pjlink,
188 "version": "1"},
189 'POWR': {"method": self.process_powr,
190 "version": "1"},
191 'SNUM': {"method": self.process_snum,
192 "version": "1"},
193 'SRCH': {"method": self.process_srch, # Class 2 (reply is ACKN)
194 "version": "2"},
195 'SVER': {"method": self.process_sver,
196 "version": "1"},
197 'RFIL': {"method": self.process_rfil,
198 "version": "1"},
199 'RLMP': {"method": self.process_rlmp,
200 "version": "1"}
252 }201 }
253202
254 def reset_information(self):203 def reset_information(self):
@@ -287,8 +236,11 @@
287 self.send_busy = False236 self.send_busy = False
288 self.send_queue = []237 self.send_queue = []
289 self.priority_queue = []238 self.priority_queue = []
239 # Reset default version in command routing dict
240 for cmd in self.pjlink_functions:
241 self.pjlink_functions[cmd]["version"] = PJLINK_VALID_CMD[cmd]['default']
290242
291 def process_command(self, cmd, data):243 def process_command(self, cmd, data, *args, **kwargs):
292 """244 """
293 Verifies any return error code. Calls the appropriate command handler.245 Verifies any return error code. Calls the appropriate command handler.
294246
@@ -320,9 +272,25 @@
320 return self.change_status(status=E_AUTHENTICATION)272 return self.change_status(status=E_AUTHENTICATION)
321 # Command checks already passed273 # Command checks already passed
322 log.debug('({ip}) Calling function for {cmd}'.format(ip=self.entry.name, cmd=cmd))274 log.debug('({ip}) Calling function for {cmd}'.format(ip=self.entry.name, cmd=cmd))
323 self.pjlink_functions[cmd](data=data)275 self.pjlink_functions[cmd]["method"](data=data, *args, **kwargs)
324276
325 def process_avmt(self, data):277 def process_ackn(self, data, host, port):
278 """
279 Process the ACKN command.
280
281 :param data: Data in packet
282 :param host: IP address of sending host
283 :param port: Port received on
284 """
285 log.debug('({ip}) Processing ACKN packet'.format(ip=self.entry.name))
286 if host not in self.ackn_list:
287 log.debug('({ip}) Adding {host} to ACKN list'.format(ip=self.entry.name, host=host))
288 self.ackn_list[host] = {'data': data,
289 'port': port}
290 else:
291 log.warning('({ip}) Host {host} already replied - ignoring'.format(ip=self.entry.name, host=host))
292
293 def process_avmt(self, data, *args, **kwargs):
326 """294 """
327 Process shutter and speaker status. See PJLink specification for format.295 Process shutter and speaker status. See PJLink specification for format.
328 Update self.mute (audio) and self.shutter (video shutter).296 Update self.mute (audio) and self.shutter (video shutter).
@@ -351,7 +319,7 @@
351 self.projectorUpdateIcons.emit()319 self.projectorUpdateIcons.emit()
352 return320 return
353321
354 def process_clss(self, data):322 def process_clss(self, data, *args, **kwargs):
355 """323 """
356 PJLink class that this projector supports. See PJLink specification for format.324 PJLink class that this projector supports. See PJLink specification for format.
357 Updates self.class.325 Updates self.class.
@@ -367,12 +335,13 @@
367 # Due to stupid projectors not following standards (Optoma, BenQ comes to mind),335 # Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
368 # AND the different responses that can be received, the semi-permanent way to336 # AND the different responses that can be received, the semi-permanent way to
369 # fix the class reply is to just remove all non-digit characters.337 # fix the class reply is to just remove all non-digit characters.
370 try:338 chk = re.findall('\d', data)
371 clss = re.findall('\d', data)[0] # Should only be the first match339 if len(chk) < 1:
372 except IndexError:
373 log.error('({ip}) No numbers found in class version reply "{data}" - '340 log.error('({ip}) No numbers found in class version reply "{data}" - '
374 'defaulting to class "1"'.format(ip=self.entry.name, data=data))341 'defaulting to class "1"'.format(ip=self.entry.name, data=data))
375 clss = '1'342 clss = '1'
343 else:
344 clss = chk[0] # Should only be the first match
376 elif not data.isdigit():345 elif not data.isdigit():
377 log.error('({ip}) NAN CLSS version reply "{data}" - '346 log.error('({ip}) NAN CLSS version reply "{data}" - '
378 'defaulting to class "1"'.format(ip=self.entry.name, data=data))347 'defaulting to class "1"'.format(ip=self.entry.name, data=data))
@@ -383,6 +352,11 @@
383 log.debug('({ip}) Setting pjlink_class for this projector '352 log.debug('({ip}) Setting pjlink_class for this projector '
384 'to "{data}"'.format(ip=self.entry.name,353 'to "{data}"'.format(ip=self.entry.name,
385 data=self.pjlink_class))354 data=self.pjlink_class))
355 # Update method class versions
356 for cmd in self.pjlink_functions:
357 if self.pjlink_class in PJLINK_VALID_CMD[cmd]['version']:
358 self.pjlink_functions[cmd]['version'] = self.pjlink_class
359
386 # Since we call this one on first connect, setup polling from here360 # Since we call this one on first connect, setup polling from here
387 if not self.no_poll:361 if not self.no_poll:
388 log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.entry.name))362 log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.entry.name))
@@ -391,7 +365,7 @@
391365
392 return366 return
393367
394 def process_erst(self, data):368 def process_erst(self, data, *args, **kwargs):
395 """369 """
396 Error status. See PJLink Specifications for format.370 Error status. See PJLink Specifications for format.
397 Updates self.projector_errors371 Updates self.projector_errors
@@ -443,7 +417,7 @@
443 PJLINK_ERST_STATUS[other]417 PJLINK_ERST_STATUS[other]
444 return418 return
445419
446 def process_inf1(self, data):420 def process_inf1(self, data, *args, **kwargs):
447 """421 """
448 Manufacturer name set in projector.422 Manufacturer name set in projector.
449 Updates self.manufacturer423 Updates self.manufacturer
@@ -455,7 +429,7 @@
455 data=self.manufacturer))429 data=self.manufacturer))
456 return430 return
457431
458 def process_inf2(self, data):432 def process_inf2(self, data, *args, **kwargs):
459 """433 """
460 Projector Model set in projector.434 Projector Model set in projector.
461 Updates self.model.435 Updates self.model.
@@ -466,7 +440,7 @@
466 log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.entry.name, data=self.model))440 log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.entry.name, data=self.model))
467 return441 return
468442
469 def process_info(self, data):443 def process_info(self, data, *args, **kwargs):
470 """444 """
471 Any extra info set in projector.445 Any extra info set in projector.
472 Updates self.other_info.446 Updates self.other_info.
@@ -477,7 +451,7 @@
477 log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.entry.name, data=self.other_info))451 log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.entry.name, data=self.other_info))
478 return452 return
479453
480 def process_inpt(self, data):454 def process_inpt(self, data, *args, **kwargs):
481 """455 """
482 Current source input selected. See PJLink specification for format.456 Current source input selected. See PJLink specification for format.
483 Update self.source457 Update self.source
@@ -499,7 +473,7 @@
499 log.debug('({ip}) Setting data source to "{data}"'.format(ip=self.entry.name, data=self.source))473 log.debug('({ip}) Setting data source to "{data}"'.format(ip=self.entry.name, data=self.source))
500 return474 return
501475
502 def process_inst(self, data):476 def process_inst(self, data, *args, **kwargs):
503 """477 """
504 Available source inputs. See PJLink specification for format.478 Available source inputs. See PJLink specification for format.
505 Updates self.source_available479 Updates self.source_available
@@ -516,7 +490,7 @@
516 data=self.source_available))490 data=self.source_available))
517 return491 return
518492
519 def process_lamp(self, data):493 def process_lamp(self, data, *args, **kwargs):
520 """494 """
521 Lamp(s) status. See PJLink Specifications for format.495 Lamp(s) status. See PJLink Specifications for format.
522 Data may have more than 1 lamp to process.496 Data may have more than 1 lamp to process.
@@ -542,7 +516,18 @@
542 self.lamp = lamps516 self.lamp = lamps
543 return517 return
544518
545 def process_name(self, data):519 def process_lkup(self, data, host, port):
520 """
521 Process reply indicating remote is available for connection
522
523 :param data: Data packet from remote
524 :param host: Remote IP address
525 :param port: Local port packet received on
526 """
527 # TODO: Check if autoconnect is enabled and connect?
528 pass
529
530 def process_name(self, data, *args, **kwargs):
546 """531 """
547 Projector name set in projector.532 Projector name set in projector.
548 Updates self.pjlink_name533 Updates self.pjlink_name
@@ -553,7 +538,7 @@
553 log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.entry.name, data=self.pjlink_name))538 log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.entry.name, data=self.pjlink_name))
554 return539 return
555540
556 def process_pjlink(self, data):541 def process_pjlink(self, data, *args, **kwargs):
557 """542 """
558 Process initial socket connection to terminal.543 Process initial socket connection to terminal.
559544
@@ -594,7 +579,7 @@
594 # Since this is an initial connection, make it a priority just in case579 # Since this is an initial connection, make it a priority just in case
595 return self.send_command(cmd="CLSS", salt=data_hash, priority=True)580 return self.send_command(cmd="CLSS", salt=data_hash, priority=True)
596581
597 def process_powr(self, data):582 def process_powr(self, data, *args, **kwargs):
598 """583 """
599 Power status. See PJLink specification for format.584 Power status. See PJLink specification for format.
600 Update self.power with status. Update icons if change from previous setting.585 Update self.power with status. Update icons if change from previous setting.
@@ -617,7 +602,7 @@
617 log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.entry.name, data=data))602 log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.entry.name, data=data))
618 return603 return
619604
620 def process_rfil(self, data):605 def process_rfil(self, data, *args, **kwargs):
621 """606 """
622 Process replacement filter type607 Process replacement filter type
623 """608 """
@@ -628,7 +613,7 @@
628 log.warning('({ip}) Saved model: "{old}"'.format(ip=self.entry.name, old=self.model_filter))613 log.warning('({ip}) Saved model: "{old}"'.format(ip=self.entry.name, old=self.model_filter))
629 log.warning('({ip}) New model: "{new}"'.format(ip=self.entry.name, new=data))614 log.warning('({ip}) New model: "{new}"'.format(ip=self.entry.name, new=data))
630615
631 def process_rlmp(self, data):616 def process_rlmp(self, data, *args, **kwargs):
632 """617 """
633 Process replacement lamp type618 Process replacement lamp type
634 """619 """
@@ -639,7 +624,7 @@
639 log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.entry.name, old=self.model_lamp))624 log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.entry.name, old=self.model_lamp))
640 log.warning('({ip}) New lamp: "{new}"'.format(ip=self.entry.name, new=data))625 log.warning('({ip}) New lamp: "{new}"'.format(ip=self.entry.name, new=data))
641626
642 def process_snum(self, data):627 def process_snum(self, data, *args, **kwargs):
643 """628 """
644 Serial number of projector.629 Serial number of projector.
645630
@@ -659,7 +644,20 @@
659 log.warning('({ip}) NOT saving serial number'.format(ip=self.entry.name))644 log.warning('({ip}) NOT saving serial number'.format(ip=self.entry.name))
660 self.serial_no_received = data645 self.serial_no_received = data
661646
662 def process_sver(self, data):647 def process_srch(self, data, host, port):
648 """
649 Process the SRCH command.
650
651 SRCH is processed by terminals so we ignore any packet.
652
653 :param data: Data in packet
654 :param host: IP address of sending host
655 :param port: Port received on
656 """
657 log.warning('(UDP) SRCH packet received from {host} - ignoring'.format(host=host))
658 return
659
660 def process_sver(self, data, *args, **kwargs):
663 """661 """
664 Software version of projector662 Software version of projector
665 """663 """
@@ -716,6 +714,7 @@
716 self.pin = self.entry.pin714 self.pin = self.entry.pin
717 self.port = self.entry.port715 self.port = self.entry.port
718 self.pjlink_class = PJLINK_CLASS if self.entry.pjlink_class is None else self.entry.pjlink_class716 self.pjlink_class = PJLINK_CLASS if self.entry.pjlink_class is None else self.entry.pjlink_class
717 self.ackn_list = {} # Replies from online projectors (Class 2 option)
719 self.db_update = False # Use to check if db needs to be updated prior to exiting718 self.db_update = False # Use to check if db needs to be updated prior to exiting
720 # Poll time 20 seconds unless called with something else719 # Poll time 20 seconds unless called with something else
721 self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000720 self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
@@ -916,7 +915,10 @@
916 """915 """
917 Clean out extraneous stuff in the buffer.916 Clean out extraneous stuff in the buffer.
918 """917 """
919 log.warning('({ip}) {message}'.format(ip=self.entry.name, message='Invalid packet' if msg is None else msg))918 log.debug('({ip}) Cleaning buffer - msg = "{message}"'.format(ip=self.entry.name, message=msg))
919 if msg is None:
920 msg = 'Invalid packet'
921 log.warning('({ip}) {message}'.format(ip=self.entry.name, message=msg))
920 self.send_busy = False922 self.send_busy = False
921 trash_count = 0923 trash_count = 0
922 while self.bytesAvailable() > 0:924 while self.bytesAvailable() > 0:
@@ -960,7 +962,7 @@
960 self.socket_timer.stop()962 self.socket_timer.stop()
961 return self.get_data(buff=read, ip=self.ip)963 return self.get_data(buff=read, ip=self.ip)
962964
963 def get_data(self, buff, ip=None):965 def get_data(self, buff, ip=None, *args, **kwargs):
964 """966 """
965 Process received data967 Process received data
966968
@@ -973,45 +975,61 @@
973 ip = self.ip975 ip = self.ip
974 log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.entry.name, ip_in=ip, buff=buff))976 log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.entry.name, ip_in=ip, buff=buff))
975 # NOTE: Class2 has changed to some values being UTF-8977 # NOTE: Class2 has changed to some values being UTF-8
976 data_in = decode(buff, 'utf-8')978 if isinstance(buff, bytes):
979 data_in = decode(buff, 'utf-8')
980 else:
981 data_in = buff
977 data = data_in.strip()982 data = data_in.strip()
978 # Initial packet checks983 # Initial packet checks
979 if (len(data) < 7):984 if (len(data) < 7):
980 self._trash_buffer(msg='get_data(): Invalid packet - length')985 self._trash_buffer(msg='get_data(): Invalid packet - length')
981 return self.receive_data_signal()986 return self.receive_data_signal()
982 elif len(data) > self.max_size:987 elif len(data) > self.max_size:
983 self._trash_buffer(msg='get_data(): Invalid packet - too long')988 self._trash_buffer(msg='get_data(): Invalid packet - too long ({length} bytes)'.format(length=len(data)))
984 return self.receive_data_signal()989 return self.receive_data_signal()
985 elif not data.startswith(PJLINK_PREFIX):990 elif not data.startswith(PJLINK_PREFIX):
986 self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing')991 self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing')
987 return self.receive_data_signal()992 return self.receive_data_signal()
988 elif '=' not in data:993 elif data[6] != '=':
989 self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="')994 self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="')
990 return self.receive_data_signal()995 return self.receive_data_signal()
991 log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.entry.name, data=data))996 log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.entry.name, data=data))
992 header, data = data.split('=')997 header, data = data.split('=')
998 log.debug('({ip}) get_data() header="{header}" data="{data}"'.format(ip=self.entry.name,
999 header=header, data=data))
993 # At this point, the header should contain:1000 # At this point, the header should contain:
994 # "PVCCCC"1001 # "PVCCCC"
995 # Where:1002 # Where:
996 # P = PJLINK_PREFIX1003 # P = PJLINK_PREFIX
997 # V = PJLink class or version1004 # V = PJLink class or version
998 # C = PJLink command1005 # C = PJLink command
1006 version, cmd = header[1], header[2:].upper()
1007 log.debug('({ip}) get_data() version="{version}" cmd="{cmd}"'.format(ip=self.entry.name,
1008 version=version, cmd=cmd))
1009 # TODO: Below commented for now since it seems to cause issues with testing some invalid data.
1010 # Revisit after more refactoring is finished.
1011 '''
999 try:1012 try:
1000 version, cmd = header[1], header[2:].upper()1013 version, cmd = header[1], header[2:].upper()
1014 log.debug('({ip}) get_data() version="{version}" cmd="{cmd}"'.format(ip=self.entry.name,
1015 version=version, cmd=cmd))
1001 except ValueError as e:1016 except ValueError as e:
1002 self.change_status(E_INVALID_DATA)1017 self.change_status(E_INVALID_DATA)
1003 log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.entry.name, data=data_in))1018 log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.entry.name, data=data_in))
1004 self._trash_buffer('get_data(): Expected header + command + data')1019 self._trash_buffer('get_data(): Expected header + command + data')
1005 return self.receive_data_signal()1020 return self.receive_data_signal()
1021 '''
1006 if cmd not in PJLINK_VALID_CMD:1022 if cmd not in PJLINK_VALID_CMD:
1007 log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.entry.name,1023 self._trash_buffer('get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.entry.name,
1008 data=cmd))1024 data=cmd))
1009 self._trash_buffer(msg='get_data(): Unknown command "{data}"'.format(data=cmd))1025 return self.receive_data_signal()
1010 return self.receive_data_signal()1026 elif version not in PJLINK_VALID_CMD[cmd]['version']:
1011 if int(self.pjlink_class) < int(version):1027 self._trash_buffer(msg='get_data() Command reply version does not match a valid command version')
1028 return self.receive_data_signal()
1029 elif int(self.pjlink_class) < int(version):
1012 log.warning('({ip}) get_data(): Projector returned class reply higher '1030 log.warning('({ip}) get_data(): Projector returned class reply higher '
1013 'than projector stated class'.format(ip=self.entry.name))1031 'than projector stated class'.format(ip=self.entry.name))
1014 self.process_command(cmd, data)1032 self.process_command(cmd, data, *args, **kwargs)
1015 return self.receive_data_signal()1033 return self.receive_data_signal()
10161034
1017 @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)1035 @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)
@@ -1063,16 +1081,7 @@
1063 data=opts,1081 data=opts,
1064 salt='' if salt is None1082 salt='' if salt is None
1065 else ' with hash'))1083 else ' with hash'))
1066 cmd_ver = PJLINK_VALID_CMD[cmd]['version']1084 header = PJLINK_HEADER.format(linkclass=self.pjlink_functions[cmd]["version"])
1067 if self.pjlink_class in PJLINK_VALID_CMD[cmd]['version']:
1068 header = PJLINK_HEADER.format(linkclass=self.pjlink_class)
1069 elif len(cmd_ver) == 1 and (int(cmd_ver[0]) < int(self.pjlink_class)):
1070 # Typically a class 1 only command
1071 header = PJLINK_HEADER.format(linkclass=cmd_ver[0])
1072 else:
1073 # NOTE: Once we get to version 3 then think about looping
1074 log.error('({ip}): send_command(): PJLink class check issue? Aborting'.format(ip=self.entry.name))
1075 return
1076 out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt,1085 out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt,
1077 header=header,1086 header=header,
1078 command=cmd,1087 command=cmd,
10791088
=== modified file 'tests/openlp_core/projectors/test_projector_db.py'
--- tests/openlp_core/projectors/test_projector_db.py 2018-01-13 05:41:42 +0000
+++ tests/openlp_core/projectors/test_projector_db.py 2018-04-20 06:16:23 +0000
@@ -29,12 +29,15 @@
29import shutil29import shutil
30from tempfile import mkdtemp30from tempfile import mkdtemp
31from unittest import TestCase31from unittest import TestCase
32from unittest.mock import patch32from unittest.mock import MagicMock, patch
3333
34from openlp.core.common.registry import Registry
34from openlp.core.lib.db import upgrade_db35from openlp.core.lib.db import upgrade_db
35from openlp.core.projectors import upgrade36from openlp.core.projectors import upgrade
36from openlp.core.projectors.constants import PJLINK_PORT37from openlp.core.projectors.constants import PJLINK_PORT
37from openlp.core.projectors.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source38from openlp.core.projectors.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source
39from openlp.core.ui.mainwindow import MainWindow
40from tests.helpers.testmixin import TestMixin
38from tests.resources.projector.data import TEST_DB_PJLINK1, TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA41from tests.resources.projector.data import TEST_DB_PJLINK1, TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA
39from tests.utils.constants import TEST_RESOURCES_PATH42from tests.utils.constants import TEST_RESOURCES_PATH
4043
@@ -122,7 +125,7 @@
122 assert updated_to_version == latest_version, 'The projector DB should have been upgrade to the latest version'125 assert updated_to_version == latest_version, 'The projector DB should have been upgrade to the latest version'
123126
124127
125class TestProjectorDB(TestCase):128class TestProjectorDB(TestCase, TestMixin):
126 """129 """
127 Test case for ProjectorDB130 Test case for ProjectorDB
128 """131 """
@@ -131,6 +134,33 @@
131 """134 """
132 Set up anything necessary for all tests135 Set up anything necessary for all tests
133 """136 """
137 # Create a test app to keep from segfaulting
138 Registry.create()
139 self.registry = Registry()
140 self.setup_application()
141 # Mock cursor busy/normal methods.
142 self.app.set_busy_cursor = MagicMock()
143 self.app.set_normal_cursor = MagicMock()
144 self.app.args = []
145 Registry().register('application', self.app)
146 Registry().set_flag('no_web_server', True)
147 # Mock classes and methods used by mainwindow.
148 with patch('openlp.core.ui.mainwindow.SettingsForm'), \
149 patch('openlp.core.ui.mainwindow.ImageManager'), \
150 patch('openlp.core.ui.mainwindow.LiveController'), \
151 patch('openlp.core.ui.mainwindow.PreviewController'), \
152 patch('openlp.core.ui.mainwindow.OpenLPDockWidget'), \
153 patch('openlp.core.ui.mainwindow.QtWidgets.QToolBox'), \
154 patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget'), \
155 patch('openlp.core.ui.mainwindow.ServiceManager'), \
156 patch('openlp.core.ui.mainwindow.ThemeManager'), \
157 patch('openlp.core.ui.mainwindow.ProjectorManager'), \
158 patch('openlp.core.ui.mainwindow.Renderer'), \
159 patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \
160 patch('openlp.core.ui.mainwindow.server.HttpServer'):
161 self.main_window = MainWindow()
162
163 # Create a temporary database directory and database
134 self.tmp_folder = mkdtemp(prefix='openlp_')164 self.tmp_folder = mkdtemp(prefix='openlp_')
135 tmpdb_url = 'sqlite:///{db}'.format(db=os.path.join(self.tmp_folder, TEST_DB))165 tmpdb_url = 'sqlite:///{db}'.format(db=os.path.join(self.tmp_folder, TEST_DB))
136 mocked_init_url.return_value = tmpdb_url166 mocked_init_url.return_value = tmpdb_url
@@ -139,9 +169,12 @@
139 def tearDown(self):169 def tearDown(self):
140 """170 """
141 Clean up171 Clean up
172
173 Delete all the C++ objects at the end so that we don't have a segfault
142 """174 """
143 self.projector.session.close()175 self.projector.session.close()
144 self.projector = None176 self.projector = None
177 del self.main_window
145 # Ignore errors since windows can have problems with locked files178 # Ignore errors since windows can have problems with locked files
146 shutil.rmtree(self.tmp_folder, ignore_errors=True)179 shutil.rmtree(self.tmp_folder, ignore_errors=True)
147180
148181
=== renamed file 'tests/openlp_core/projectors/test_projectoreditform.py' => 'tests/openlp_core/projectors/test_projector_editform.py'
=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py'
--- tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2018-01-13 05:41:42 +0000
+++ tests/openlp_core/projectors/test_projector_pjlink_cmd_routing.py 2018-04-20 06:16:23 +0000
@@ -39,30 +39,31 @@
39 """39 """
40 Tests for the PJLink module command routing40 Tests for the PJLink module command routing
41 """41 """
42 def test_get_data_unknown_command(self):42 @patch.object(openlp.core.projectors.pjlink, 'log')
43 def test_get_data_unknown_command(self, mock_log):
43 """44 """
44 Test not a valid command45 Test not a valid command
45 """46 """
46 # GIVEN: Test object47 # GIVEN: Test object
47 with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \48 pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
48 patch.object(openlp.core.projectors.pjlink.PJLink, '_trash_buffer') as mock_buffer:49 pjlink.pjlink_functions = MagicMock()
4950 log_warning_text = [call('({ip}) get_data(): Invalid packet - '
50 pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)51 'unknown command "UNKN"'.format(ip=pjlink.name))]
51 pjlink.pjlink_functions = MagicMock()52 log_debug_text = [call('(___TEST_ONE___) get_data(ip="111.111.111.111" buffer="%1UNKN=Huh?"'),
52 log_warning_text = [call('({ip}) get_data(): Invalid packet - '53 call('(___TEST_ONE___) get_data(): Checking new data "%1UNKN=Huh?"'),
53 'unknown command "UNK"'.format(ip=pjlink.name))]54 call('(___TEST_ONE___) get_data() header="%1UNKN" data="Huh?"'),
54 log_debug_text = [call('({ip}) get_data(ip="111.111.111.111" '55 call('(___TEST_ONE___) get_data() version="1" cmd="UNKN"'),
55 'buffer="b\'%1UNK=Huh?\'"'.format(ip=pjlink.name)),56 call('(___TEST_ONE___) Cleaning buffer - msg = "get_data(): '
56 call('({ip}) get_data(): Checking new data "%1UNK=Huh?"'.format(ip=pjlink.name))]57 'Invalid packet - unknown command "UNKN""'),
5758 call('(___TEST_ONE___) Finished cleaning buffer - 0 bytes dropped')]
58 # WHEN: get_data called with an unknown command59
59 pjlink.get_data(buff='{prefix}1UNK=Huh?'.format(prefix=PJLINK_PREFIX).encode('utf-8'))60 # WHEN: get_data called with an unknown command
6061 pjlink.get_data(buff='{prefix}1UNKN=Huh?'.format(prefix=PJLINK_PREFIX))
61 # THEN: Appropriate log entries should have been made and methods called/not called62
62 mock_log.debug.assert_has_calls(log_debug_text)63 # THEN: Appropriate log entries should have been made and methods called/not called
63 mock_log.warning.assert_has_calls(log_warning_text)64 mock_log.warning.assert_has_calls(log_warning_text)
64 assert pjlink.pjlink_functions.called is False, 'Should not have accessed pjlink_functions'65 mock_log.debug.assert_has_calls(log_debug_text)
65 assert mock_buffer.called is True, 'Should have called _trash_buffer'66 assert pjlink.pjlink_functions.called is False, 'Should not have accessed pjlink_functions'
6667
67 def test_process_command_call_clss(self):68 def test_process_command_call_clss(self):
68 """69 """
@@ -219,7 +220,6 @@
219 """220 """
220 Test command returned success221 Test command returned success
221 """222 """
222 # GIVEN: Initial mocks and data
223 # GIVEN: Test object and mocks223 # GIVEN: Test object and mocks
224 with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \224 with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
225 patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command, \225 patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command, \
226226
=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_commands_02.py'
--- tests/openlp_core/projectors/test_projector_pjlink_commands_02.py 2018-01-13 05:41:42 +0000
+++ tests/openlp_core/projectors/test_projector_pjlink_commands_02.py 2018-04-20 06:16:23 +0000
@@ -22,14 +22,14 @@
22"""22"""
23Package to test the openlp.core.projectors.pjlink commands package.23Package to test the openlp.core.projectors.pjlink commands package.
24"""24"""
25from unittest import TestCase25from unittest import TestCase, skip
26from unittest.mock import patch, call26from unittest.mock import patch, call
2727
28import openlp.core.projectors.pjlink28import openlp.core.projectors.pjlink
29from openlp.core.projectors.constants import S_CONNECTED, S_OFF, S_ON29from openlp.core.projectors.constants import PJLINK_PORT, S_CONNECTED, S_OFF, S_ON
30from openlp.core.projectors.db import Projector30from openlp.core.projectors.db import Projector
31from openlp.core.projectors.pjlink import PJLink31from openlp.core.projectors.pjlink import PJLink, PJLinkUDP
32from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT, TEST1_DATA32from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT, TEST1_DATA, TEST2_DATA
3333
3434
35class TestPJLinkCommands(TestCase):35class TestPJLinkCommands(TestCase):
@@ -235,3 +235,114 @@
235 mock_log.error.assert_has_calls(log_check)235 mock_log.error.assert_has_calls(log_check)
236 assert 1 == mock_disconnect_from_host.call_count, 'Should have only been called once'236 assert 1 == mock_disconnect_from_host.call_count, 'Should have only been called once'
237 mock_send_command.assert_not_called()237 mock_send_command.assert_not_called()
238
239 @skip('Change to pjlink_udp.get_datagram() call')
240 @patch.object(openlp.core.projectors.pjlink, 'log')
241 def test_process_ackn_duplicate(self, mock_log):
242 """
243 Test process_ackn method with multiple calls with same data
244 """
245 # TODO: Change this to call pjlink_udp.get_datagram() so ACKN can be processed properly
246
247 # GIVEN: Test setup
248 pjlink = PJLink(projector=self.test_list[0])
249 check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT}}
250 log_warn_calls = [call('(___TEST_ONE___) Host {host} already replied - '
251 'ignoring'.format(host=TEST1_DATA['ip']))]
252 log_debug_calls = [call('PJlinkCommands(args=() kwargs={})'),
253 call('(___TEST_ONE___) reset_information() connect status is S_NOT_CONNECTED'),
254 call('(___TEST_ONE___) Processing ACKN packet'),
255 call('(___TEST_ONE___) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip'])),
256 call('(___TEST_ONE___) Processing ACKN packet')]
257
258 # WHEN: process_ackn called twice with same data
259 pjlink.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
260 pjlink.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
261
262 # THEN: pjlink_udp.ack_list should equal test_list
263 # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
264 if pjlink.ackn_list != check_list:
265 # Check this way so we can print differences to stdout
266 print('\nackn_list: ', pjlink.ackn_list)
267 print('test_list: ', check_list, '\n')
268 assert pjlink.ackn_list == check_list
269 mock_log.debug.assert_has_calls(log_debug_calls)
270 mock_log.warning.assert_has_calls(log_warn_calls)
271
272 @skip('Change to pjlink_udp.get_datagram() call')
273 @patch.object(openlp.core.projectors.pjlink, 'log')
274 def test_process_ackn_multiple(self, mock_log):
275 """
276 Test process_ackn method with multiple calls
277 """
278 # TODO: Change this to call pjlink_udp.get_datagram() so ACKN can be processed properly
279
280 # GIVEN: Test setup
281 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
282 check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT},
283 TEST2_DATA['ip']: {'data': TEST2_DATA['mac_adx'], 'port': PJLINK_PORT}}
284 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
285 call('(UDP) Processing ACKN packet'),
286 call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip'])),
287 call('(UDP) Processing ACKN packet'),
288 call('(UDP) Adding {host} to ACKN list'.format(host=TEST2_DATA['ip']))]
289
290 # WHEN: process_ackn called twice with different data
291 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
292 pjlink_udp.process_ackn(data=TEST2_DATA['mac_adx'], host=TEST2_DATA['ip'], port=PJLINK_PORT)
293
294 # THEN: pjlink_udp.ack_list should equal test_list
295 # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
296 if pjlink_udp.ackn_list != check_list:
297 # Check this way so we can print differences to stdout
298 print('\nackn_list: ', pjlink_udp.ackn_list)
299 print('test_list: ', check_list)
300 assert pjlink_udp.ackn_list == check_list
301 mock_log.debug.assert_has_calls(log_debug_calls)
302
303 @skip('Change to pjlink_udp.get_datagram() call')
304 @patch.object(openlp.core.projectors.pjlink, 'log')
305 def test_process_ackn_single(self, mock_log):
306 """
307 Test process_ackn method with single call
308 """
309 # TODO: Change this to call pjlink_udp.get_datagram() so ACKN can be processed properly
310
311 # GIVEN: Test setup
312 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
313 check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT}}
314 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
315 call('(UDP) Processing ACKN packet'),
316 call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip']))]
317
318 # WHEN: process_ackn called twice with different data
319 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
320
321 # THEN: pjlink_udp.ack_list should equal test_list
322 # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
323 if pjlink_udp.ackn_list != check_list:
324 # Check this way so we can print differences to stdout
325 print('\nackn_list: ', pjlink_udp.ackn_list)
326 print('test_list: ', check_list)
327 assert pjlink_udp.ackn_list == check_list
328 mock_log.debug.assert_has_calls(log_debug_calls)
329
330 @skip('Change to pjlink_udp.get_datagram() call')
331 @patch.object(openlp.core.projectors.pjlink, 'log')
332 def test_process_srch(self, mock_log):
333 """
334 Test process_srch method
335 """
336 # TODO: Change this to call pjlink_udp.get_datagram() so ACKN can be processed properly
337
338 # GIVEN: Test setup
339 log_warn_calls = [call('(UDP) SRCH packet received from {ip} - ignoring'.format(ip=TEST1_DATA['ip'])), ]
340 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'), ]
341 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
342
343 # WHEN: process_srch called
344 pjlink_udp.process_srch(data=None, host=TEST1_DATA['ip'], port=PJLINK_PORT)
345
346 # THEN: log entries should be entered
347 mock_log.warning.assert_has_calls(log_warn_calls)
348 mock_log.debug.assert_has_calls(log_debug_calls)
238349
=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_udp.py'
--- tests/openlp_core/projectors/test_projector_pjlink_udp.py 2018-02-11 11:42:13 +0000
+++ tests/openlp_core/projectors/test_projector_pjlink_udp.py 2018-04-20 06:16:23 +0000
@@ -28,10 +28,10 @@
28from unittest.mock import call, patch28from unittest.mock import call, patch
2929
30import openlp.core.projectors.pjlink30import openlp.core.projectors.pjlink
31from openlp.core.projectors.constants import PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_PREFIX31from openlp.core.projectors.constants import PJLINK_PORT
3232
33from openlp.core.projectors.db import Projector33from openlp.core.projectors.db import Projector
34from openlp.core.projectors.pjlink import PJLinkUDP34from openlp.core.projectors.pjlink import PJLinkUDP, PJLink
35from tests.resources.projector.data import TEST1_DATA, TEST2_DATA35from tests.resources.projector.data import TEST1_DATA, TEST2_DATA
3636
3737
@@ -43,7 +43,8 @@
43 """43 """
44 Setup generic test conditions44 Setup generic test conditions
45 """45 """
46 self.test_list = [Projector(**TEST1_DATA), Projector(**TEST2_DATA)]46 self.test_list = [PJLink(projector=Projector(**TEST1_DATA)),
47 PJLink(projector=Projector(**TEST2_DATA))]
4748
48 def tearDown(self):49 def tearDown(self):
49 """50 """
@@ -52,132 +53,6 @@
52 self.test_list = None53 self.test_list = None
5354
54 @patch.object(openlp.core.projectors.pjlink, 'log')55 @patch.object(openlp.core.projectors.pjlink, 'log')
55 def test_get_datagram_data_invalid_class(self, mock_log):
56 """
57 Test get_datagram with invalid class number
58 """
59 # GIVEN: Test setup
60 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
61 log_warn_calls = [call('(UDP) Invalid packet - missing/invalid PJLink class version')]
62 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
63 call('(UDP) get_datagram() - Receiving data'),
64 call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
65 call('(UDP) packet "%1ACKN=11:11:11:11:11:11"')]
66 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
67 patch.object(pjlink_udp, 'readDatagram') as mock_read:
68 mock_datagram.return_value = 24
69 mock_read.return_value = ('{prefix}1ACKN={mac}'.format(prefix=PJLINK_PREFIX, mac=TEST1_DATA['mac_adx']),
70 TEST1_DATA['ip'], PJLINK_PORT)
71
72 # WHEN: get_datagram called with 0 bytes ready
73 pjlink_udp.get_datagram()
74
75 # THEN: Log entries should be made and method returns
76 mock_log.debug.assert_has_calls(log_debug_calls)
77 mock_log.warn.assert_has_calls(log_warn_calls)
78
79 @patch.object(openlp.core.projectors.pjlink, 'log')
80 def test_get_datagram_data_invalid_command(self, mock_log):
81 """
82 Test get_datagram with invalid PJLink UDP command
83 """
84 # GIVEN: Test setup
85 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
86 log_warn_calls = [call('(UDP) Invalid packet - not a valid PJLink UDP reply')]
87 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
88 call('(UDP) get_datagram() - Receiving data'),
89 call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
90 call('(UDP) packet "%2DUMB=11:11:11:11:11:11"')]
91 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
92 patch.object(pjlink_udp, 'readDatagram') as mock_read:
93 mock_datagram.return_value = 24
94 mock_read.return_value = ('{prefix}2DUMB={mac}'.format(prefix=PJLINK_PREFIX, mac=TEST1_DATA['mac_adx']),
95 TEST1_DATA['ip'], PJLINK_PORT)
96
97 # WHEN: get_datagram called with 0 bytes ready
98 pjlink_udp.get_datagram()
99
100 # THEN: Log entries should be made and method returns
101 mock_log.debug.assert_has_calls(log_debug_calls)
102 mock_log.warn.assert_has_calls(log_warn_calls)
103
104 @patch.object(openlp.core.projectors.pjlink, 'log')
105 def test_get_datagram_data_invalid_prefix(self, mock_log):
106 """
107 Test get_datagram when prefix != PJLINK_PREFIX
108 """
109 # GIVEN: Test setup
110 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
111 log_warn_calls = [call('(UDP) Invalid packet - does not start with PJLINK_PREFIX')]
112 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
113 call('(UDP) get_datagram() - Receiving data'),
114 call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
115 call('(UDP) packet "$2ACKN=11:11:11:11:11:11"')]
116 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
117 patch.object(pjlink_udp, 'readDatagram') as mock_read:
118 mock_datagram.return_value = 24
119 mock_read.return_value = ('{prefix}2ACKN={mac}'.format(prefix='$', mac=TEST1_DATA['mac_adx']),
120 TEST1_DATA['ip'], PJLINK_PORT)
121
122 # WHEN: get_datagram called with 0 bytes ready
123 pjlink_udp.get_datagram()
124
125 # THEN: Log entries should be made and method returns
126 mock_log.debug.assert_has_calls(log_debug_calls)
127 mock_log.warn.assert_has_calls(log_warn_calls)
128
129 @patch.object(openlp.core.projectors.pjlink, 'log')
130 def test_get_datagram_data_invalid_separator(self, mock_log):
131 """
132 Test get_datagram when separator not equal to =
133 """
134 # GIVEN: Test setup
135 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
136 log_warn_calls = [call('(UDP) Invalid packet - separator missing')]
137 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
138 call('(UDP) get_datagram() - Receiving data'),
139 call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
140 call('(UDP) packet "%2ACKN 11:11:11:11:11:11"')]
141 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
142 patch.object(pjlink_udp, 'readDatagram') as mock_read:
143 mock_datagram.return_value = 24
144 mock_read.return_value = ('{prefix}2ACKN {mac}'.format(prefix=PJLINK_PREFIX, mac=TEST1_DATA['mac_adx']),
145 TEST1_DATA['ip'], PJLINK_PORT)
146
147 # WHEN: get_datagram called with 0 bytes ready
148 pjlink_udp.get_datagram()
149
150 # THEN: Log entries should be made and method returns
151 mock_log.debug.assert_has_calls(log_debug_calls)
152 mock_log.warn.assert_has_calls(log_warn_calls)
153
154 @patch.object(openlp.core.projectors.pjlink, 'log')
155 def test_get_datagram_data_long(self, mock_log):
156 """
157 Test get_datagram when datagram > PJLINK_MAX_PACKET
158 """
159 # GIVEN: Test setup
160 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
161 log_warn_calls = [call('(UDP) Invalid packet - length too long')]
162 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
163 call('(UDP) get_datagram() - Receiving data'),
164 call('(UDP) 143 bytes received from 111.111.111.111 on port 4352'),
165 call('(UDP) packet "%2ACKN={long}"'.format(long='X' * PJLINK_MAX_PACKET))]
166 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
167 patch.object(pjlink_udp, 'readDatagram') as mock_read:
168 mock_datagram.return_value = PJLINK_MAX_PACKET + 7
169 mock_read.return_value = ('{prefix}2ACKN={long}'.format(prefix=PJLINK_PREFIX,
170 long='X' * PJLINK_MAX_PACKET),
171 TEST1_DATA['ip'], PJLINK_PORT)
172
173 # WHEN: get_datagram called with 0 bytes ready
174 pjlink_udp.get_datagram()
175
176 # THEN: Log entries should be made and method returns
177 mock_log.debug.assert_has_calls(log_debug_calls)
178 mock_log.warn.assert_has_calls(log_warn_calls)
179
180 @patch.object(openlp.core.projectors.pjlink, 'log')
181 def test_get_datagram_data_negative_zero_length(self, mock_log):56 def test_get_datagram_data_negative_zero_length(self, mock_log):
182 """57 """
183 Test get_datagram when pendingDatagramSize = 058 Test get_datagram when pendingDatagramSize = 0
@@ -196,7 +71,7 @@
196 pjlink_udp.get_datagram()71 pjlink_udp.get_datagram()
19772
198 # THEN: Log entries should be made and method returns73 # THEN: Log entries should be made and method returns
199 mock_log.warn.assert_has_calls(log_warn_calls)74 mock_log.warning.assert_has_calls(log_warn_calls)
200 mock_log.debug.assert_has_calls(log_debug_calls)75 mock_log.debug.assert_has_calls(log_debug_calls)
20176
202 @patch.object(openlp.core.projectors.pjlink, 'log')77 @patch.object(openlp.core.projectors.pjlink, 'log')
@@ -206,41 +81,18 @@
206 """81 """
207 # GIVEN: Test setup82 # GIVEN: Test setup
208 pjlink_udp = PJLinkUDP(projector_list=self.test_list)83 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
209 log_warn_calls = [call('(UDP) Invalid packet - not enough data')]84 log_warn_calls = [call('(UDP) get_datagram() called when pending data size is 0')]
210 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),85 log_debug_calls = [call('(UDP) get_datagram() - Receiving data')]
211 call('(UDP) get_datagram() - Receiving data')]
212 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \86 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
213 patch.object(pjlink_udp, 'readDatagram') as mock_read:87 patch.object(pjlink_udp, 'readDatagram') as mock_read:
214 mock_datagram.return_value = 188 mock_datagram.return_value = 0
215 mock_read.return_value = ('', TEST1_DATA['ip'], PJLINK_PORT)89 mock_read.return_value = ('', TEST1_DATA['ip'], PJLINK_PORT)
21690
217 # WHEN: get_datagram called with 0 bytes ready91 # WHEN: get_datagram called with 0 bytes ready
218 pjlink_udp.get_datagram()92 pjlink_udp.get_datagram()
21993
220 # THEN: Log entries should be made and method returns94 # THEN: Log entries should be made and method returns
221 mock_log.warn.assert_has_calls(log_warn_calls)95 mock_log.warning.assert_has_calls(log_warn_calls)
222 mock_log.debug.assert_has_calls(log_debug_calls)
223
224 @patch.object(openlp.core.projectors.pjlink, 'log')
225 def test_get_datagram_data_short(self, mock_log):
226 """
227 Test get_datagram when data length < 8
228 """
229 # GIVEN: Test setup
230 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
231 log_warn_calls = [call('(UDP) Invalid packet - not enough data')]
232 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
233 call('(UDP) get_datagram() - Receiving data')]
234 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
235 patch.object(pjlink_udp, 'readDatagram') as mock_read:
236 mock_datagram.return_value = 6
237 mock_read.return_value = ('{prefix}2ACKN'.format(prefix=PJLINK_PREFIX), TEST1_DATA['ip'], PJLINK_PORT)
238
239 # WHEN: get_datagram called with 0 bytes ready
240 pjlink_udp.get_datagram()
241
242 # THEN: Log entries should be made and method returns
243 mock_log.warn.assert_has_calls(log_warn_calls)
244 mock_log.debug.assert_has_calls(log_debug_calls)96 mock_log.debug.assert_has_calls(log_debug_calls)
24597
246 @patch.object(openlp.core.projectors.pjlink, 'log')98 @patch.object(openlp.core.projectors.pjlink, 'log')
@@ -260,101 +112,5 @@
260 pjlink_udp.get_datagram()112 pjlink_udp.get_datagram()
261113
262 # THEN: Log entries should be made and method returns114 # THEN: Log entries should be made and method returns
263 mock_log.warn.assert_has_calls(log_warn_calls)115 mock_log.warning.assert_has_calls(log_warn_calls)
264 mock_log.debug.assert_has_calls(log_debug_calls)116 mock_log.debug.assert_has_calls(log_debug_calls)
265
266 @patch.object(openlp.core.projectors.pjlink, 'log')
267 def test_process_ackn_duplicate(self, mock_log):
268 """
269 Test process_ackn method with multiple calls with same data
270 """
271 # GIVEN: Test setup
272 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
273 check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT}}
274 log_warn_calls = [call('(UDP) Host {host} already replied - ignoring'.format(host=TEST1_DATA['ip']))]
275 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
276 call('(UDP) Processing ACKN packet'),
277 call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip'])),
278 call('(UDP) Processing ACKN packet')]
279
280 # WHEN: process_ackn called twice with same data
281 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
282 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
283
284 # THEN: pjlink_udp.ack_list should equal test_list
285 # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
286 if pjlink_udp.ackn_list != check_list:
287 # Check this way so we can print differences to stdout
288 print('\nackn_list: ', pjlink_udp.ackn_list)
289 print('test_list: ', check_list)
290 assert pjlink_udp.ackn_list == check_list
291 mock_log.debug.assert_has_calls(log_debug_calls)
292 mock_log.warn.assert_has_calls(log_warn_calls)
293
294 @patch.object(openlp.core.projectors.pjlink, 'log')
295 def test_process_ackn_multiple(self, mock_log):
296 """
297 Test process_ackn method with multiple calls
298 """
299 # GIVEN: Test setup
300 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
301 check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT},
302 TEST2_DATA['ip']: {'data': TEST2_DATA['mac_adx'], 'port': PJLINK_PORT}}
303 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
304 call('(UDP) Processing ACKN packet'),
305 call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip'])),
306 call('(UDP) Processing ACKN packet'),
307 call('(UDP) Adding {host} to ACKN list'.format(host=TEST2_DATA['ip']))]
308
309 # WHEN: process_ackn called twice with different data
310 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
311 pjlink_udp.process_ackn(data=TEST2_DATA['mac_adx'], host=TEST2_DATA['ip'], port=PJLINK_PORT)
312
313 # THEN: pjlink_udp.ack_list should equal test_list
314 # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
315 if pjlink_udp.ackn_list != check_list:
316 # Check this way so we can print differences to stdout
317 print('\nackn_list: ', pjlink_udp.ackn_list)
318 print('test_list: ', check_list)
319 assert pjlink_udp.ackn_list == check_list
320 mock_log.debug.assert_has_calls(log_debug_calls)
321
322 @patch.object(openlp.core.projectors.pjlink, 'log')
323 def test_process_ackn_single(self, mock_log):
324 """
325 Test process_ackn method with single call
326 """
327 # GIVEN: Test setup
328 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
329 check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT}}
330 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
331 call('(UDP) Processing ACKN packet'),
332 call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip']))]
333
334 # WHEN: process_ackn called twice with different data
335 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
336
337 # THEN: pjlink_udp.ack_list should equal test_list
338 # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
339 if pjlink_udp.ackn_list != check_list:
340 # Check this way so we can print differences to stdout
341 print('\nackn_list: ', pjlink_udp.ackn_list)
342 print('test_list: ', check_list)
343 assert pjlink_udp.ackn_list == check_list
344 mock_log.debug.assert_has_calls(log_debug_calls)
345
346 @patch.object(openlp.core.projectors.pjlink, 'log')
347 def test_process_srch(self, mock_log):
348 """
349 Test process_srch method
350 """
351 # GIVEN: Test setup
352 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
353 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
354 call('(UDP) SRCH packet received - ignoring')]
355
356 # WHEN: process_srch called
357 pjlink_udp.process_srch(data=None, host=None, port=None)
358
359 # THEN: debug log entry should be entered
360 mock_log.debug.assert_has_calls(log_debug_calls)
361117
=== renamed file 'tests/openlp_core/projectors/test_projectorsourceform.py' => 'tests/openlp_core/projectors/test_projector_sourceform.py'