PJLink Updates N

This commit is contained in:
Ken Roberts 2017-12-25 00:44:30 -08:00
parent 6e2bc427a7
commit 598a373929
7 changed files with 1433 additions and 1185 deletions

View File

@ -32,9 +32,128 @@ log.debug('projector_constants loaded')
# Set common constants. # Set common constants.
CR = chr(0x0D) # \r CR = chr(0x0D) # \r
LF = chr(0x0A) # \n LF = chr(0x0A) # \n
PJLINK_PORT = 4352 PJLINK_CLASS = '1' # Default to class 1 until we query the projector
TIMEOUT = 30.0
PJLINK_MAX_PACKET = 136 PJLINK_MAX_PACKET = 136
PJLINK_PREFIX = '%'
PJLINK_PORT = 4352
PJLINK_SUFFIX = CR
PJLINK_TIMEOUT = 30.0
# Error and status codes
S_OK = E_OK = 0 # E_OK included since I sometimes forget
# Error codes. Start at 200 so we don't duplicate system error codes.
E_GENERAL = 200 # Unknown error
E_NOT_CONNECTED = 201
E_UNDEFINED = 202 # PJLink ERR1
E_PARAMETER = 203 # PJLink ERR2
E_UNAVAILABLE = 204 # PJLink ERR3
E_PROJECTOR = 205 # PJLink ERR4
E_AUTHENTICATION = 206 # PJLink ERRA
E_NO_AUTHENTICATION = 207 # PJLink authentication mismatch between projector and program
E_PREFIX = 208 # PJLink invalid prefix for packet
E_CLASS = 209 # PJLink class version mismatch
E_INVALID_DATA = 210
E_WARN = 211
E_ERROR = 212
E_FAN = 213
E_LAMP = 214
E_TEMP = 215
E_COVER = 216
E_FILTER = 217
E_UNKNOWN = 218
# Remap Qt socket error codes to local error codes
E_CONNECTION_REFUSED = 230
E_REMOTE_HOST_CLOSED_CONNECTION = 231
E_HOST_NOT_FOUND = 232
E_SOCKET_ACCESS = 233
E_SOCKET_RESOURCE = 234
E_SOCKET_TIMEOUT = 235
E_DATAGRAM_TOO_LARGE = 236
E_NETWORK = 237
E_ADDRESS_IN_USE = 238
E_SOCKET_ADDRESS_NOT_AVAILABLE = 239
E_UNSUPPORTED_SOCKET_OPERATION = 240
E_PROXY_AUTHENTICATION_REQUIRED = 241
E_SLS_HANDSHAKE_FAILED = 242
E_UNFINISHED_SOCKET_OPERATION = 243
E_PROXY_CONNECTION_REFUSED = 244
E_PROXY_CONNECTION_CLOSED = 245
E_PROXY_CONNECTION_TIMEOUT = 246
E_PROXY_NOT_FOUND = 247
E_PROXY_PROTOCOL = 248
E_UNKNOWN_SOCKET_ERROR = 249
# Status codes start at 300
# Remap Qt socket states to local status codes
S_NOT_CONNECTED = 300
S_HOST_LOOKUP = 301
S_CONNECTING = 302
S_CONNECTED = 303
S_BOUND = 304
S_LISTENING = 305 # Listed as internal use only in QAbstractSocket
S_CLOSING = 306
# Projector states
S_INITIALIZE = 310
S_STATUS = 311
S_OFF = 312
S_STANDBY = 313
S_WARMUP = 314
S_ON = 315
S_COOLDOWN = 316
S_INFO = 317
# Information that does not affect status
S_NETWORK_IDLE = 400
S_NETWORK_SENDING = 401
S_NETWORK_RECEIVING = 402
# Map PJlink errors to local status
PJLINK_ERRORS = {
'ERRA': E_AUTHENTICATION, # Authentication error
'ERR1': E_UNDEFINED, # Undefined command error
'ERR2': E_PARAMETER, # Invalid parameter error
'ERR3': E_UNAVAILABLE, # Projector busy
'ERR4': E_PROJECTOR, # Projector or display failure
E_AUTHENTICATION: 'ERRA',
E_UNDEFINED: 'ERR1',
E_PARAMETER: 'ERR2',
E_UNAVAILABLE: 'ERR3',
E_PROJECTOR: 'ERR4'
}
# Map QAbstractSocketState enums to local status
QSOCKET_STATE = {
0: S_NOT_CONNECTED, # 'UnconnectedState',
1: S_HOST_LOOKUP, # 'HostLookupState',
2: S_CONNECTING, # 'ConnectingState',
3: S_CONNECTED, # 'ConnectedState',
4: S_BOUND, # 'BoundState',
5: S_LISTENING, # 'ListeningState' - Noted as "Internal Use Only" on Qt website
6: S_CLOSING, # 'ClosingState',
S_NOT_CONNECTED: 0,
S_HOST_LOOKUP: 1,
S_CONNECTING: 2,
S_CONNECTED: 3,
S_BOUND: 4,
S_LISTENING: 5,
S_CLOSING: 6
}
PROJECTOR_STATE = [
S_INITIALIZE,
S_STATUS,
S_OFF,
S_STANDBY,
S_WARMUP,
S_ON,
S_COOLDOWN,
S_INFO
]
# NOTE: Changed format to account for some commands are both class 1 and 2 # NOTE: Changed format to account for some commands are both class 1 and 2
PJLINK_VALID_CMD = { PJLINK_VALID_CMD = {
'ACKN': {'version': ['2', ], 'ACKN': {'version': ['2', ],
@ -144,227 +263,140 @@ PJLINK_VALID_CMD = {
} }
} }
# QAbstractSocketState enums converted to string CONNECTION_ERRORS = [
S_QSOCKET_STATE = { E_ADDRESS_IN_USE,
0: 'QSocketState - UnconnectedState', E_CONNECTION_REFUSED,
1: 'QSocketState - HostLookupState', E_DATAGRAM_TOO_LARGE,
2: 'QSocketState - ConnectingState', E_HOST_NOT_FOUND,
3: 'QSocketState - ConnectedState', E_NETWORK,
4: 'QSocketState - BoundState', E_NOT_CONNECTED,
5: 'QSocketState - ListeningState (internal use only)', E_PROXY_AUTHENTICATION_REQUIRED,
6: 'QSocketState - ClosingState', E_PROXY_CONNECTION_CLOSED,
'UnconnectedState': 0, E_PROXY_CONNECTION_REFUSED,
'HostLookupState': 1, E_PROXY_CONNECTION_TIMEOUT,
'ConnectingState': 2, E_PROXY_NOT_FOUND,
'ConnectedState': 3, E_PROXY_PROTOCOL,
'BoundState': 4, E_REMOTE_HOST_CLOSED_CONNECTION,
'ListeningState': 5, E_SLS_HANDSHAKE_FAILED,
'ClosingState': 6 E_SOCKET_ACCESS,
} E_SOCKET_ADDRESS_NOT_AVAILABLE,
E_SOCKET_RESOURCE,
E_SOCKET_TIMEOUT,
E_UNFINISHED_SOCKET_OPERATION,
E_UNKNOWN_SOCKET_ERROR,
E_UNSUPPORTED_SOCKET_OPERATION
]
# Error and status codes PROJECTOR_ERRORS = [
S_OK = E_OK = 0 # E_OK included since I sometimes forget E_AUTHENTICATION,
# Error codes. Start at 200 so we don't duplicate system error codes. E_CLASS,
E_GENERAL = 200 # Unknown error E_INVALID_DATA,
E_NOT_CONNECTED = 201 E_NO_AUTHENTICATION,
E_FAN = 202 E_PARAMETER,
E_LAMP = 203 E_PREFIX,
E_TEMP = 204 E_PROJECTOR,
E_COVER = 205 E_UNAVAILABLE,
E_FILTER = 206 E_UNDEFINED,
E_NO_AUTHENTICATION = 207 # PIN set and no authentication set on projector E_UNKNOWN
E_UNDEFINED = 208 # ERR1 ]
E_PARAMETER = 209 # ERR2
E_UNAVAILABLE = 210 # ERR3
E_PROJECTOR = 211 # ERR4
E_INVALID_DATA = 212
E_WARN = 213
E_ERROR = 214
E_AUTHENTICATION = 215 # ERRA
E_CLASS = 216
E_PREFIX = 217
# Remap Qt socket error codes to projector error codes # Show status code as string
E_CONNECTION_REFUSED = 230 STATUS_CODE = {
E_REMOTE_HOST_CLOSED_CONNECTION = 231
E_HOST_NOT_FOUND = 232
E_SOCKET_ACCESS = 233
E_SOCKET_RESOURCE = 234
E_SOCKET_TIMEOUT = 235
E_DATAGRAM_TOO_LARGE = 236
E_NETWORK = 237
E_ADDRESS_IN_USE = 238
E_SOCKET_ADDRESS_NOT_AVAILABLE = 239
E_UNSUPPORTED_SOCKET_OPERATION = 240
E_PROXY_AUTHENTICATION_REQUIRED = 241
E_SLS_HANDSHAKE_FAILED = 242
E_UNFINISHED_SOCKET_OPERATION = 243
E_PROXY_CONNECTION_REFUSED = 244
E_PROXY_CONNECTION_CLOSED = 245
E_PROXY_CONNECTION_TIMEOUT = 246
E_PROXY_NOT_FOUND = 247
E_PROXY_PROTOCOL = 248
E_UNKNOWN_SOCKET_ERROR = -1
# Status codes start at 300
S_NOT_CONNECTED = 300
S_CONNECTING = 301
S_CONNECTED = 302
S_INITIALIZE = 303
S_STATUS = 304
S_OFF = 305
S_STANDBY = 306
S_WARMUP = 307
S_ON = 308
S_COOLDOWN = 309
S_INFO = 310
# Information that does not affect status
S_NETWORK_SENDING = 400
S_NETWORK_RECEIVED = 401
CONNECTION_ERRORS = {
E_NOT_CONNECTED, E_NO_AUTHENTICATION, E_AUTHENTICATION, E_CLASS,
E_PREFIX, E_CONNECTION_REFUSED, E_REMOTE_HOST_CLOSED_CONNECTION,
E_HOST_NOT_FOUND, E_SOCKET_ACCESS, E_SOCKET_RESOURCE, E_SOCKET_TIMEOUT,
E_DATAGRAM_TOO_LARGE, E_NETWORK, E_ADDRESS_IN_USE, E_SOCKET_ADDRESS_NOT_AVAILABLE,
E_UNSUPPORTED_SOCKET_OPERATION, E_PROXY_AUTHENTICATION_REQUIRED,
E_SLS_HANDSHAKE_FAILED, E_UNFINISHED_SOCKET_OPERATION, E_PROXY_CONNECTION_REFUSED,
E_PROXY_CONNECTION_CLOSED, E_PROXY_CONNECTION_TIMEOUT, E_PROXY_NOT_FOUND,
E_PROXY_PROTOCOL, E_UNKNOWN_SOCKET_ERROR
}
PJLINK_ERRORS = {
'ERRA': E_AUTHENTICATION, # Authentication error
'ERR1': E_UNDEFINED, # Undefined command error
'ERR2': E_PARAMETER, # Invalid parameter error
'ERR3': E_UNAVAILABLE, # Projector busy
'ERR4': E_PROJECTOR, # Projector or display failure
E_AUTHENTICATION: 'ERRA',
E_UNDEFINED: 'ERR1',
E_PARAMETER: 'ERR2',
E_UNAVAILABLE: 'ERR3',
E_PROJECTOR: 'ERR4'
}
# Map error/status codes to string
ERROR_STRING = {
0: 'S_OK',
E_GENERAL: 'E_GENERAL',
E_NOT_CONNECTED: 'E_NOT_CONNECTED',
E_FAN: 'E_FAN',
E_LAMP: 'E_LAMP',
E_TEMP: 'E_TEMP',
E_COVER: 'E_COVER',
E_FILTER: 'E_FILTER',
E_AUTHENTICATION: 'E_AUTHENTICATION',
E_NO_AUTHENTICATION: 'E_NO_AUTHENTICATION',
E_UNDEFINED: 'E_UNDEFINED',
E_PARAMETER: 'E_PARAMETER',
E_UNAVAILABLE: 'E_UNAVAILABLE',
E_PROJECTOR: 'E_PROJECTOR',
E_INVALID_DATA: 'E_INVALID_DATA',
E_WARN: 'E_WARN',
E_ERROR: 'E_ERROR',
E_CLASS: 'E_CLASS',
E_PREFIX: 'E_PREFIX', # Last projector error
E_CONNECTION_REFUSED: 'E_CONNECTION_REFUSED', # First QtSocket error
E_REMOTE_HOST_CLOSED_CONNECTION: 'E_REMOTE_HOST_CLOSED_CONNECTION',
E_HOST_NOT_FOUND: 'E_HOST_NOT_FOUND',
E_SOCKET_ACCESS: 'E_SOCKET_ACCESS',
E_SOCKET_RESOURCE: 'E_SOCKET_RESOURCE',
E_SOCKET_TIMEOUT: 'E_SOCKET_TIMEOUT',
E_DATAGRAM_TOO_LARGE: 'E_DATAGRAM_TOO_LARGE',
E_NETWORK: 'E_NETWORK',
E_ADDRESS_IN_USE: 'E_ADDRESS_IN_USE', E_ADDRESS_IN_USE: 'E_ADDRESS_IN_USE',
E_SOCKET_ADDRESS_NOT_AVAILABLE: 'E_SOCKET_ADDRESS_NOT_AVAILABLE', E_AUTHENTICATION: 'E_AUTHENTICATION',
E_UNSUPPORTED_SOCKET_OPERATION: 'E_UNSUPPORTED_SOCKET_OPERATION', E_CLASS: 'E_CLASS',
E_CONNECTION_REFUSED: 'E_CONNECTION_REFUSED',
E_COVER: 'E_COVER',
E_DATAGRAM_TOO_LARGE: 'E_DATAGRAM_TOO_LARGE',
E_ERROR: 'E_ERROR',
E_FAN: 'E_FAN',
E_FILTER: 'E_FILTER',
E_GENERAL: 'E_GENERAL',
E_HOST_NOT_FOUND: 'E_HOST_NOT_FOUND',
E_INVALID_DATA: 'E_INVALID_DATA',
E_LAMP: 'E_LAMP',
E_NETWORK: 'E_NETWORK',
E_NO_AUTHENTICATION: 'E_NO_AUTHENTICATION',
E_NOT_CONNECTED: 'E_NOT_CONNECTED',
E_PARAMETER: 'E_PARAMETER',
E_PREFIX: 'E_PREFIX',
E_PROJECTOR: 'E_PROJECTOR',
E_PROXY_AUTHENTICATION_REQUIRED: 'E_PROXY_AUTHENTICATION_REQUIRED', E_PROXY_AUTHENTICATION_REQUIRED: 'E_PROXY_AUTHENTICATION_REQUIRED',
E_SLS_HANDSHAKE_FAILED: 'E_SLS_HANDSHAKE_FAILED',
E_UNFINISHED_SOCKET_OPERATION: 'E_UNFINISHED_SOCKET_OPERATION',
E_PROXY_CONNECTION_REFUSED: 'E_PROXY_CONNECTION_REFUSED',
E_PROXY_CONNECTION_CLOSED: 'E_PROXY_CONNECTION_CLOSED', E_PROXY_CONNECTION_CLOSED: 'E_PROXY_CONNECTION_CLOSED',
E_PROXY_CONNECTION_REFUSED: 'E_PROXY_CONNECTION_REFUSED',
E_PROXY_CONNECTION_TIMEOUT: 'E_PROXY_CONNECTION_TIMEOUT', E_PROXY_CONNECTION_TIMEOUT: 'E_PROXY_CONNECTION_TIMEOUT',
E_PROXY_NOT_FOUND: 'E_PROXY_NOT_FOUND', E_PROXY_NOT_FOUND: 'E_PROXY_NOT_FOUND',
E_PROXY_PROTOCOL: 'E_PROXY_PROTOCOL', E_PROXY_PROTOCOL: 'E_PROXY_PROTOCOL',
E_UNKNOWN_SOCKET_ERROR: 'E_UNKNOWN_SOCKET_ERROR' E_REMOTE_HOST_CLOSED_CONNECTION: 'E_REMOTE_HOST_CLOSED_CONNECTION',
} E_SLS_HANDSHAKE_FAILED: 'E_SLS_HANDSHAKE_FAILED',
E_SOCKET_ACCESS: 'E_SOCKET_ACCESS',
STATUS_STRING = { E_SOCKET_ADDRESS_NOT_AVAILABLE: 'E_SOCKET_ADDRESS_NOT_AVAILABLE',
S_NOT_CONNECTED: 'S_NOT_CONNECTED', E_SOCKET_RESOURCE: 'E_SOCKET_RESOURCE',
S_CONNECTING: 'S_CONNECTING', E_SOCKET_TIMEOUT: 'E_SOCKET_TIMEOUT',
S_CONNECTED: 'S_CONNECTED', E_TEMP: 'E_TEMP',
S_STATUS: 'S_STATUS', E_UNAVAILABLE: 'E_UNAVAILABLE',
S_OFF: 'S_OFF', E_UNDEFINED: 'E_UNDEFINED',
S_INITIALIZE: 'S_INITIALIZE', E_UNFINISHED_SOCKET_OPERATION: 'E_UNFINISHED_SOCKET_OPERATION',
S_STANDBY: 'S_STANDBY', E_UNKNOWN: 'E_UNKNOWN',
S_WARMUP: 'S_WARMUP', E_UNKNOWN_SOCKET_ERROR: 'E_UNKNOWN_SOCKET_ERROR',
S_ON: 'S_ON', E_UNSUPPORTED_SOCKET_OPERATION: 'E_UNSUPPORTED_SOCKET_OPERATION',
E_WARN: 'E_WARN',
S_BOUND: 'S_BOUND',
S_COOLDOWN: 'S_COOLDOWN', S_COOLDOWN: 'S_COOLDOWN',
S_CLOSING: 'S_CLOSING',
S_CONNECTED: 'S_CONNECTED',
S_CONNECTING: 'S_CONNECTING',
S_HOST_LOOKUP: 'S_HOST_LOOKUP',
S_INFO: 'S_INFO', S_INFO: 'S_INFO',
S_INITIALIZE: 'S_INITIALIZE',
S_LISTENING: 'S_LISTENING',
S_NETWORK_RECEIVING: 'S_NETWORK_RECEIVING',
S_NETWORK_SENDING: 'S_NETWORK_SENDING', S_NETWORK_SENDING: 'S_NETWORK_SENDING',
S_NETWORK_RECEIVED: 'S_NETWORK_RECEIVED' S_NETWORK_IDLE: 'S_NETWORK_IDLE',
S_NOT_CONNECTED: 'S_NOT_CONNECTED',
S_OFF: 'S_OFF',
S_OK: 'S_OK', # S_OK or E_OK
S_ON: 'S_ON',
S_STANDBY: 'S_STANDBY',
S_STATUS: 'S_STATUS',
S_WARMUP: 'S_WARMUP',
} }
# Map error/status codes to message strings # Map status codes to message strings
ERROR_MSG = { STATUS_MSG = {
E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
E_GENERAL: translate('OpenLP.ProjectorConstants', 'General projector error'),
E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected error'),
E_LAMP: translate('OpenLP.ProjectorConstants', 'Lamp error'),
E_FAN: translate('OpenLP.ProjectorConstants', 'Fan error'),
E_TEMP: translate('OpenLP.ProjectorConstants', 'High temperature detected'),
E_COVER: translate('OpenLP.ProjectorConstants', 'Cover open detected'),
E_FILTER: translate('OpenLP.ProjectorConstants', 'Check filter'),
E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'Authentication Error'),
E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'Undefined Command'),
E_PARAMETER: translate('OpenLP.ProjectorConstants', 'Invalid Parameter'),
E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'Projector Busy'),
E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'Projector/Display Error'),
E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'Invalid packet received'),
E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'),
E_ERROR: translate('OpenLP.ProjectorConstants', 'Error condition detected'),
E_CLASS: translate('OpenLP.ProjectorConstants', 'PJLink class not supported'),
E_PREFIX: translate('OpenLP.ProjectorConstants', 'Invalid prefix character'),
E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'The connection was refused by the peer (or timed out)'),
E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
'The remote host closed the connection'),
E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'),
E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants',
'The socket operation failed because the application '
'lacked the required privileges'),
E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants',
'The local system ran out of resources (e.g., too many sockets)'),
E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants',
'The socket operation timed out'),
E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants',
'The datagram was larger than the operating system\'s limit'),
E_NETWORK: translate('OpenLP.ProjectorConstants',
'An error occurred with the network (Possibly someone pulled the plug?)'),
E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants', E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants',
'The address specified with socket.bind() ' 'The address specified with socket.bind() '
'is already in use and was set to be exclusive'), 'is already in use and was set to be exclusive'),
E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants', E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'PJLink returned "ERRA: Authentication Error"'),
'The address specified to socket.bind() ' E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'does not belong to the host'), 'The connection was refused by the peer (or timed out)'),
E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants', E_COVER: translate('OpenLP.ProjectorConstants', 'Projector cover open detected'),
'The requested socket operation is not supported by the local ' E_CLASS: translate('OpenLP.ProjectorConstants', 'PJLink class not supported'),
'operating system (e.g., lack of IPv6 support)'), E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants',
"The datagram was larger than the operating system's limit"),
E_ERROR: translate('OpenLP.ProjectorConstants', 'Error condition detected'),
E_FAN: translate('OpenLP.ProjectorConstants', 'Projector fan error'),
E_FILTER: translate('OpenLP.ProjectorConstants', 'Projector check filter'),
E_GENERAL: translate('OpenLP.ProjectorConstants', 'General projector error'),
E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'),
E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'PJLink invalid packet received'),
E_LAMP: translate('OpenLP.ProjectorConstants', 'Projector lamp error'),
E_NETWORK: translate('OpenLP.ProjectorConstants',
'An error occurred with the network (Possibly someone pulled the plug?)'),
E_NO_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'PJlink authentication Mismatch Error'),
E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Projector not connected error'),
E_PARAMETER: translate('OpenLP.ProjectorConstants', 'PJLink returned "ERR2: Invalid Parameter"'),
E_PREFIX: translate('OpenLP.ProjectorConstants', 'PJLink Invalid prefix character'),
E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'PJLink returned "ERR4: Projector/Display Error"'),
E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants', E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants',
'The socket is using a proxy, ' 'The socket is using a proxy, '
'and the proxy requires authentication'), 'and the proxy requires authentication'),
E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants',
'The SSL/TLS handshake failed'),
E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'The last operation attempted has not finished yet '
'(still in progress in the background)'),
E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'Could not contact the proxy server because the connection '
'to that server was denied'),
E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants', E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants',
'The connection to the proxy server was closed unexpectedly ' 'The connection to the proxy server was closed unexpectedly '
'(before the connection to the final peer was established)'), '(before the connection to the final peer was established)'),
E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'Could not contact the proxy server because the connection '
'to that server was denied'),
E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants', E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants',
'The connection to the proxy server timed out or the proxy ' 'The connection to the proxy server timed out or the proxy '
'server stopped responding in the authentication phase.'), 'server stopped responding in the authentication phase.'),
@ -373,51 +405,91 @@ ERROR_MSG = {
E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants', E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
'The connection negotiation with the proxy server failed because the ' 'The connection negotiation with the proxy server failed because the '
'response from the proxy server could not be understood'), 'response from the proxy server could not be understood'),
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'), E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'), 'The remote host closed the connection'),
S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'), E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants',
'The SSL/TLS handshake failed'),
E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants',
'The address specified to socket.bind() '
'does not belong to the host'),
E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants',
'The socket operation failed because the application '
'lacked the required privileges'),
E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants',
'The local system ran out of resources (e.g., too many sockets)'),
E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants',
'The socket operation timed out'),
E_TEMP: translate('OpenLP.ProjectorConstants', 'Projector high temperature detected'),
E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'PJLink returned "ERR3: Busy"'),
E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'PJLink returned "ERR1: Undefined Command"'),
E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'The last operation attempted has not finished yet '
'(still in progress in the background)'),
E_UNKNOWN: translate('OpenLP.ProjectorConstants', 'Unknown condiction detected'),
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified socket error occurred'),
E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'The requested socket operation is not supported by the local '
'operating system (e.g., lack of IPv6 support)'),
E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'),
S_BOUND: translate('OpenLP.ProjectorConstants', 'Socket is bount to an address or port'),
S_CLOSING: translate('OpenLP.ProjectorConstants', 'Socket is about to close'),
S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'), S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'),
S_STATUS: translate('OpenLP.ProjectorConstants', 'Getting status'), S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
S_OFF: translate('OpenLP.ProjectorConstants', 'Off'),
S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'),
S_STANDBY: translate('OpenLP.ProjectorConstants', 'Power in standby'),
S_WARMUP: translate('OpenLP.ProjectorConstants', 'Warmup in progress'),
S_ON: translate('OpenLP.ProjectorConstants', 'Power is on'),
S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'), S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'),
S_HOST_LOOKUP: translate('OpenLP.ProjectorConstants', 'Performing a host name lookup'),
S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'), S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'),
S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'),
S_LISTENING: translate('OpenLP.ProjectorConstants', 'Socket it listening (internal use only)'),
S_NETWORK_IDLE: translate('OpenLP.ProjectorConstants', 'No network activity at this time'),
S_NETWORK_RECEIVING: translate('OpenLP.ProjectorConstants', 'Received data'),
S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'Sending data'), S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'Sending data'),
S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data') S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not Connected'),
S_OFF: translate('OpenLP.ProjectorConstants', 'Off'),
S_OK: translate('OpenLP.ProjectorConstants', 'OK'),
S_ON: translate('OpenLP.ProjectorConstants', 'Power is on'),
S_STANDBY: translate('OpenLP.ProjectorConstants', 'Power in standby'),
S_STATUS: translate('OpenLP.ProjectorConstants', 'Getting status'),
S_WARMUP: translate('OpenLP.ProjectorConstants', 'Warmup in progress'),
} }
# Map ERST return code positions to equipment # Map ERST reply positions to equipment
PJLINK_ERST_LIST = {
"FAN": translate('OpenLP.PJLink', 'Fan'),
"LAMP": translate('OpenLP.PJLink', 'Lamp'),
"TEMP": translate('OpenLP.PJLink', 'Temperature'),
"COVER": translate('OpenLP.PJLink', 'Cover'),
"FILTER": translate('OpenLP.PJLink', 'Filter'),
"OTHER": translate('OpenPL.PJLink', 'Other')
}
# Map projector item to ERST data position
PJLINK_ERST_DATA = { PJLINK_ERST_DATA = {
'DATA_LENGTH': 6, 'DATA_LENGTH': 6, # Zero based so enums are 0-5
0: 'FAN',
1: 'LAMP',
2: 'TEMP',
3: 'COVER',
4: 'FILTER',
5: 'OTHER',
'FAN': 0, 'FAN': 0,
'LAMP': 1, 'LAMP': 1,
'TEMP': 2, 'TEMP': 2,
'COVER': 3, 'COVER': 3,
'FILTER': 4, 'FILTER': 4,
'OTHER': 5 'OTHER': 5,
0: 'FAN',
1: 'LAMP',
2: 'TEMP',
3: 'COVER',
4: 'FILTER',
5: 'OTHER'
} }
# Map for ERST return codes to string # Map ERST reply codes to string
PJLINK_ERST_STATUS = { PJLINK_ERST_STATUS = {
'0': 'OK', '0': S_OK,
'1': ERROR_STRING[E_WARN], '1': E_WARN,
'2': ERROR_STRING[E_ERROR], '2': E_ERROR,
'OK': '0', S_OK: '0',
E_OK: '0',
E_WARN: '1', E_WARN: '1',
E_ERROR: '2' E_ERROR: '2'
} }
# Map for POWR return codes to status code # Map POWR return codes to status code
PJLINK_POWR_STATUS = { PJLINK_POWR_STATUS = {
'0': S_STANDBY, '0': S_STANDBY,
'1': S_ON, '1': S_ON,
@ -485,3 +557,7 @@ for source in PJLINK_DEFAULT_SOURCES:
label = "{source}{item}".format(source=source, item=item) label = "{source}{item}".format(source=source, item=item)
PJLINK_DEFAULT_CODES[label] = "{source} {item}".format(source=PJLINK_DEFAULT_SOURCES[source], PJLINK_DEFAULT_CODES[label] = "{source} {item}".format(source=PJLINK_DEFAULT_SOURCES[source],
item=PJLINK_DEFAULT_ITEMS[item]) item=PJLINK_DEFAULT_ITEMS[item])
# Remove temp variables so they don't become part of the module dict
del(source)
del(item)
del(label)

View File

@ -35,9 +35,25 @@ from openlp.core.common.registry import RegistryBase
from openlp.core.common.settings import Settings from openlp.core.common.settings import Settings
from openlp.core.lib.ui import create_widget_action from openlp.core.lib.ui import create_widget_action
from openlp.core.projectors import DialogSourceStyle from openlp.core.projectors import DialogSourceStyle
from openlp.core.projectors.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \ from openlp.core.projectors.constants import \
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \ E_AUTHENTICATION, \
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP E_ERROR, \
E_NETWORK, \
E_NOT_CONNECTED, \
E_UNKNOWN_SOCKET_ERROR, \
S_CONNECTED, \
S_CONNECTING, \
S_COOLDOWN, \
S_INITIALIZE, \
S_NOT_CONNECTED, \
S_OFF, \
S_ON, \
S_STANDBY, \
S_WARMUP, \
STATUS_CODE, \
STATUS_MSG, \
QSOCKET_STATE
from openlp.core.projectors.db import ProjectorDB from openlp.core.projectors.db import ProjectorDB
from openlp.core.projectors.pjlink import PJLink, PJLinkUDP from openlp.core.projectors.pjlink import PJLink, PJLinkUDP
from openlp.core.projectors.editform import ProjectorEditForm from openlp.core.projectors.editform import ProjectorEditForm
@ -440,11 +456,12 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
:param opt: Needed by PyQt5 :param opt: Needed by PyQt5
""" """
projector = item.data(QtCore.Qt.UserRole) projector = item.data(QtCore.Qt.UserRole)
if projector.link.state() != projector.link.ConnectedState: if QSOCKET_STATE[projector.link.state()] != S_CONNECTED:
try: try:
log.debug('ProjectorManager: Calling connect_to_host() on "{ip}"'.format(ip=projector.link.ip))
projector.link.connect_to_host() projector.link.connect_to_host()
except: except:
pass log.debug('ProjectorManager: "{ip}" already connected - skipping'.format(ip=projector.link.ip))
return return
def on_connect_projector(self, opt=None): def on_connect_projector(self, opt=None):
@ -647,7 +664,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
'Other info'), 'Other info'),
data=projector.link.other_info) data=projector.link.other_info)
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Power status'), message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Power status'),
data=ERROR_MSG[projector.link.power]) data=STATUS_MSG[projector.link.power])
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Shutter is'), message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Shutter is'),
data=translate('OpenLP.ProjectorManager', 'Closed') data=translate('OpenLP.ProjectorManager', 'Closed')
if projector.link.shutter if projector.link.shutter
@ -692,7 +709,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
else: else:
message += '<b>{data}</b>'.format(data=translate('OpenLP.ProjectorManager', 'Current errors/warnings')) message += '<b>{data}</b>'.format(data=translate('OpenLP.ProjectorManager', 'Current errors/warnings'))
for (key, val) in projector.link.projector_errors.items(): for (key, val) in projector.link.projector_errors.items():
message += '<b>{key}</b>: {data}<br />'.format(key=key, data=ERROR_MSG[val]) message += '<b>{key}</b>: {data}<br />'.format(key=key, data=STATUS_MSG[val])
QtWidgets.QMessageBox.information(self, translate('OpenLP.ProjectorManager', 'Projector Information'), message) QtWidgets.QMessageBox.information(self, translate('OpenLP.ProjectorManager', 'Projector Information'), message)
def _add_projector(self, projector): def _add_projector(self, projector):
@ -817,31 +834,18 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
if ip == list_item.link.ip: if ip == list_item.link.ip:
item = list_item item = list_item
break break
message = translate('OpenLP.ProjectorManager', 'No message') if msg is None else msg if item is None:
if status in STATUS_STRING: log.error('ProjectorManager: Unknown item "{ip}" - not updating status'.format(ip=ip))
status_code = STATUS_STRING[status] return
message = ERROR_MSG[status] if msg is None else msg elif item.status == status:
elif status in ERROR_STRING: log.debug('ProjectorManager: No status change for "{ip}" - not updating status'.format(ip=ip))
status_code = ERROR_STRING[status] return
message = ERROR_MSG[status] if msg is None else msg
else: item.status = status
status_code = status item.icon = QtGui.QIcon(QtGui.QPixmap(STATUS_ICONS[status]))
message = ERROR_MSG[status] if msg is None else msg log.debug('({name}) Updating icon with {code}'.format(name=item.link.name, code=STATUS_CODE[status]))
log.debug('({name}) updateStatus(status={status}) message: "{message}"'.format(name=item.link.name, item.widget.setIcon(item.icon)
status=status_code, return self.update_icons()
message=message))
if status in STATUS_ICONS:
if item.status == status:
return
item.status = status
item.icon = QtGui.QIcon(QtGui.QPixmap(STATUS_ICONS[status]))
if status in ERROR_STRING:
status_code = ERROR_STRING[status]
elif status in STATUS_STRING:
status_code = STATUS_STRING[status]
log.debug('({name}) Updating icon with {code}'.format(name=item.link.name, code=status_code))
item.widget.setIcon(item.icon)
self.update_icons()
def get_toolbar_item(self, name, enabled=False, hidden=False): def get_toolbar_item(self, name, enabled=False, hidden=False):
item = self.one_toolbar.findChild(QtWidgets.QAction, name) item = self.one_toolbar.findChild(QtWidgets.QAction, name)
@ -877,7 +881,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
self.get_toolbar_item('show_projector_multiple', hidden=True) self.get_toolbar_item('show_projector_multiple', hidden=True)
elif count == 1: elif count == 1:
projector = self.projector_list_widget.selectedItems()[0].data(QtCore.Qt.UserRole) projector = self.projector_list_widget.selectedItems()[0].data(QtCore.Qt.UserRole)
connected = projector.link.state() == projector.link.ConnectedState connected = QSOCKET_STATE[projector.link.state()] == S_CONNECTED
power = projector.link.power == S_ON power = projector.link.power == S_ON
self.get_toolbar_item('connect_projector_multiple', hidden=True) self.get_toolbar_item('connect_projector_multiple', hidden=True)
self.get_toolbar_item('disconnect_projector_multiple', hidden=True) self.get_toolbar_item('disconnect_projector_multiple', hidden=True)

