def render(self, request): try: return Resource.render(self, request) except UnsupportedMethod, e: Logr.debug("(%s) unhandled method %s", self.service.serviceType, request.method) raise e
def stop(self): Logr.debug("stop()") if not self.running: return self.notifySequenceLoop.stop() self.listen_port.stopListening()
def send_NOTIFY(self, nt, uuid=None, nts='ssdp:alive'): if self.ssdp.device.bootID is None: self.ssdp.device.bootID = int(time.time()) location = self.ssdp.device.getLocation(get_default_v4_address()) if uuid is None: uuid = self.ssdp.device.uuid usn, nt = build_notification_type(uuid, nt) Logr.debug("send_NOTIFY %s:%s", nts, usn) headers = { # max-age is notifySequenceInterval + 10 minutes 'CACHE-CONTROL': 'max-age = %d' % (self.notifySequenceInterval + (10 * 60)), 'LOCATION': location, 'SERVER': self.ssdp.device.server, 'NT': nt, 'NTS': nts, 'USN': usn, 'BOOTID.UPNP.ORG': self.ssdp.device.bootID, 'CONFIGID.UPNP.ORG': self.ssdp.device.configID } self.send('NOTIFY', headers, (SSDP_ADDR_V4, SSDP_PORT))
def stop(self): Logr.debug("stop()") if not self.running: return self.listen_port.stopListening()
def dump(self): Logr.debug("xml tree dumped") scpd = et.Element('scpd', attrib={ 'xmlns': 'urn:schemas-upnp-org:service-1-0', }) # specVersion specVersion = et.Element('specVersion') specVersion.append(make_element('major', str(self.version[0]))) specVersion.append(make_element('minor', str(self.version[1]))) scpd.append(specVersion) # actionList actionList = et.Element('actionList') for action_name, action_args in self.actions.items(): action = et.Element('action') action.append(make_element('name', action_name)) argumentList = et.Element('argumentList') for arg in action_args: argumentList.append(arg.dump()) action.append(argumentList) actionList.append(action) scpd.append(actionList) # serviceStateTable serviceStateTable = et.Element('serviceStateTable') for stateVariable in self.stateVariables.values(): serviceStateTable.append(stateVariable.dump()) scpd.append(serviceStateTable) return scpd
def listen(self): if self.running: raise Exception() Logr.debug("listen()") self.listen_port = reactor.listenMulticast(SSDP_PORT, self, listenMultiple=True) self.running = True
def render_POST(self, request): data = request.content.getvalue() (r, header, body, attrs) = parseSOAPRPC(data, header=1, body=1, attrs=1) name = r._name kwargs = r._asdict() Logr.debug("(%s) %s", self.service.serviceType, name) if name not in self.service.actions or name not in self.service.actionFunctions: raise NotImplementedError() action = self.service.actions[name] func = self.service.actionFunctions[name] for argument in action: if argument.direction == 'in': if argument.name in kwargs: value = kwargs[argument.name] del kwargs[argument.name] kwargs[argument.parameterName] = value else: raise TypeError() result = func(**kwargs) return buildSOAP(kw={ '%sResponse' % name: result })
def stop(self): if not self.running: return Logr.debug("stop()") self.site_port.stopListening() self.running = False
def render_SUBSCRIBE(self, request): Logr.debug("(%s) SUBSCRIBE", self.service.serviceType) if request.requestHeaders.hasHeader('sid'): # Renew sid = getHeader(request, 'sid') if sid in self.service.subscriptions: self.service.subscriptions[sid].last_subscribe = time.time() self.service.subscriptions[sid].expired = False Logr.debug("(%s) Successfully renewed subscription", self.service.serviceType) else: Logr.debug("(%s) Received invalid subscription renewal", self.service.serviceType) else: # New Subscription nt = self._parse_nt(getHeader(request, 'nt')) callback = self._parse_callback(getHeader(request, 'callback')) timeout = self._parse_timeout(getHeader(request, 'timeout', False)) Logr.debug("(%s) %s %s", self.service.serviceType, callback, timeout) responseHeaders = self.service.subscribe(callback, timeout) if responseHeaders is not None and type(responseHeaders) is dict: for name, value in responseHeaders.items(): request.setHeader(name, value) return '' else: Logr.debug("(%s) SUBSCRIBE FAILED", self.service.serviceType)
def startProtocol(self): self.transport.setTTL(2) for interface in self.interfaces: self.transport.joinGroup(SSDP_ADDR_V4, interface) if interface == '': Logr.debug("joined on ANY") else: Logr.debug("joined on %s", interface)
def getChild(self, path, request): if path == 'event': return ServiceEventResource(self.service) if path == 'control': return ServiceControlResource(self.service) Logr.debug("(%s) unhandled request %s", self.service.serviceType, path) return Resource()
def __init__(self, host): Logr.configure(logging.DEBUG) self.host = host self.device = TmServerDevice(host) self.upnp = UPnP(self.device) self.ssdp = SSDP(self.device, [host], host)
def listen(self): if self.running: raise Exception() Logr.debug("listen()") self.listen_port = reactor.listenUDP(0, self, self.interface) self.running = True Logr.debug("listening on %s", self.listen_port.socket.getsockname()) reactor.callLater(0, self._notifySequenceCall, True) self.notifySequenceLoop.start(self.notifySequenceInterval)
def getChild(self, path, request): # Hack to fix twisted not accepting absolute URIs path, request = twisted_absolute_path(path, request) if path == '': return ServeResource(self.device.dumps(), 'application/xml') for service in self.device.services: if path == service.serviceId: return ServiceResource(service) Logr.debug("unhandled request %s", path) return Resource()
def _default(self): if not self.initialized: raise Exception() if self.state_variable.dataType == 'string': return str() if self.state_variable.dataType == 'boolean': return False if self.state_variable.dataType == 'ui4': return 0 Logr.warning(self.state_variable.dataType + "not implemented") raise NotImplementedError()
def listen(self, interface=''): if self.running: raise Exception() Logr.debug("listen()") self.site = Site(self) self.site_port = reactor.listenTCP(0, self.site, interface=interface) self.listen_address = self.site_port.socket.getsockname()[0] self.listen_port = self.site_port.socket.getsockname()[1] self.running = True self.device.location = "http://%s:" + str(self.listen_port) Logr.debug("listening on %s:%s", self.listen_address, self.listen_port)
class SSDP_Listener(DatagramProtocol): def __init__(self, ssdp, interfaces, bind=None, responseExpire=900): self.ssdp = ssdp self.interfaces = interfaces self.responseExpire = responseExpire self.running = False self.rand = Random() self.bind = bind def listen(self): if self.running: raise Exception() Logr.debug("listen()") self.listen_port = reactor.listenMulticast(SSDP_PORT, self, listenMultiple=True) self.running = True def startProtocol(self): self.transport.setTTL(2) for interface in self.interfaces: self.transport.joinGroup(SSDP_ADDR_V4, interface) if interface == '': Logr.debug("joined on ANY") else: Logr.debug("joined on %s", interface) def stop(self): Logr.debug("stop()") if not self.running: return self.listen_port.stopListening() def datagramReceived(self, data, (address, port)): Logr.debug("datagramReceived() from %s:%s", address, port) method, path, version, headers = http_parse_raw(data) if method == 'M-SEARCH': self.received_MSEARCH(headers, (address, port)) elif method == 'NOTIFY': self.received_NOTIFY(headers, (address, port)) else: Logr.warning("Unhandled Method '%s'", method)
def notify(self, props): """ :type props: EventProperty or list of EventProperty """ if type(props) is not list: props = [props] if self.expired: return if self.check_expiration(): Logr.info("(%s) subscription expired", self.sid) return # noinspection PyTypeChecker Logr.debug("(%s) notify(), %d props: %s", self.sid, len(props), str(props)) headers = { 'NT': 'upnp:event', 'NTS': 'upnp:propchange', 'SID': self.sid, 'SEQ': self.next_notify_key } _propertyset = et.Element( 'e:propertyset', attrib={'xmlns:e': 'urn:schemas-upnp-org:event-1-0'}) for prop in props: _property = et.Element('e:property') _property.append(make_element(prop.name, str(prop.value))) _propertyset.append(_property) data = '<?xml version="1.0"?>' + et.tostring(_propertyset) try: requests.request('NOTIFY', self.callback, headers=headers, data=data) except requests.exceptions.ConnectionError: pass self._increment_notify_key()
def dump(self): Logr.debug("xml tree dumped") root = et.Element('root', attrib={'configId': str(self.configID)}) for prefix, namespace in self.namespaces.items(): if prefix == '': prefix = 'xmlns' else: prefix = 'xmlns:' + prefix root.attrib[prefix] = namespace # specVersion specVersion = et.Element('specVersion') specVersion.append(make_element('major', str(self.version[0]))) specVersion.append(make_element('minor', str(self.version[1]))) root.append(specVersion) root.append(self.dump_device()) return root
def _notifySequenceCall(self, initial=False): Logr.debug("_notifySequenceCall initial=%s", initial) # 3 + 2d + k # - 3 rootdevice # - 2d embedded devices # - k distinct services # TODO: Embedded device calls call_count = 3 + len(self.ssdp.device.services) call_delay = self.notifySequenceInterval / call_count if initial: call_delay = 1 Logr.debug("sending %d calls with delay of %ds per call, total duration of %ds", call_count, call_delay, call_count * call_delay) self.sendall_NOTIFY(call_delay)
def notify(self, props): """ :type props: EventProperty or list of EventProperty """ if type(props) is not list: props = [props] if self.expired: return if self.check_expiration(): Logr.info("(%s) subscription expired", self.sid) return # noinspection PyTypeChecker Logr.debug("(%s) notify(), %d props: %s", self.sid, len(props), str(props)) headers = { 'NT': 'upnp:event', 'NTS': 'upnp:propchange', 'SID': self.sid, 'SEQ': self.next_notify_key } _propertyset = et.Element('e:propertyset', attrib={ 'xmlns:e': 'urn:schemas-upnp-org:event-1-0' }) for prop in props: _property = et.Element('e:property') _property.append(make_element(prop.name, str(prop.value))) _propertyset.append(_property) data = '<?xml version="1.0"?>' + et.tostring(_propertyset) try: requests.request('NOTIFY', self.callback, headers=headers, data=data) except requests.exceptions.ConnectionError: pass self._increment_notify_key()
def render_UNSUBSCRIBE(self, request): Logr.debug("(%s) UNSUBSCRIBE", self.service.serviceType) if request.requestHeaders.hasHeader('sid'): # Cancel sid = getHeader(request, 'sid') if sid in self.service.subscriptions: self.service.subscriptions[sid].expired = True Logr.debug("(%s) Successfully unsubscribed", self.service.serviceType) else: Logr.debug("(%s) Received invalid UNSUBSCRIBE request", self.service.serviceType) else: Logr.debug("(%s) Received invalid UNSUBSCRIBE request", self.service.serviceType)
def dump(self): Logr.debug("xml tree dumped") root = et.Element('root', attrib={ 'configId': str(self.configID) }) for prefix, namespace in self.namespaces.items(): if prefix == '': prefix = 'xmlns' else: prefix = 'xmlns:' + prefix root.attrib[prefix] = namespace # specVersion specVersion = et.Element('specVersion') specVersion.append(make_element('major', str(self.version[0]))) specVersion.append(make_element('minor', str(self.version[1]))) root.append(specVersion) root.append(self.dump_device()) return root
class SSDP_Client(DatagramProtocol): def __init__(self, ssdp, interface, bind=None, notifyInterval=1800): self.ssdp = ssdp self.interface = interface self.notifySequenceInterval = notifyInterval self.notifySequenceLoop = task.LoopingCall(self._notifySequenceCall) self.running = False self.bind = bind def listen(self): if self.running: raise Exception() Logr.debug("listen()") self.listen_port = reactor.listenUDP(0, self, self.interface) self.running = True Logr.debug("listening on %s", self.listen_port.socket.getsockname()) reactor.callLater(0, self._notifySequenceCall, True) self.notifySequenceLoop.start(self.notifySequenceInterval) def stop(self): Logr.debug("stop()") if not self.running: return self.notifySequenceLoop.stop() self.listen_port.stopListening() def respond(self, headers, (address, port)): Logr.debug("respond() %s %d", address, port) msg = 'HTTP/1.1 200 OK\r\n' msg += headers_join(headers) msg += '\r\n\r\n' try: self.transport.write(msg, (address, port)) except socket.error, e: Logr.warning("socket.error: %s", e)
def render_POST(self, request): data = request.content.getvalue() (r, header, body, attrs) = parseSOAPRPC(data, header=1, body=1, attrs=1) name = r._name kwargs = r._asdict() Logr.debug("(%s) %s", self.service.serviceType, name) if name not in self.service.actions or name not in self.service.actionFunctions: raise NotImplementedError() action = self.service.actions[name] func = self.service.actionFunctions[name] for argument in action: if argument.direction == 'in': if argument.name in kwargs: value = kwargs[argument.name] del kwargs[argument.name] kwargs[argument.parameterName] = value else: raise TypeError() try: result = func(**kwargs) except upnpError as e: request.setResponseCode(500) fault = {'faultcode' : 's:Client', 'faultstring' : 'UPnPError'} fault['detail'] = {'UPnPError' : {'errorCode' : e.errorCode, 'errorDescription' : str(e)}} return buildSOAP(method='Fault', kw=fault, namespace='http://schemas.xmlsoap.org/soap/envelope/') #return buildSOAP(kw={ # '%sResponse' % name: result #}) return buildSOAP(method='%sResponse' % name, kw=result, namespace=self.service.serviceType)
def stop(self): Logr.debug("stop()") self.clients.stop() self.listener.stop()
def listen(self): Logr.debug("listen()") self.clients.listen() self.listener.listen()
self.notifySequenceLoop.stop() self.listen_port.stopListening() def respond(self, headers, (address, port)): Logr.debug("respond() %s %d", address, port) msg = 'HTTP/1.1 200 OK\r\n' msg += headers_join(headers) msg += '\r\n\r\n' try: self.transport.write(msg, (address, port)) except socket.error, e: Logr.warning("socket.error: %s", e) def send(self, method, headers, (address, port)): Logr.debug("send() %s:%s", address, port) msg = '%s * HTTP/1.1\r\n' % method msg += headers_join(headers) msg += '\r\n\r\n' try: self.transport.write(msg, (address, port)) except socket.error, e: Logr.warning("socket.error: %s", e) def send_NOTIFY(self, nt, uuid=None, nts='ssdp:alive'): if self.ssdp.device.bootID is None: self.ssdp.device.bootID = int(time.time()) location = self.ssdp.device.getLocation(get_default_v4_address())
def stop(self): Logr.debug("stop()") for client in self.clients: client.stop()
self.notifySequenceLoop.stop() self.listen_port.stopListening() def respond(self, headers, (address, port)): Logr.debug("respond() %s %d", address, port) msg = 'HTTP/1.1 200 OK\r\n' msg += headers_join(headers) msg += '\r\n\r\n' try: self.transport.write(msg, (address, port)) except socket.error, e: Logr.warning("socket.error: %s", e) def send(self, method, headers, (address, port)): Logr.debug("send() %s:%s", address, port) msg = '%s * HTTP/1.1\r\n' % method msg += headers_join(headers) msg += '\r\n\r\n' try: self.transport.write(msg, (address, port)) except socket.error, e: Logr.warning("socket.error: %s", e) def send_NOTIFY(self, nt, uuid=None, nts='ssdp:alive'): if self.ssdp.device.bootID is None: self.ssdp.device.bootID = int(time.time()) if not self.bind: location = self.ssdp.device.getLocation(get_default_v4_address())
def registerDevice(self, request): Logr.warning("RegisterDevice not implemented") return {'RegistrationRespMsg': None}
def listen(self): Logr.debug("listen()") for client in self.clients: client.listen()
getattr(self, command)() except EOFError: self.command_stop() def command_stop(self): # Send 'byebye' NOTIFY self.ssdp.clients.sendall_NOTIFY(None, 'ssdp:byebye', True) # Stop everything self.upnp.stop() self.ssdp.stop() reactor.stop() self.running = False if __name__ == '__main__': Logr.configure(logging.DEBUG) device = MediaServerDevice() upnp = UPnP(device) ssdp = SSDP(device) upnp.listen() ssdp.listen() def event_test(): device.contentDirectory.system_update_id = time.time() reactor.callLater(5, event_test) event_test()
except EOFError: self.command_stop() def command_stop(self): # Send 'byebye' NOTIFY self.ssdp.clients.sendall_NOTIFY(None, 'ssdp:byebye', True) # Stop everything self.upnp.stop() self.ssdp.stop() reactor.stop() self.running = False if __name__ == '__main__': Logr.configure(logging.DEBUG) device = MediaServerDevice() upnp = UPnP(device) ssdp = SSDP(device) upnp.listen() ssdp.listen() def event_test(): # device.contentDirectory.system_update_id = time.time() reactor.callLater(5, event_test) event_test()
def registerDevice(self, request): Logr.warning("RegisterDevice not implemented") return { 'RegistrationRespMsg': None }