IP camera status - how to catch "Stream ended." status

Is there any way to programmatically monitor the status (quality) of the IP Camera component’s video stream? Seems like there would be some response code attribute available to check if/when things go wrong.

There are two situations I need to catch if possible:

  1. When the number of connect retries has been reached
  2. When (for whatever reason) the stream ends inadvertently.

Is there any hidden properties that can be used in scripting (eg. property change scripts) to catch these events?

The IP camera component has an internally nested FrameProducer class on which you can call getState(). Getting to that is an exercise for the reader, but I’ll give two hints: 1. It’s not going to show up with a dir() call, and 2. it’s internally named parser.

Hmm… I find the parser attribute in the serialized fields documentation.

I cannot find anything regarding the parser object:

  • com.inductiveautomation.factorypmi.application.components.FrameProducer

Also there is no mention of the parser attribute in the PMIIPCamViewer class itself.

Programmatically the IP Camera Viewer component’s only exposed interface would be the property-change event script. Any hint on what property to monitor? The component does not allow creation of custom properties. Normally property-change scripts only fire for changes of bindable properties.

I tried getting around this issue by switching from MPEG stream to JPEG stills. Big bonus of doing this is the lower load it creates on the IP-camera app-server.

Unfortunately even using JPG Stills results in occasional exceptions that I would like to trap:

Really all I need is some way to catch any/whatever exception occurs in the component. Usually reloading the screen will rectify whatever the problem is.

Using script console I explored the PMIIPCamViewer object:

from com.inductiveautomation.factorypmi.application.components import PMIIPCamViewer 
ip = PMIIPCamViewer() 
dir(ip)

The hint about the parser attribute in the serialized fields documentation is misleading.

from com.inductiveautomation.factorypmi.application.components import FrameProducer

throws an exception:

ImportError: cannot import name FrameProducer

As Paul indicated there is no FrameProducer / parser attribute in the object.