View File

@ -54,11 +54,12 @@ from PyQt5 import QtCore, QtNetwork
from openlp.core.common import qmd5_hash from openlp.core.common import qmd5_hash
from openlp.core.common.i18n import translate from openlp.core.common.i18n import translate
from openlp.core.projectors.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \ from openlp.core.projectors.constants import CONNECTION_ERRORS, PJLINK_CLASS, PJLINK_DEFAULT_CODES, PJLINK_ERRORS, \
E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \ PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PREFIX, PJLINK_PORT, PJLINK_POWR_STATUS, \
E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \ PJLINK_SUFFIX, PJLINK_VALID_CMD, PROJECTOR_STATE, STATUS_CODE, STATUS_MSG, QSOCKET_STATE, \
PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \ E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \
STATUS_STRING, S_CONNECTED, S_CONNECTING, S_INFO, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_QSOCKET_STATE, S_STATUS E_OK, E_SOCKET_TIMEOUT, \
S_CONNECTED, S_CONNECTING, S_INFO, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
log.debug('pjlink loaded') log.debug('pjlink loaded')
@ -69,12 +70,9 @@ __all__ = ['PJLink']
SocketError = QtNetwork.QAbstractSocket.SocketError SocketError = QtNetwork.QAbstractSocket.SocketError
SocketSTate = QtNetwork.QAbstractSocket.SocketState SocketSTate = QtNetwork.QAbstractSocket.SocketState
PJLINK_PREFIX = '%'
PJLINK_CLASS = '1' # Default to class 1 until we query the projector
# Add prefix here, but defer linkclass expansion until later when we have the actual # Add prefix here, but defer linkclass expansion until later when we have the actual
# PJLink class for the command # PJLink class for the command
PJLINK_HEADER = '{prefix}{{linkclass}}'.format(prefix=PJLINK_PREFIX) PJLINK_HEADER = '{prefix}{{linkclass}}'.format(prefix=PJLINK_PREFIX)
PJLINK_SUFFIX = CR
class PJLinkUDP(QtNetwork.QUdpSocket): class PJLinkUDP(QtNetwork.QUdpSocket):
@ -136,8 +134,9 @@ class PJLinkCommands(object):
""" """
Initialize instance variables. Also used to reset projector-specific information to default. Initialize instance variables. Also used to reset projector-specific information to default.
""" """
conn_state = STATUS_CODE[QSOCKET_STATE[self.state()]]
log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip,
state=S_QSOCKET_STATE[self.state()])) state=conn_state))
self.fan = None # ERST self.fan = None # ERST
self.filter_time = None # FILT self.filter_time = None # FILT
self.lamp = None # LAMP self.lamp = None # LAMP
@ -183,42 +182,25 @@ class PJLinkCommands(object):
# Due to some replies should stay as mixed-case, validate using separate uppercase check # Due to some replies should stay as mixed-case, validate using separate uppercase check
_data = data.upper() _data = data.upper()
# Check if we have a future command not available yet # Check if we have a future command not available yet
if cmd not in PJLINK_VALID_CMD: if cmd not in self.pjlink_functions:
log.error('({ip}) Ignoring command="{cmd}" (Invalid/Unknown)'.format(ip=self.ip, cmd=cmd)) log.warning('({ip}) Unable to process command="{cmd}" (Future option?)'.format(ip=self.ip, cmd=cmd))
return return
elif _data == 'OK': elif _data == 'OK':
log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd)) log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd))
# A command returned successfully, so do a query on command to verify status # A command returned successfully, so do a query on command to verify status
return self.send_command(cmd=cmd) return self.send_command(cmd=cmd)
elif cmd not in self.pjlink_functions:
log.warning('({ip}) Unable to process command="{cmd}" (Future option?)'.format(ip=self.ip, cmd=cmd))
return
elif _data in PJLINK_ERRORS: elif _data in PJLINK_ERRORS:
# Oops - projector error # Oops - projector error
log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data)) log.error('({ip}) {cmd}: {err}'.format(ip=self.ip,
if _data == PJLINK_ERRORS[E_AUTHENTICATION]: cmd=cmd,
# Authentication error err=STATUS_MSG[PJLINK_ERRORS[_data]]))
if PJLINK_ERRORS[_data] == E_AUTHENTICATION:
self.disconnect_from_host() self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
self.projectorAuthentication.emit(self.name) self.projectorAuthentication.emit(self.name)
elif _data == PJLINK_ERRORS[E_UNDEFINED]: return self.change_status(status=E_AUTHENTICATION)
# Projector does not recognize command
self.change_status(E_UNDEFINED, '{error}: "{data}"'.format(error=ERROR_MSG[E_UNDEFINED],
data=cmd))
elif _data == PJLINK_ERRORS[E_PARAMETER]:
# Invalid parameter
self.change_status(E_PARAMETER)
elif _data == PJLINK_ERRORS[E_UNAVAILABLE]:
# Projector busy
self.change_status(E_UNAVAILABLE)
elif _data == PJLINK_ERRORS[E_PROJECTOR]:
# Projector/display error
self.change_status(E_PROJECTOR)
return
# Command checks already passed # Command checks already passed
log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd)) log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
self.pjlink_functions[cmd](data) self.pjlink_functions[cmd](data=data)
def process_avmt(self, data): def process_avmt(self, data):
""" """
@ -313,22 +295,22 @@ class PJLinkCommands(object):
data[PJLINK_ERST_DATA['COVER']], data[PJLINK_ERST_DATA['COVER']],
data[PJLINK_ERST_DATA['FILTER']], data[PJLINK_ERST_DATA['FILTER']],
data[PJLINK_ERST_DATA['OTHER']]) data[PJLINK_ERST_DATA['OTHER']])
if fan != PJLINK_ERST_STATUS[E_OK]: if fan != PJLINK_ERST_STATUS[S_OK]:
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \
PJLINK_ERST_STATUS[fan] PJLINK_ERST_STATUS[fan]
if lamp != PJLINK_ERST_STATUS[E_OK]: if lamp != PJLINK_ERST_STATUS[S_OK]:
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \
PJLINK_ERST_STATUS[lamp] PJLINK_ERST_STATUS[lamp]
if temp != PJLINK_ERST_STATUS[E_OK]: if temp != PJLINK_ERST_STATUS[S_OK]:
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \
PJLINK_ERST_STATUS[temp] PJLINK_ERST_STATUS[temp]
if cover != PJLINK_ERST_STATUS[E_OK]: if cover != PJLINK_ERST_STATUS[S_OK]:
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \
PJLINK_ERST_STATUS[cover] PJLINK_ERST_STATUS[cover]
if filt != PJLINK_ERST_STATUS[E_OK]: if filt != PJLINK_ERST_STATUS[S_OK]:
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \
PJLINK_ERST_STATUS[filt] PJLINK_ERST_STATUS[filt]
if other != PJLINK_ERST_STATUS[E_OK]: if other != PJLINK_ERST_STATUS[S_OK]:
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \
PJLINK_ERST_STATUS[other] PJLINK_ERST_STATUS[other]
return return
@ -373,8 +355,18 @@ class PJLinkCommands(object):
:param data: Currently selected source :param data: Currently selected source
""" """
# First, see if we have a valid input based on what is installed (if available)
if self.source_available is not None:
# We have available inputs, so verify it's in the list
if data not in self.source_available:
log.warn('({ip}) Input source not listed in available sources - ignoring'.format(ip=self.ip))
return
elif data not in PJLINK_DEFAULT_CODES:
# Hmm - no sources available yet, so check with PJLink defaults
log.warn('({ip}) Input source not listed as a PJLink available source - ignoring'.format(ip=self.ip))
return
self.source = data self.source = data
log.info('({ip}) Setting data source to "{data}"'.format(ip=self.ip, data=self.source)) log.debug('({ip}) Setting data source to "{data}"'.format(ip=self.ip, data=self.source))
return return
def process_inst(self, data): def process_inst(self, data):
@ -390,7 +382,6 @@ class PJLinkCommands(object):
sources.append(source) sources.append(source)
sources.sort() sources.sort()
self.source_available = sources self.source_available = sources
self.projectorUpdateIcons.emit()
log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.ip, log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.ip,
data=self.source_available)) data=self.source_available))
return return
@ -551,17 +542,15 @@ class PJLinkCommands(object):
return return
elif self.sw_version is None: elif self.sw_version is None:
log.debug('({ip}) Setting projector software version to "{data}"'.format(ip=self.ip, data=data)) log.debug('({ip}) Setting projector software version to "{data}"'.format(ip=self.ip, data=data))
self.sw_version = data
self.db_update = True
else: else:
# Compare software version and see if we got the same projector if self.sw_version != data:
if self.serial_no != data:
log.warning('({ip}) Projector software version does not match saved ' log.warning('({ip}) Projector software version does not match saved '
'software version'.format(ip=self.ip)) 'software version'.format(ip=self.ip))
log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.sw_version)) log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.sw_version))
log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data)) log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data))
log.warning('({ip}) Saving new serial number as sw_version_received'.format(ip=self.ip)) log.warning('({ip}) Updating software version'.format(ip=self.ip))
self.sw_version_received = data self.sw_version = data
self.db_update = True
class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
@ -678,7 +667,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
Retrieve information from projector that changes. Retrieve information from projector that changes.
Normally called by timer(). Normally called by timer().
""" """
if self.state() != S_QSOCKET_STATE['ConnectedState']: if QSOCKET_STATE[self.state()] != S_CONNECTED:
log.warning('({ip}) poll_loop(): Not connected - returning'.format(ip=self.ip)) log.warning('({ip}) poll_loop(): Not connected - returning'.format(ip=self.ip))
return return
log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.ip)) log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.ip))
@ -688,13 +677,8 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
self.timer.setInterval(self.poll_time) self.timer.setInterval(self.poll_time)
# Restart timer # Restart timer
self.timer.start() self.timer.start()
# These commands may change during connection
check_list = ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']
if self.pjlink_class == '2':
check_list.extend(['FILT', 'FREZ'])
for command in check_list:
self.send_command(command)
# The following commands do not change, so only check them once # The following commands do not change, so only check them once
# Call them first in case other functions rely on something here
if self.power == S_ON and self.source_available is None: if self.power == S_ON and self.source_available is None:
self.send_command('INST') self.send_command('INST')
if self.other_info is None: if self.other_info is None:
@ -715,22 +699,28 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
self.send_command('RFIL') self.send_command('RFIL')
if self.model_lamp is None: if self.model_lamp is None:
self.send_command('RLMP') self.send_command('RLMP')
# These commands may change during connection
check_list = ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']
if self.pjlink_class == '2':
check_list.extend(['FILT', 'FREZ'])
for command in check_list:
self.send_command(command)
def _get_status(self, status): def _get_status(self, status):
""" """
Helper to retrieve status/error codes and convert to strings. Helper to retrieve status/error codes and convert to strings.
:param status: Status/Error code :param status: Status/Error code
:returns: (Status/Error code, String) :returns: tuple (-1 if code not INT, None)
:returns: tuple (string: code as string, None if no description)
:returns: tuple (string: code as string, string: Status/Error description)
""" """
if not isinstance(status, int): if not isinstance(status, int):
return -1, 'Invalid status code' return -1, None
elif status in ERROR_STRING: elif status not in STATUS_MSG:
return ERROR_STRING[status], ERROR_MSG[status] return None, None
elif status in STATUS_STRING:
return STATUS_STRING[status], ERROR_MSG[status]
else: else:
return status, translate('OpenLP.PJLink', 'Unknown status') return STATUS_CODE[status], STATUS_MSG[status]
def change_status(self, status, msg=None): def change_status(self, status, msg=None):
""" """
@ -740,19 +730,27 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
:param status: Status code :param status: Status code
:param msg: Optional message :param msg: Optional message
""" """
message = translate('OpenLP.PJLink', 'No message') if msg is None else msg if status in STATUS_CODE:
(code, message) = self._get_status(status) log.debug('({ip}) Changing status to {status} '
if msg is not None: '"{msg}"'.format(ip=self.ip,
message = msg status=STATUS_CODE[status],
msg=msg if msg is not None else STATUS_MSG[status]))
else:
log.warning('({ip}) Unknown status change code: {code}'.format(ip=self.ip,
code=status))
return
if status in CONNECTION_ERRORS: if status in CONNECTION_ERRORS:
# Projector, connection state # Connection state error affects both socket and projector
self.projector_status = self.error_status = self.status_connect = E_NOT_CONNECTED self.error_status = status
elif status >= S_NOT_CONNECTED and status < S_STATUS: self.status_connect = E_NOT_CONNECTED
elif status >= S_NOT_CONNECTED and status in QSOCKET_STATE:
# Socket connection status update
self.status_connect = status self.status_connect = status
self.projector_status = S_NOT_CONNECTED elif status >= S_NOT_CONNECTED and status in PROJECTOR_STATE:
elif status <= S_INFO: # Only affects the projector status
self.status_connect = S_CONNECTED
self.projector_status = status self.projector_status = status
# These log entries are for troubleshooting only
(status_code, status_message) = self._get_status(self.status_connect) (status_code, status_message) = self._get_status(self.status_connect)
log.debug('({ip}) status_connect: {code}: "{message}"'.format(ip=self.ip, log.debug('({ip}) status_connect: {code}: "{message}"'.format(ip=self.ip,
code=status_code, code=status_code,
@ -765,6 +763,15 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
log.debug('({ip}) error_status: {code}: "{message}"'.format(ip=self.ip, log.debug('({ip}) error_status: {code}: "{message}"'.format(ip=self.ip,
code=status_code, code=status_code,
message=status_message if msg is None else msg)) message=status_message if msg is None else msg))
# Now that we logged extra information for debugging, broadcast the original change/message
(code, message) = self._get_status(status)
if msg is not None:
message = msg
elif message is None:
# No message for status code
message = translate('OpenLP.PJLink', 'No message') if msg is None else msg
self.changeStatus.emit(self.ip, status, message) self.changeStatus.emit(self.ip, status, message)
self.projectorUpdateIcons.emit() self.projectorUpdateIcons.emit()
@ -794,7 +801,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
data = decode(read, 'utf-8') data = decode(read, 'utf-8')
# Possibility of extraneous data on input when reading. # Possibility of extraneous data on input when reading.
# Clean out extraneous characters in buffer. # Clean out extraneous characters in buffer.
self.readLine(self.max_size) self.read(2048)
log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip())) log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip()))
# At this point, we should only have the initial login prompt with # At this point, we should only have the initial login prompt with
# possible authentication # possible authentication
@ -805,6 +812,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
# Invalid initial packet - close socket # Invalid initial packet - close socket
log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.ip)) log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.ip))
return self.disconnect_from_host() return self.disconnect_from_host()
# Convert the initial login prompt with the expected PJLink normal command format for processing
log.debug('({ip}) check_login(): Formatting initial connection prompt to PJLink packet'.format(ip=self.ip)) log.debug('({ip}) check_login(): Formatting initial connection prompt to PJLink packet'.format(ip=self.ip))
return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX, return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX,
clss='1', clss='1',
@ -895,7 +903,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
Get data from TCP socket. Get data from TCP socket.
""" """
log.debug('({ip}) get_socket(): Reading data'.format(ip=self.ip)) log.debug('({ip}) get_socket(): Reading data'.format(ip=self.ip))
if self.state() != self.ConnectedState: if QSOCKET_STATE[self.state()] != S_CONNECTED:
log.debug('({ip}) get_socket(): Not connected - returning'.format(ip=self.ip)) log.debug('({ip}) get_socket(): Not connected - returning'.format(ip=self.ip))
self.send_busy = False self.send_busy = False
return return
@ -907,8 +915,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip)) log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip))
return self.receive_data_signal() return self.receive_data_signal()
self.socket_timer.stop() self.socket_timer.stop()
self.get_data(buff=read, ip=self.ip) return self.get_data(buff=read, ip=self.ip)
return self.receive_data_signal()
def get_data(self, buff, ip=None): def get_data(self, buff, ip=None):
""" """
@ -927,13 +934,17 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
data = data_in.strip() data = data_in.strip()
# Initial packet checks # Initial packet checks
if (len(data) < 7): if (len(data) < 7):
return self._trash_buffer(msg='get_data(): Invalid packet - length') self._trash_buffer(msg='get_data(): Invalid packet - length')
return self.receive_data_signal()
elif len(data) > self.max_size: elif len(data) > self.max_size:
return self._trash_buffer(msg='get_data(): Invalid packet - too long') self._trash_buffer(msg='get_data(): Invalid packet - too long')
return self.receive_data_signal()
elif not data.startswith(PJLINK_PREFIX): elif not data.startswith(PJLINK_PREFIX):
return self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing') self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing')
return self.receive_data_signal()
elif '=' not in data: elif '=' not in data:
return self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="') self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="')
return self.receive_data_signal()
log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data)) log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data))
header, data = data.split('=') header, data = data.split('=')
# At this point, the header should contain: # At this point, the header should contain:
@ -947,15 +958,17 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
except ValueError as e: except ValueError as e:
self.change_status(E_INVALID_DATA) self.change_status(E_INVALID_DATA)
log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in)) log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in))
return self._trash_buffer('get_data(): Expected header + command + data') self._trash_buffer('get_data(): Expected header + command + data')
return self.receive_data_signal()
if cmd not in PJLINK_VALID_CMD: if cmd not in PJLINK_VALID_CMD:
log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd)) log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd))
return self._trash_buffer(msg='get_data(): Unknown command "{data}"'.format(data=cmd)) self._trash_buffer(msg='get_data(): Unknown command "{data}"'.format(data=cmd))
return self.receive_data_signal()
if int(self.pjlink_class) < int(version): if int(self.pjlink_class) < int(version):
log.warning('({ip}) get_data(): Projector returned class reply higher ' log.warning('({ip}) get_data(): Projector returned class reply higher '
'than projector stated class'.format(ip=self.ip)) 'than projector stated class'.format(ip=self.ip))
self.send_busy = False self.process_command(cmd, data)
return self.process_command(cmd, data) return self.receive_data_signal()
@QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError) @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)
def get_error(self, err): def get_error(self, err):
@ -993,7 +1006,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
:param salt: Optional salt for md5 hash initial authentication :param salt: Optional salt for md5 hash initial authentication
:param priority: Option to send packet now rather than queue it up :param priority: Option to send packet now rather than queue it up
""" """
if self.state() != self.ConnectedState: if QSOCKET_STATE[self.state()] != S_CONNECTED:
log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip)) log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip))
return self.reset_information() return self.reset_information()
if cmd not in PJLINK_VALID_CMD: if cmd not in PJLINK_VALID_CMD:
@ -1018,7 +1031,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
header=header, header=header,
command=cmd, command=cmd,
options=opts, options=opts,
suffix=CR) suffix=PJLINK_SUFFIX)
if out in self.priority_queue: if out in self.priority_queue:
log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.ip)) log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.ip))
elif out in self.send_queue: elif out in self.send_queue:
@ -1044,9 +1057,10 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
""" """
# Funny looking data check, but it's a quick check for data=None # Funny looking data check, but it's a quick check for data=None
log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.ip, data=data.strip() if data else data)) log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.ip, data=data.strip() if data else data))
conn_state = STATUS_CODE[QSOCKET_STATE[self.state()]]
log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.ip, log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.ip,
data=S_QSOCKET_STATE[self.state()])) data=conn_state))
if self.state() != self.ConnectedState: if QSOCKET_STATE[self.state()] != S_CONNECTED:
log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.ip)) log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.ip))
self.send_busy = False self.send_busy = False
return self.disconnect_from_host() return self.disconnect_from_host()
@ -1088,10 +1102,11 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
""" """
Initiate connection to projector. Initiate connection to projector.
""" """
log.debug('{ip}) connect_to_host(): Starting connection'.format(ip=self.ip)) log.debug('({ip}) connect_to_host(): Starting connection'.format(ip=self.ip))
if self.state() == self.ConnectedState: if QSOCKET_STATE[self.state()] == S_CONNECTED:
log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip)) log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip))
return return
self.error_status = S_OK
self.change_status(S_CONNECTING) self.change_status(S_CONNECTING)
self.connectToHost(self.ip, self.port if isinstance(self.port, int) else int(self.port)) self.connectToHost(self.ip, self.port if isinstance(self.port, int) else int(self.port))
@ -1100,12 +1115,13 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
""" """
Close socket and cleanup. Close socket and cleanup.
""" """
if abort or self.state() != self.ConnectedState: if abort or QSOCKET_STATE[self.state()] != S_NOT_CONNECTED:
if abort: if abort:
log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip)) log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip))
self.abort()
else: else:
log.warning('({ip}) disconnect_from_host(): Not connected'.format(ip=self.ip)) log.warning('({ip}) disconnect_from_host(): Not connected'.format(ip=self.ip))
self.disconnectFromHost() self.disconnectFromHost()
try: try:
self.readyRead.disconnect(self.get_socket) self.readyRead.disconnect(self.get_socket)
except TypeError: except TypeError:
@ -1201,7 +1217,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
elif src not in self.source_available: elif src not in self.source_available:
return return
log.debug('({ip}) Setting input source to "{data}"'.format(ip=self.ip, data=src)) log.debug('({ip}) Setting input source to "{data}"'.format(ip=self.ip, data=src))
self.send_command(cmd='INPT', opts=src) self.send_command(cmd='INPT', opts=src, priority=True)
self.poll_loop() self.poll_loop()
def set_power_on(self): def set_power_on(self):
@ -1209,7 +1225,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
Send command to turn power to on. Send command to turn power to on.
""" """
log.debug('({ip}) Setting POWR to 1 (on)'.format(ip=self.ip)) log.debug('({ip}) Setting POWR to 1 (on)'.format(ip=self.ip))
self.send_command(cmd='POWR', opts='1') self.send_command(cmd='POWR', opts='1', priority=True)
self.poll_loop() self.poll_loop()
def set_power_off(self): def set_power_off(self):
@ -1217,7 +1233,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
Send command to turn power to standby. Send command to turn power to standby.
""" """
log.debug('({ip}) Setting POWR to 0 (standby)'.format(ip=self.ip)) log.debug('({ip}) Setting POWR to 0 (standby)'.format(ip=self.ip))
self.send_command(cmd='POWR', opts='0') self.send_command(cmd='POWR', opts='0', priority=True)
self.poll_loop() self.poll_loop()
def set_shutter_closed(self): def set_shutter_closed(self):
@ -1225,7 +1241,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
Send command to set shutter to closed position. Send command to set shutter to closed position.
""" """
log.debug('({ip}) Setting AVMT to 11 (shutter closed)'.format(ip=self.ip)) log.debug('({ip}) Setting AVMT to 11 (shutter closed)'.format(ip=self.ip))
self.send_command(cmd='AVMT', opts='11') self.send_command(cmd='AVMT', opts='11', priority=True)
self.poll_loop() self.poll_loop()
def set_shutter_open(self): def set_shutter_open(self):
@ -1233,8 +1249,9 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
Send command to set shutter to open position. Send command to set shutter to open position.
""" """
log.debug('({ip}) Setting AVMT to "10" (shutter open)'.format(ip=self.ip)) log.debug('({ip}) Setting AVMT to "10" (shutter open)'.format(ip=self.ip))
self.send_command(cmd='AVMT', opts='10') self.send_command(cmd='AVMT', opts='10', priority=True)
self.poll_loop() self.poll_loop()
self.projectorUpdateIcons.emit()
def receive_data_signal(self): def receive_data_signal(self):
""" """

