title: Using OnVif PullPoint Services with Zeep
body: <p>I'm having hard time in trying to subscribe to the OnVif pullpoint services in standard IP cameras.</p>
  
  <p>The SOAP client I'm using is Zeep <a href="https://python-zeep.readthedocs.io/en/master/index.html" rel="nofollow noreferrer">https://python-zeep.readthedocs.io/en/master/index.html</a></p>
  
  <p>It seems that Zeep constructs erroneous xml data, but I could be wrong (thanks to my limited knowledge of SOAP).  Let's see the example:</p>
  
  <pre><code>from zeep.client import Client, CachingClient, Settings
  from zeep.wsse.username import UsernameToken
  import zeep.helpers
  
  import logging.config
  
  # # Put Zeep into verbose mode
  logging.config.dictConfig({
      'version': 1,
      'formatters': {
          'verbose': {
              'format': '%(name)s: %(message)s'
          }
      },
      'handlers': {
          'console': {
              'level': 'DEBUG',
              'class': 'logging.StreamHandler',
              'formatter': 'verbose',
          },
      },
      'loggers': {
          'zeep.transports': {
              'level': 'DEBUG',
              'propagate': True,
              'handlers': ['console'],
          },
      }
  })
  
  ip="192.168.0.134"; user="admin"; passwd="123456"; port=80 # My home cam 1.  Now you know its username and password.  :)
  
  settings = Settings()
  settings.strict = False
  settings.xml_huge_tree = True
  
  # # WSDL File
  url = "https://www.onvif.org/ver10/events/wsdl/event.wsdl"
  
  # # *** Events Service ***
  xaddr = "http://"+ip+"/onvif/events_service"
  print("creating a soap client with url = ", url)
  zeep_client_events = CachingClient(wsdl=url, wsse=UsernameToken(user, passwd, use_digest=True), settings=settings)
  print("soap client created")
  print("binding to service")
  ws_client_events = zeep_client_events.create_service("{http://www.onvif.org/ver10/events/wsdl}EventBinding", xaddr)
  print("service OK")
  
  # # *** PullPoint Service ***
  xaddr = "http://"+ip+"/onvif/events_service"
  print("creating a soap client with url = ", url)
  zeep_client_pp = CachingClient(wsdl=url, wsse=UsernameToken(user, passwd, use_digest=True), settings=settings)
  print("soap client created")
  print("binding to service")
  ws_client_pp = zeep_client_pp.create_service("{http://www.onvif.org/ver10/events/wsdl}PullPointSubscriptionBinding", xaddr)
  print("service bound")
  
  res = ws_client_events.CreatePullPointSubscription()
  
  # # could see the namespaces like this:
  # zeep_client_pp.namespaces
  
  # # could create PullMessages' parameters like this:
  # pm = zeep_client_pp.get_element("ns7:PullMessages")()
  
  # So, this call never works
  ws_client_pp.PullMessages(MessageLimit=1, Timeout="PT1S")
  </code></pre>
  
  <p>Depending on the camera, this always results in "Remote end closed connection without response" or otherwise, the server sends a message that the value is invalid.</p>
  
  <p>When putting Zeep into verbose mode and inspecting the SOAP message body (confirmed this with Wireshark as well) it look like this:</p>
  
  <pre><code>&lt;soap-env:Body&gt;
      &lt;ns0:PullMessages xmlns:ns0="http://www.onvif.org/ver10/events/wsdl"&gt;
          &lt;ns0:Timeout&gt;P%P&lt;/ns0:Timeout&gt;
          &lt;ns0:MessageLimit&gt;1&lt;/ns0:MessageLimit&gt;
      &lt;/ns0:PullMessages&gt;
  &lt;/soap-env:Body&gt;
  </code></pre>
  
  <p>So it seems that the string "PT1S" doesn't make its way into the message body, but there persists that "P%P" instead !</p>
  
  <p>How to convince Zeep to insert the correct time in place?</p>
  
  <p>P. S. And please, don't tell me to use "python-onvif-zeep".  Of course I did that first and then ended up with this question (the examples of "python-onvif-zeep" for pullpoint services don't work)</p>