>>> dir(ip)
['ABORT', 'ALLBITS', 'AccessibleJComponent', 'BOTTOM_ALIGNMENT', 'BaselineResizeBehavior', 'CENTER_ALIGNMENT', 'COMPONENT_RUNNING', 'ERROR', 'FRAMEBITS', 'HEIGHT', 'LEFT_ALIGNMENT', 'MODE_JPEG', 'MODE_MJPEG', 'PROPERTIES', 'RIGHT_ALIGNMENT', 'SOMEBITS', 'TOOL_TIP_TEXT_KEY', 'TOP_ALIGNMENT', 'UIClassID', 'UNDEFINED_CONDITION', 'WHEN_ANCESTOR_OF_FOCUSED_COMPONENT', 'WHEN_FOCUSED', 'WHEN_IN_FOCUSED_WINDOW', 'WIDTH', '__class__', '__copy__', '__deepcopy__', '__delattr__', '__doc__', '__eq__', '__getattribute__', '__hash__', '__init__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__unicode__', 'accessibleContext', 'action', 'actionMap', 'add', 'addAncestorListener', 'addComponentListener', 'addContainerListener', 'addFocusListener', 'addHierarchyBoundsListener', 'addHierarchyListener', 'addInputMethodListener', 'addKeyListener', 'addMouseListener', 'addMouseMotionListener', 'addMouseWheelListener', 'addNotify', 'addPropertyChangeListener', 'addVetoableChangeListener', 'alignmentX', 'alignmentY', 'ancestorAdded', 'ancestorListener', 'ancestorListeners', 'ancestorMoved', 'ancestorRemoved', 'ancestorResized', 'appContext', 'applyComponentOrientation', 'areFocusTraversalKeysSet', 'autoscrolls', 'background', 'backgroundSet', 'baselineResizeBehavior', 'border', 'bounds', 'cameraBufferSize', 'caretPositionChanged', 'checkImage', 'class', 'colorModel', 'componentAdded', 'componentCount', 'componentHidden', 'componentListener', 'componentListeners', 'componentMoved', 'componentOrientation', 'componentPopupMenu', 'componentRemoved', 'componentResized', 'componentShown', 'components', 'computeVisibleRect', 'connectRetries', 'containerListener', 'containerListeners', 'contains', 'countComponents', 'createImage', 'createToolTip', 'createVolatileImage', 'cursor', 'cursorCode', 'cursorSet', 'debugGraphicsOptions', 'deliverEvent', 'disable', 'dispatchEvent', 'displayable', 'doLayout', 'doubleBuffered', 'dropTarget', 'enable', 'enableInputMethods', 'enabled', 'equals', 'findComponentAt', 'firePropertyChange', 'focusCycleRoot', 'focusCycleRootAncestor', 'focusGained', 'focusListener', 'focusListeners', 'focusLost', 'focusOwner', 'focusTraversable', 'focusTraversalKeysEnabled', 'focusTraversalPolicy', 'focusTraversalPolicyProvider', 'focusTraversalPolicySet', 'focusable', 'font', 'fontSet', 'foreground', 'foregroundSet', 'getAccessibleContext', 'getActionForKeyStroke', 'getActionMap', 'getAlignmentX', 'getAlignmentY', 'getAncestorListeners', 'getAppContext', 'getAutoscrolls', 'getBackground', 'getBaseline', 'getBaselineResizeBehavior', 'getBorder', 'getBounds', 'getCameraBufferSize', 'getClass', 'getClientProperty', 'getColorModel', 'getComponent', 'getComponentAt', 'getComponentCount', 'getComponentListeners', 'getComponentOrientation', 'getComponentPopupMenu', 'getComponentZOrder', 'getComponents', 'getConditionForKeyStroke', 'getConnectRetries', 'getContainerListeners', 'getCursor', 'getCursorCode', 'getDebugGraphicsOptions', 'getDefaultLocale', 'getDropTarget', 'getFocusCycleRootAncestor', 'getFocusListeners', 'getFocusTraversalKeys', 'getFocusTraversalKeysEnabled', 'getFocusTraversalPolicy', 'getFont', 'getFontMetrics', 'getForeground', 'getGraphics', 'getGraphicsConfiguration', 'getHeight', 'getHierarchyBoundsListeners', 'getHierarchyListeners', 'getIgnoreRepaint', 'getInheritsPopupMenu', 'getInputContext', 'getInputMap', 'getInputMethodListeners', 'getInputMethodRequests', 'getInputVerifier', 'getInsets', 'getKeyListeners', 'getLayout', 'getListeners', 'getLocale', 'getLocation', 'getLocationOnScreen', 'getMaximumSize', 'getMinimumSize', 'getMode', 'getMouseListeners', 'getMouseMotionListeners', 'getMousePosition', 'getMouseWheelListeners', 'getName', 'getNextFocusableComponent', 'getParent', 'getPassword', 'getPeer', 'getPopupLocation', 'getPreferredSize', 'getPropertyChangeListeners', 'getRefreshRate', 'getRegisteredKeyStrokes', 'getRetryDelay', 'getRootPane', 'getScaleMode', 'getSize', 'getToolTipLocation', 'getToolTipText', 'getToolkit', 'getTopLevelAncestor', 'getTransferHandler', 'getTreeLock', 'getUIClassID', 'getUrl', 'getUserAgent', 'getUsername', 'getVerifyInputWhenFocusTarget', 'getVetoableChangeListeners', 'getVisibleRect', 'getWidth', 'getX', 'getY', 'gotFocus', 'grabFocus', 'graphics', 'graphicsConfiguration', 'handleEvent', 'hasFocus', 'hashCode', 'height', 'hide', 'hierarchyBoundsListener', 'hierarchyBoundsListeners', 'hierarchyChanged', 'hierarchyListener', 'hierarchyListeners', 'ignoreRepaint', 'imageUpdate', 'inheritsPopupMenu', 'inputContext', 'inputMap', 'inputMethodListener', 'inputMethodListeners', 'inputMethodRequests', 'inputMethodTextChanged', 'inputVerifier', 'insets', 'inside', 'invalidate', 'isAncestorOf', 'isBackgroundSet', 'isCursorSet', 'isDisplayable', 'isDoubleBuffered', 'isEnabled', 'isFocusCycleRoot', 'isFocusOwner', 'isFocusTraversable', 'isFocusTraversalPolicyProvider', 'isFocusTraversalPolicySet', 'isFocusable', 'isFontSet', 'isForegroundSet', 'isLightweight', 'isLightweightComponent', 'isManagingFocus', 'isMaximumSizeSet', 'isMinimumSizeSet', 'isOpaque', 'isOptimizedDrawingEnabled', 'isPaintingForPrint', 'isPaintingTile', 'isPreferredSizeSet', 'isRequestFocusEnabled', 'isScaleVideo', 'isShowStats', 'isShowing', 'isUseAuthentication', 'isValid', 'isValidateRoot', 'isVisible', 'keyDown', 'keyListener', 'keyListeners', 'keyPressed', 'keyReleased', 'keyTyped', 'keyUp', 'layout', 'lightweight', 'list', 'locale', 'locate', 'location', 'locationOnScreen', 'lostFocus', 'managingFocus', 'maximumSize', 'maximumSizeSet', 'minimumSize', 'minimumSizeSet', 'mode', 'mouseClicked', 'mouseDown', 'mouseDrag', 'mouseDragged', 'mouseEnter', 'mouseEntered', 'mouseExit', 'mouseExited', 'mouseListener', 'mouseListeners', 'mouseMotionListener', 'mouseMotionListeners', 'mouseMove', 'mouseMoved', 'mousePosition', 'mousePressed', 'mouseReleased', 'mouseUp', 'mouseWheelListener', 'mouseWheelListeners', 'mouseWheelMoved', 'move', 'name', 'nextFocus', 'nextFocusableComponent', 'nextFrame', 'notify', 'notifyAll', 'notifyStateChanged', 'opaque', 'optimizedDrawingEnabled', 'paint', 'paintAll', 'paintComponent', 'paintComponents', 'paintImmediately', 'paintingForPrint', 'paintingTile', 'parent', 'password', 'peer', 'postEvent', 'preferredSize', 'preferredSizeSet', 'prepareImage', 'print', 'printAll', 'printComponents', 'propertyChange', 'propertyChangeListener', 'propertyChangeListeners', 'putClientProperty', 'reconnect', 'refreshRate', 'registerKeyboardAction', 'registeredKeyStrokes', 'remove', 'removeAll', 'removeAncestorListener', 'removeComponentListener', 'removeContainerListener', 'removeFocusListener', 'removeHierarchyBoundsListener', 'removeHierarchyListener', 'removeInputMethodListener', 'removeKeyListener', 'removeMouseListener', 'removeMouseMotionListener', 'removeMouseWheelListener', 'removeNotify', 'removePropertyChangeListener', 'removeVetoableChangeListener', 'repaint', 'requestDefaultFocus', 'requestFocus', 'requestFocusEnabled', 'requestFocusInWindow', 'resetKeyboardActions', 'reshape', 'resize', 'retryDelay', 'revalidate', 'rootPane', 'scaleMode', 'scaleVideo', 'scrollRectToVisible', 'setActionMap', 'setAlignmentX', 'setAlignmentY', 'setAutoscrolls', 'setBackground', 'setBorder', 'setBounds', 'setCameraBufferSize', 'setComponentOrientation', 'setComponentPopupMenu', 'setComponentZOrder', 'setConnectRetries', 'setCursor', 'setCursorCode', 'setDebugGraphicsOptions', 'setDefaultLocale', 'setDoubleBuffered', 'setDropTarget', 'setEnabled', 'setFocusCycleRoot', 'setFocusTraversalKeys', 'setFocusTraversalKeysEnabled', 'setFocusTraversalPolicy', 'setFocusTraversalPolicyProvider', 'setFocusable', 'setFont', 'setForeground', 'setIgnoreRepaint', 'setInheritsPopupMenu', 'setInputMap', 'setInputVerifier', 'setLayout', 'setLocale', 'setLocation', 'setMaximumSize', 'setMinimumSize', 'setMode', 'setName', 'setNextFocusableComponent', 'setOpaque', 'setPassword', 'setPreferredSize', 'setRefreshRate', 'setRequestFocusEnabled', 'setRetryDelay', 'setScaleMode', 'setScaleVideo', 'setShowStats', 'setSize', 'setToolTipText', 'setTransferHandler', 'setUrl', 'setUseAuthentication', 'setUserAgent', 'setUsername', 'setVerifyInputWhenFocusTarget', 'setVisible', 'show', 'showStats', 'showing', 'shutdownComponent', 'size', 'startupComponent', 'toString', 'toolTipText', 'toolkit', 'topLevelAncestor', 'transferFocus', 'transferFocusBackward', 'transferFocusDownCycle', 'transferFocusUpCycle', 'transferHandler', 'treeLock', 'unregisterKeyboardAction', 'update', 'updateUI', 'url', 'useAuthentication', 'userAgent', 'username', 'valid', 'validate', 'validateRoot', 'verifyInputWhenFocusTarget', 'vetoableChange', 'vetoableChangeListener', 'vetoableChangeListeners', 'visible', 'visibleRect', 'wait', 'width', 'x', 'y']
>>> 