View File

@ -23,12 +23,51 @@
Package to test the openlp.core.projectors.constants module. Package to test the openlp.core.projectors.constants module.
""" """
from unittest import TestCase from unittest import TestCase
from openlp.core.projectors import constants
from openlp.core.projectors.constants import STATUS_CODE, STATUS_MSG
class TestProjectorConstants(TestCase): class TestProjectorConstants(TestCase):
""" """
Test specific functions in the projector constants module. Test specific functions in the projector constants module.
""" """
def test_defined_codes_in_status_code(self):
"""
Test defined status/error codes have equivalent strings
"""
check = []
missing_str = []
# GIVEN: List of defined E_* and S_* items defined in constants
for item in constants.__dict__:
if item.startswith('E_') or item.startswith('S_'):
check.append(item)
# WHEN: Verify defined list against STATUS_STR
for item in check:
if constants.__dict__[item] not in STATUS_CODE:
missing_str.append(item)
# THEN: There should be no missing items
assert 0 == len(missing_str), 'Status string missing: {msg}'.format(msg=missing_str)
def test_status_code_in_status_message(self):
"""
Test if entries in STATUS_CODE have equivalent descriptions in STATUS_MSG
"""
missing_msg = []
# GIVEN: Test items
check = STATUS_CODE
# WHEN: Verify each entry in STATUS_MSG against STATUS_CODE
for item in check:
if item not in STATUS_MSG:
missing_msg.append(item)
# THEN: There should be no missing items
assert 0 == len(missing_msg), 'Status message missing: {msg}'.format(msg=missing_msg)
def test_build_pjlink_video_label(self): def test_build_pjlink_video_label(self):
""" """
Test building PJLINK_DEFAULT_CODES dictionary Test building PJLINK_DEFAULT_CODES dictionary

View File

@ -25,7 +25,7 @@ Package to test the openlp.core.projectors.pjlink base package.
from unittest import TestCase from unittest import TestCase
from unittest.mock import call, patch, MagicMock from unittest.mock import call, patch, MagicMock
from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED, S_QSOCKET_STATE from openlp.core.projectors.constants import E_PARAMETER, STATUS_CODE, S_ON, S_CONNECTED, QSOCKET_STATE
from openlp.core.projectors.db import Projector from openlp.core.projectors.db import Projector
from openlp.core.projectors.pjlink import PJLink from openlp.core.projectors.pjlink import PJLink
@ -65,7 +65,7 @@ class TestPJLinkBase(TestCase):
# as first parameter # as first parameter
mock_change_status.called_with(E_PARAMETER, mock_change_status.called_with(E_PARAMETER,
'change_status should have been called with "{}"'.format( 'change_status should have been called with "{}"'.format(
ERROR_STRING[E_PARAMETER])) STATUS_CODE[E_PARAMETER]))
@patch.object(pjlink_test, 'disconnect_from_host') @patch.object(pjlink_test, 'disconnect_from_host')
def test_socket_abort(self, mock_disconnect): def test_socket_abort(self, mock_disconnect):
@ -104,7 +104,7 @@ class TestPJLinkBase(TestCase):
""" """
# GIVEN: Mocks and test data # GIVEN: Mocks and test data
mock_state = patch.object(self.pjlink_test, 'state').start() mock_state = patch.object(self.pjlink_test, 'state').start()
mock_state.return_value = S_QSOCKET_STATE['ConnectedState'] mock_state.return_value = QSOCKET_STATE[S_CONNECTED]
mock_timer = patch.object(self.pjlink_test, 'timer').start() mock_timer = patch.object(self.pjlink_test, 'timer').start()
mock_timer.interval.return_value = 10 mock_timer.interval.return_value = 10
mock_send_command = patch.object(self.pjlink_test, 'send_command').start() mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
@ -117,7 +117,6 @@ class TestPJLinkBase(TestCase):
pjlink.manufacturer = None pjlink.manufacturer = None
pjlink.model = None pjlink.model = None
pjlink.pjlink_name = None pjlink.pjlink_name = None
pjlink.ConnectedState = S_CONNECTED
call_list = [ call_list = [
call('POWR'), call('POWR'),
call('ERST'), call('ERST'),

View File

@ -24,209 +24,222 @@ Package to test the openlp.core.projectors.pjlink command routing.
""" """
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import MagicMock, call, patch
import openlp.core.projectors.pjlink import openlp.core.projectors.pjlink
from openlp.core.projectors.constants import PJLINK_ERRORS, \ from openlp.core.projectors.constants import PJLINK_ERRORS, PJLINK_PREFIX, STATUS_MSG, \
E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED
from openlp.core.projectors.db import Projector from openlp.core.projectors.db import Projector
from openlp.core.projectors.pjlink import PJLink from openlp.core.projectors.pjlink import PJLink
''' from tests.resources.projector.data import TEST1_DATA
from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
'''
from tests.resources.projector.data import TEST_PIN, TEST1_DATA
pjlink_test = PJLink(Projector(**TEST1_DATA), pin=TEST_PIN, no_poll=True)
pjlink_test.ip = '127.0.0.1'
class TestPJLinkRouting(TestCase): class TestPJLinkRouting(TestCase):
""" """
Tests for the PJLink module command routing Tests for the PJLink module command routing
""" """
def setUp(self): def test_get_data_unknown_command(self):
''' """
TestPJLinkCommands part 2 initialization Test not a valid command
''' """
self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) # GIVEN: Test object
log_warning_text = [call('(111.111.111.111) get_data(): Invalid packet - unknown command "UNK"')]
log_debug_text = [call('(111.111.111.111) get_data(ip="111.111.111.111" buffer="b\'%1UNK=Huh?\'"'),
call('(111.111.111.111) get_data(): Checking new data "%1UNK=Huh?"')]
def tearDown(self): with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
''' patch.object(openlp.core.projectors.pjlink.PJLink, '_trash_buffer') as mock_buffer:
TestPJLinkCommands part 2 cleanups
'''
self.pjlink_test = None
@patch.object(openlp.core.projectors.pjlink, 'log') pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
def test_process_command_call_clss(self, mock_log): pjlink.pjlink_functions = MagicMock()
# WHEN: get_data called with an unknown command
pjlink.get_data(buff='{prefix}1UNK=Huh?'.format(prefix=PJLINK_PREFIX).encode('utf-8'))
# THEN: Appropriate log entries should have been made and methods called/not called
mock_log.debug.assert_has_calls(log_debug_text)
mock_log.warning.assert_has_calls(log_warning_text)
self.assertFalse(pjlink.pjlink_functions.called, 'Should not have accessed pjlink_functions')
self.assertTrue(mock_buffer.called, 'Should have called _trash_buffer')
def test_process_command_call_clss(self):
""" """
Test process_command calls proper function Test process_command calls proper function
""" """
# GIVEN: Test object # GIVEN: Test object and mocks
pjlink = pjlink_test log_debug_calls = [call('(111.111.111.111) Processing command "CLSS" with data "1"'),
log_text = '(127.0.0.1) Calling function for CLSS' call('(111.111.111.111) Calling function for CLSS')]
mock_log.reset_mock()
mock_process_clss = MagicMock()
pjlink.pjlink_functions['CLSS'] = mock_process_clss
# WHEN: process_command is called with valid function and data with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
pjlink.process_command(cmd='CLSS', data='1') patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
# THEN: Process method should have been called properly pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
mock_log.debug.assert_called_with(log_text)
mock_process_clss.assert_called_with('1')
@patch.object(pjlink_test, 'change_status') # WHEN: process_command is called with valid function and data
@patch.object(openlp.core.projectors.pjlink, 'log') pjlink.process_command(cmd='CLSS', data='1')
def test_process_command_err1(self, mock_log, mock_change_status):
"""
Test ERR1 - Undefined projector function
"""
# GIVEN: Test object
pjlink = pjlink_test
log_text = '(127.0.0.1) Projector returned error "ERR1"'
mock_change_status.reset_mock()
mock_log.reset_mock()
# WHEN: process_command called with ERR1 # THEN: Appropriate log entries should have been made and methods called
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNDEFINED]) mock_log.debug.assert_has_calls(log_debug_calls)
mock_process_clss.assert_called_once_with(data='1')
# THEN: Error should be logged and status_change should be called def test_process_command_erra(self):
mock_change_status.assert_called_once_with(E_UNDEFINED, 'Undefined Command: "CLSS"')
mock_log.error.assert_called_with(log_text)
@patch.object(pjlink_test, 'change_status')
@patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_err2(self, mock_log, mock_change_status):
"""
Test ERR2 - Parameter Error
"""
# GIVEN: Test object
pjlink = pjlink_test
log_text = '(127.0.0.1) Projector returned error "ERR2"'
mock_change_status.reset_mock()
mock_log.reset_mock()
# WHEN: process_command called with ERR2
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PARAMETER])
# THEN: Error should be logged and status_change should be called
mock_change_status.assert_called_once_with(E_PARAMETER)
mock_log.error.assert_called_with(log_text)
@patch.object(pjlink_test, 'change_status')
@patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_err3(self, mock_log, mock_change_status):
"""
Test ERR3 - Unavailable error
"""
# GIVEN: Test object
pjlink = pjlink_test
log_text = '(127.0.0.1) Projector returned error "ERR3"'
mock_change_status.reset_mock()
mock_log.reset_mock()
# WHEN: process_command called with ERR3
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNAVAILABLE])
# THEN: Error should be logged and status_change should be called
mock_change_status.assert_called_once_with(E_UNAVAILABLE)
mock_log.error.assert_called_with(log_text)
@patch.object(pjlink_test, 'change_status')
@patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_err4(self, mock_log, mock_change_status):
"""
Test ERR3 - Unavailable error
"""
# GIVEN: Test object
pjlink = pjlink_test
log_text = '(127.0.0.1) Projector returned error "ERR4"'
mock_change_status.reset_mock()
mock_log.reset_mock()
# WHEN: process_command called with ERR3
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PROJECTOR])
# THEN: Error should be logged and status_change should be called
mock_change_status.assert_called_once_with(E_PROJECTOR)
mock_log.error.assert_called_with(log_text)
@patch.object(pjlink_test, 'projectorAuthentication')
@patch.object(pjlink_test, 'change_status')
@patch.object(pjlink_test, 'disconnect_from_host')
@patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_erra(self, mock_log, mock_disconnect, mock_change_status, mock_err_authenticate):
""" """
Test ERRA - Authentication Error Test ERRA - Authentication Error
""" """
# GIVEN: Test object # GIVEN: Test object
pjlink = pjlink_test log_error_calls = [call('(111.111.111.111) PJLINK: {msg}'.format(msg=STATUS_MSG[E_AUTHENTICATION]))]
log_text = '(127.0.0.1) Projector returned error "ERRA"' log_debug_calls = [call('(111.111.111.111) Processing command "PJLINK" with data "ERRA"')]
mock_change_status.reset_mock()
mock_log.reset_mock()
# WHEN: process_command called with ERRA with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_AUTHENTICATION]) patch.object(openlp.core.projectors.pjlink.PJLink, 'process_pjlink') as mock_process_pjlink, \
patch.object(openlp.core.projectors.pjlink.PJLink, 'change_status') as mock_change_status, \
patch.object(openlp.core.projectors.pjlink.PJLink, 'disconnect_from_host') as mock_disconnect, \
patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorAuthentication') as mock_authentication:
# THEN: Error should be logged and several methods should be called pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
self.assertTrue(mock_disconnect.called, 'disconnect_from_host should have been called')
mock_change_status.assert_called_once_with(E_AUTHENTICATION) # WHEN: process_command called with ERRA
mock_log.error.assert_called_with(log_text) pjlink.process_command(cmd='PJLINK', data=PJLINK_ERRORS[E_AUTHENTICATION])
# THEN: Appropriate log entries should have been made and methods called/not called
mock_log.error.assert_has_calls(log_error_calls)
mock_log.debug.assert_has_calls(log_debug_calls)
self.assertTrue(mock_disconnect.called, 'disconnect_from_host should have been called')
mock_change_status.assert_called_once_with(status=E_AUTHENTICATION)
mock_authentication.emit.assert_called_once_with(pjlink.name)
mock_process_pjlink.assert_not_called()
def test_process_command_err1(self):
"""
Test ERR1 - Undefined projector function
"""
# GIVEN: Test object
log_error_text = [call('(111.111.111.111) CLSS: {msg}'.format(msg=STATUS_MSG[E_UNDEFINED]))]
log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "ERR1"'),
call('(111.111.111.111) Calling function for CLSS')]
with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
# WHEN: process_command called with ERR1
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNDEFINED])
# THEN: Appropriate log entries should have been made and methods called
mock_log.error.assert_has_calls(log_error_text)
mock_log.debug.assert_has_calls(log_debug_text)
mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_UNDEFINED])
def test_process_command_err2(self):
"""
Test ERR2 - Parameter Error
"""
# GIVEN: Test object
log_error_text = [call('(111.111.111.111) CLSS: {msg}'.format(msg=STATUS_MSG[E_PARAMETER]))]
log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "ERR2"'),
call('(111.111.111.111) Calling function for CLSS')]
with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
# WHEN: process_command called with ERR2
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PARAMETER])
# THEN: Appropriate log entries should have been made and methods called/not called
mock_log.error.assert_has_calls(log_error_text)
mock_log.debug.assert_has_calls(log_debug_text)
mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_PARAMETER])
def test_process_command_err3(self):
"""
Test ERR3 - Unavailable error
"""
# GIVEN: Test object
log_error_text = [call('(111.111.111.111) CLSS: {msg}'.format(msg=STATUS_MSG[E_UNAVAILABLE]))]
log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "ERR3"'),
call('(111.111.111.111) Calling function for CLSS')]
with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
# WHEN: process_command called with ERR3
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNAVAILABLE])
# THEN: Appropriate log entries should have been made and methods called
mock_log.error.assert_has_calls(log_error_text)
mock_log.debug.assert_has_calls(log_debug_text)
mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_UNAVAILABLE])
def test_process_command_err4(self):
"""
Test ERR3 - Unavailable error
"""
# GIVEN: Test object
log_error_text = [call('(111.111.111.111) CLSS: {msg}'.format(msg=STATUS_MSG[E_PROJECTOR]))]
log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "ERR4"'),
call('(111.111.111.111) Calling function for CLSS')]
with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
# WHEN: process_command called with ERR4
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PROJECTOR])
# THEN: Appropriate log entries should have been made and methods called
mock_log.error.assert_has_calls(log_error_text)
mock_log.debug.assert_has_calls(log_debug_text)
mock_process_clss.assert_called_once_with(data=PJLINK_ERRORS[E_PROJECTOR])
def test_process_command_future(self): def test_process_command_future(self):
""" """
Test command valid but no method to process yet Test command valid but no method to process yet
""" """
# GIVEN: Initial mocks and data
mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
mock_functions = patch.object(self.pjlink_test, 'pjlink_functions').start()
mock_functions.return_value = []
pjlink = self.pjlink_test
log_text = '(111.111.111.111) Unable to process command="CLSS" (Future option?)'
# WHEN: process_command called with an unknown command
pjlink.process_command(cmd='CLSS', data='DONT CARE')
# THEN: Error should be logged and no command called
self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
mock_log.warning.assert_called_once_with(log_text)
@patch.object(pjlink_test, 'pjlink_functions')
@patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_invalid(self, mock_log, mock_functions):
"""
Test not a valid command
"""
# GIVEN: Test object # GIVEN: Test object
pjlink = pjlink_test log_warning_text = [call('(111.111.111.111) Unable to process command="CLSS" (Future option?)')]
mock_functions.reset_mock() log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "Huh?"')]
mock_log.reset_mock()
# WHEN: process_command called with an unknown command with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
pjlink.process_command(cmd='Unknown', data='Dont Care') patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
log_text = '(127.0.0.1) Ignoring command="Unknown" (Invalid/Unknown)'
# THEN: Error should be logged and no command called pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') pjlink.pjlink_functions = MagicMock()
mock_log.error.assert_called_once_with(log_text)
# WHEN: Processing a possible future command
pjlink.process_command(cmd='CLSS', data="Huh?")
# THEN: Appropriate log entries should have been made and methods called/not called
mock_log.debug.assert_has_calls(log_debug_text)
mock_log.warning.assert_has_calls(log_warning_text)
self.assertFalse(pjlink.pjlink_functions.called, 'Should not have accessed pjlink_functions')
self.assertFalse(mock_process_clss.called, 'Should not have called process_clss')
def test_process_command_ok(self): def test_process_command_ok(self):
""" """
Test command returned success Test command returned success
""" """
# GIVEN: Initial mocks and data # GIVEN: Initial mocks and data
mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() # GIVEN: Test object and mocks
mock_send_command = patch.object(self.pjlink_test, 'send_command').start() log_debug_calls = [call('(111.111.111.111) Processing command "CLSS" with data "OK"'),
call('(111.111.111.111) Command "CLSS" returned OK')]
pjlink = self.pjlink_test with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \
log_text = '(111.111.111.111) Command "POWR" returned OK' patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command, \
patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss:
# WHEN: process_command called with a command that returns OK pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
pjlink.process_command(cmd='POWR', data='OK')
# THEN: Appropriate calls should have been made # WHEN: process_command is called with valid function and data
mock_log.debug.assert_called_with(log_text) pjlink.process_command(cmd='CLSS', data='OK')
mock_send_command.assert_called_once_with(cmd='POWR')
# THEN: Appropriate log entries should have been made and methods called
mock_log.debug.assert_has_calls(log_debug_calls)
mock_send_command.assert_called_once_with(cmd='CLSS')
mock_process_clss.assert_not_called()