I am testing a bandage polled idea using runScript() on a bindable property (eg. visible) with a custom method:

def setTrue(self)
	try:
		self.nextFrame()
#		system.gui.messageBox("success","nextFrame")
	except:
		self.reconnect()
		system.gui.messageBox("failure","reconnect")
	return True

Rationale is to periodically issue the PMIIPCamViewer object’s nextFrame() method and – if it fails – try the reconnect() method.

For what it is worth, this is not a new issue, just unsolved:

  1. 2014: IP Camera Viewer - IOException: Premature EOF
  2. 2014: IP Camera not automatically reconnecting
  3. 2015: Refresh IP Camera Module on Error
  4. 2015: IP Camera Viewer Error & Recovery Question

This band-aid solution seems work pretty well. For what it is worth, here is a template containing the IP camera with the script added.

template_IP Camera w reconnect baindaid.proj (6.1 KB)

Note: Rather than drinking MPEG, I am using a sequence of JPEG stills to reduce bandwidth / load on our already-over-taxed video servers. I have not tested the band-aid to see if it works with the full blown MPEG streaming.

Nope even this band-aid is not full proof.

Still getting occasional HTTP 400: Bad Request exceptions that leave the stream stalled.

Interim work around is to put another slower polled band-aid (eg. every 5 minutes) that simply hammers the cam-viewer, issuing the reconnect() whether or not it is necessary.

After a little digging around I got this sorted out. Through the use of reflection I got hold of the parser object and then the getState method inside. The only oddity I found was when displaying the string in a label used for debugging purposes sometimes it was all upper case and other times it was first letter upper, rest lower case (RUNNING or Running).

The script below was attached to a mouseClicked event on the IP Cam viewer for testing but in prod we use a timer to run the check and issue the reconnect() call if need be

from com.inductiveautomation.factorypmi.application.components import PMIIPCamViewer

parserRef = PMIIPCamViewer().getClass().getDeclaredField('parser')
parserRef.setAccessible(True)
cam = event.source
parser = parserRef.get(cam)

# If the url property is blank, parser is None
if parser is not None:
    stateRef = parser.getClass().getSuperclass().getDeclaredMethod('getState')
    stateRef.setAccessible(True)
    # Enum contents: CONNECTING, RUNNING, CANCELED, ERROR
    state = stateRef.invoke(parser)
    stateRef.setAccessible(False)

parserRef.setAccessible(False)
1 Like

Looks familiar… If you look at the Usage Notes for my Image Streamer module, you’ll see a formal version of this as a script module, suited to use in a binding. Allows you to copy camera view instances around without needing a separate timer component.

https://www.automation-pros.com/streamer/doc/

Also:

1 Like