class HelperClient(object):
    """
    Helper Client class to perform requests to remote servers in a simplified way.
    """
    def __init__(self,
                 server,
                 sock=None,
                 cb_ignore_read_exception=None,
                 cb_ignore_write_exception=None):
        """
        Initialize a client to perform request to a server.

        :param server: the remote CoAP server
        :param sock: if a socket has been created externally, it can be used directly
        :param cb_ignore_read_exception: Callback function to handle exception raised during the socket read operation
        :param cb_ignore_write_exception: Callback function to handle exception raised during the socket write operation 
        """
        self.server = server
        self.protocol = CoAP(
            self.server,
            random.randint(1, 65535),
            self._wait_response,
            sock=sock,
            cb_ignore_read_exception=cb_ignore_read_exception,
            cb_ignore_write_exception=cb_ignore_write_exception)

        self.requests_lock = threading.RLock()
        self.requests = dict()

    def _wait_response(self, message):
        """
        Private function to get responses from the server.

        :param message: the received message
        """
        if message.code == defines.Codes.CONTINUE.number:
            return
        with self.requests_lock:
            if message.token not in self.requests:
                return
            context = self.requests[message.token]
            if message.timeouted:
                # Message is actually the original timed out request (not the response), discard content
                message = None
            if hasattr(context, 'callback'):
                if not hasattr(context.request, 'observe'):
                    # OBSERVE stays until cancelled, for all others we're done
                    del self.requests[message.token]
                context.callback(message)
            else:
                # Signal that a response is available to blocking call
                context.response = message
                context.responded.set()

    def stop(self):
        """
        Stop the client.
        """
        self.protocol.close()
        with self.requests_lock:
            # Unblock/signal waiters
            for token in self.requests:
                context = self.requests[token]
                if hasattr(context, 'callback'):
                    context.callback(None)
                else:
                    context.responded.set()

    def close(self):
        """
        Close the client.
        """
        self.stop()

    def cancel_observe_token(self,
                             token,
                             explicit,
                             timeout=None):  # pragma: no cover
        """
        Delete observing on the remote server.

        :param token: the observe token
        :param explicit: if explicitly cancel
        :type explicit: bool
        """
        with self.requests_lock:
            if token not in self.requests:
                return
            if not hasattr(self.requests[token].request, 'observe'):
                return
            context = self.requests[token]
            del self.requests[token]

        self.protocol.end_observation(token)

        if not explicit:
            return

        request = self.mk_request(defines.Codes.GET, context.request.uri_path)

        # RFC7641 explicit cancel is by sending OBSERVE=1 with the same token,
        # not by an unsolicited RST (which would be ignored)
        request.token = token
        request.observe = 1

        self.send_request(request, callback=None, timeout=timeout)

    def cancel_observing(self, response, explicit):  # pragma: no cover
        """
        Delete observing on the remote server.

        :param response: the last received response
        :param explicit: if explicitly cancel using token
        :type send_rst: bool
        """
        self.cancel_observe_token(self, response.token, explicit)

    def get(self,
            path,
            proxy_uri=None,
            callback=None,
            timeout=None,
            **kwargs):  # pragma: no cover
        """
        Perform a GET on a certain path.

        :param path: the path
        :param proxy_uri: Proxy-Uri option of a request
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.GET, path)
        request.token = generate_random_token(2)
        if proxy_uri:
            request.proxy_uri = proxy_uri

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def observe(self,
                path,
                callback,
                uri_query=None,
                timeout=None,
                **kwargs):  # pragma: no cover
        """
        Perform a GET with observe on a certain path.

        :param path: the path
        :param callback: the callback function to invoke upon notifications
        :param timeout: the timeout of the request
        :return: the response to the observe request
        """
        request = self.mk_request(defines.Codes.GET, path)
        request.token = generate_random_token(2)
        request.observe = 0
        if uri_query is not None:
            request.uri_query = uri_query

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def delete(self,
               path,
               proxy_uri=None,
               callback=None,
               timeout=None,
               **kwargs):  # pragma: no cover
        """
        Perform a DELETE on a certain path.

        :param path: the path
        :param proxy_uri: Proxy-Uri option of a request
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.DELETE, path)
        request.token = generate_random_token(2)
        if proxy_uri:
            request.proxy_uri = proxy_uri

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def post(self,
             path,
             payload,
             proxy_uri=None,
             callback=None,
             timeout=None,
             **kwargs):  # pragma: no cover
        """
        Perform a POST on a certain path.

        :param path: the path
        :param payload: the request payload
        :param proxy_uri: Proxy-Uri option of a request
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.POST, path)
        request.token = generate_random_token(2)
        request.payload = payload
        if proxy_uri:
            request.proxy_uri = proxy_uri

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def put(self,
            path,
            payload,
            proxy_uri=None,
            callback=None,
            timeout=None,
            **kwargs):  # pragma: no cover
        """
        Perform a PUT on a certain path.

        :param path: the path
        :param payload: the request payload
        :param proxy_uri: Proxy-Uri option of a request
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.PUT, path)
        request.token = generate_random_token(2)
        request.payload = payload
        if proxy_uri:
            request.proxy_uri = proxy_uri

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def discover(self,
                 callback=None,
                 timeout=None,
                 **kwargs):  # pragma: no cover
        """
        Perform a Discover request on the server.

        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.GET, defines.DISCOVERY_URL)
        request.token = generate_random_token(2)

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def send_request(self,
                     request,
                     callback=None,
                     timeout=None):  # pragma: no cover
        """
        Send a request to the remote server.

        :param request: the request to send
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response (synchronous), or the token (for asynchronous callback)
        """

        with self.requests_lock:
            # Same requests from the same endpoint must have different tokens
            # Ensure there is a unique token in case the other side issues a
            # delayed response after a standalone ACK
            while request.token in self.requests:
                request.token = generate_random_token(2)
            context = _RequestContext(request, callback)
            self.requests[request.token] = context
        self.protocol.send_message(request)
        if callback:
            # So that requester can cancel asynchronous OBSERVE
            return request.token

        # Wait for response
        context.responded.wait(timeout)
        del self.requests[request.token]
        return context.response

    def send_empty(self, empty):  # pragma: no cover
        """
        Send empty message.

        :param empty: the empty message
        """
        self.protocol.send_message(empty)

    def mk_request(self, method, path):
        """
        Create a request.

        :param method: the CoAP method
        :param path: the path of the request
        :return:  the request
        """
        request = Request()
        request.destination = self.server
        request.code = method.number
        request.uri_path = path
        return request
示例#2
0
class HelperClient(object):
    def __init__(self, server):
        self.server = server
        self.protocol = CoAP(self.server, random.randint(1, 65535), self._wait_response)
        self.queue = Queue()

    def _wait_response(self, message):
        if message.code != defines.Codes.CONTINUE.number:
            self.queue.put(message)

    def stop(self):
        self.protocol.stopped.set()
        self.queue.put(None)

    def _thread_body(self, request, callback):
        self.protocol.send_message(request)
        while not self.protocol.stopped.isSet():
            response = self.queue.get(block=True)
            callback(response)

    def cancel_observing(self, response, send_rst):  # pragma: no cover
        if send_rst:
            message = Message()
            message.destination = self.server
            message.code = defines.Codes.EMPTY.number
            message.type = defines.Types["RST"]
            message.token = response.token
            message.mid = response.mid
            self.protocol.send_message(message)
        self.stop()

    def get(self, path, callback=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.GET.number
        request.uri_path = path

        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True)
            return response

    def observe(self, path, callback):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.GET.number
        request.uri_path = path
        request.observe = 0

        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True)
            return response

    def delete(self, path, callback=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.DELETE.number
        request.uri_path = path
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True)
            return response

    def post(self, path, payload, callback=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.POST.number
        request.token = generate_random_token(2)
        request.uri_path = path
        request.payload = payload
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True)
            return response

    def put(self, path, payload, callback=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.PUT.number
        request.uri_path = path
        request.payload = payload
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True)
            return response

    def discover(self, callback=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.GET.number
        request.uri_path = defines.DISCOVERY_URL
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True)
            return response

    def send_request(self, request, callback=None):  # pragma: no cover
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True)
            return response

    def send_empty(self, empty):  # pragma: no cover
        self.protocol.send_message(empty)
示例#3
0
class HelperClient(object):
    """
    Helper Client class to perform requests to remote servers in a simplified way.
    """
    def __init__(self,
                 server,
                 sock=None,
                 cb_ignore_read_exception=None,
                 cb_ignore_write_exception=None):
        """
        Initialize a client to perform request to a server.

        :param server: the remote CoAP server
        :param sock: if a socket has been created externally, it can be used directly
        :param cb_ignore_read_exception: Callback function to handle exception raised during the socket read operation
        :param cb_ignore_write_exception: Callback function to handle exception raised during the socket write operation 
        """
        self.server = server
        self.protocol = CoAP(
            self.server,
            random.randint(1, 65535),
            self._wait_response,
            sock=sock,
            cb_ignore_read_exception=cb_ignore_read_exception,
            cb_ignore_write_exception=cb_ignore_write_exception)
        self.queue = Queue()

    def _wait_response(self, message):
        """
        Private function to get responses from the server.

        :param message: the received message
        """
        if message is None or message.code != defines.Codes.CONTINUE.number:
            self.queue.put(message)

    def stop(self):
        """
        Stop the client.
        """
        self.protocol.close()
        self.queue.put(None)

    def close(self):
        """
        Close the client.
        """
        self.stop()

    def _thread_body(self, request, callback):
        """
        Private function. Send a request, wait for response and call the callback function.

        :param request: the request to send
        :param callback: the callback function
        """
        self.protocol.send_message(request)
        while not self.protocol.stopped.isSet():
            response = self.queue.get(block=True)
            callback(response)

    def cancel_observing(self, response, send_rst):  # pragma: no cover
        """
        Delete observing on the remote server.

        :param response: the last received response
        :param send_rst: if explicitly send RST message
        :type send_rst: bool
        """
        if send_rst:
            message = Message()
            message.destination = self.server
            message.code = defines.Codes.EMPTY.number
            message.type = defines.Types["RST"]
            message.token = response.token
            message.mid = response.mid
            self.protocol.send_message(message)
        self.stop()

    def get(self,
            path,
            callback=None,
            timeout=None,
            **kwargs):  # pragma: no cover
        """
        Perform a GET on a certain path.

        :param path: the path
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.GET, path)
        request.token = generate_random_token(2)

        for k, v in kwargs.items():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def get_non(self,
                path,
                callback=None,
                timeout=None,
                **kwargs):  # pragma: no cover
        """
        Perform a GET on a certain path.

        :param path: the path
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request_non(defines.Codes.GET, path)
        request.token = generate_random_token(2)

        for k, v in kwargs.items():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def observe(self,
                path,
                callback,
                timeout=None,
                **kwargs):  # pragma: no cover
        """
        Perform a GET with observe on a certain path.

        :param path: the path
        :param callback: the callback function to invoke upon notifications
        :param timeout: the timeout of the request
        :return: the response to the observe request
        """
        request = self.mk_request(defines.Codes.GET, path)
        request.observe = 0

        for k, v in kwargs.items():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def delete(self,
               path,
               callback=None,
               timeout=None,
               **kwargs):  # pragma: no cover
        """
        Perform a DELETE on a certain path.

        :param path: the path
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.DELETE, path)

        for k, v in kwargs.items():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def post(self,
             path,
             payload,
             callback=None,
             timeout=None,
             no_response=False,
             **kwargs):  # pragma: no cover
        """
        Perform a POST on a certain path.

        :param path: the path
        :param payload: the request payload
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.POST, path)
        request.token = generate_random_token(2)
        request.payload = payload

        if no_response:
            request.add_no_response()
            request.type = defines.Types["NON"]

        for k, v in kwargs.items():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request,
                                 callback,
                                 timeout,
                                 no_response=no_response)

    def put(self,
            path,
            payload,
            callback=None,
            timeout=None,
            no_response=False,
            **kwargs):  # pragma: no cover
        """
        Perform a PUT on a certain path.

        :param path: the path
        :param payload: the request payload
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.PUT, path)
        request.token = generate_random_token(2)
        request.payload = payload

        if no_response:
            request.add_no_response()
            request.type = defines.Types["NON"]

        for k, v in kwargs.items():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request,
                                 callback,
                                 timeout,
                                 no_response=no_response)

    def discover(self,
                 callback=None,
                 timeout=None,
                 **kwargs):  # pragma: no cover
        """
        Perform a Discover request on the server.

        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.GET, defines.DISCOVERY_URL)

        for k, v in kwargs.items():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def send_request(self,
                     request,
                     callback=None,
                     timeout=None,
                     no_response=False):  # pragma: no cover
        """
        Send a request to the remote server.

        :param request: the request to send
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        if callback is not None:
            thread = threading.Thread(target=self._thread_body,
                                      args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            if no_response:
                return
            try:
                response = self.queue.get(block=True, timeout=timeout)
            except Empty:
                #if timeout is set
                response = None
            return response

    def send_empty(self, empty):  # pragma: no cover
        """
        Send empty message.

        :param empty: the empty message
        """
        self.protocol.send_message(empty)

    def mk_request(self, method, path):
        """
        Create a request.

        :param method: the CoAP method
        :param path: the path of the request
        :return:  the request
        """
        request = Request()
        request.destination = self.server
        request.code = method.number
        request.uri_path = path
        return request

    def mk_request_non(self, method, path):
        """
        Create a request.

        :param method: the CoAP method
        :param path: the path of the request
        :return:  the request
        """
        request = Request()
        request.destination = self.server
        request.code = method.number
        request.uri_path = path
        request.type = defines.Types["NON"]
        return request
示例#4
0
class Client(object):
    def __init__(self, server):
        self.server = server
        self.protocol = CoAP(self.server, random.randint(1, 65535),
                             self._wait_response, self._timeout)
        self.queue = Queue()
        self.running = True

    def _wait_response(self, message):
        if message.code != defines.Codes.CONTINUE.number:
            self.queue.put(message)

    def _timeout(self, message):
        self.queue.put(None)

    def stop(self):
        self.running = False
        # self.protocol.stop()
        self.queue.put(None)

    def get(self, path, payload=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.GET.number
        request.uri_path = path
        request.payload = payload

        # Clear out queue before sending a request. It is possible that an old
        # response was received between requests. We don't want the requests
        # and responses to be mismatched. I expect the protocol to take care of
        # that, but I don't have confidence in the CoAP library.
        try:
            while True:
                self.queue.get_nowait()
        except Empty:
            pass

        self.protocol.send_message(request)
        response = self.queue.get(block=True)
        return response

    def multicast_discover(self):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.GET.number
        request.uri_path = defines.DISCOVERY_URL

        self.protocol.send_message(request)
        first_response = self.queue.get(block=True)

        if first_response is None:
            # The message timed out
            return []

        responses = [first_response]
        try:
            # Keep trying to get more responses if they come in
            while self.running:
                responses.append(self.queue.get(block=True, timeout=10))
        except Empty:
            pass

        return responses
示例#5
0
class HelperClient(object):
    """
    Helper Client class to perform requests to remote servers in a simplified way.
    """
    def __init__(self, server, sock=None, cb_ignore_read_exception=None, cb_ignore_write_exception=None):
        """
        Initialize a client to perform request to a server.

        :param server: the remote CoAP server
        :param sock: if a socket has been created externally, it can be used directly
        :param cb_ignore_read_exception: Callback function to handle exception raised during the socket read operation
        :param cb_ignore_write_exception: Callback function to handle exception raised during the socket write operation 
        """
        self.server = server
        self.protocol = CoAP(self.server, random.randint(1, 65535), self._wait_response, sock=sock,
                             cb_ignore_read_exception=cb_ignore_read_exception, cb_ignore_write_exception=cb_ignore_write_exception)

        self.requests_lock = threading.RLock()
        self.requests = dict()

    def _wait_response(self, message):
        """
        Private function to get responses from the server.

        :param message: the received message
        """
        if message.code == defines.Codes.CONTINUE.number:
            return
        with self.requests_lock:
            if message.token not in self.requests:
                return
            context = self.requests[message.token]
            if message.timeouted:
                # Message is actually the original timed out request (not the response), discard content
                message = None
            if hasattr(context, 'callback'):
                if not hasattr(context.request, 'observe'):
                    # OBSERVE stays until cancelled, for all others we're done
                    del self.requests[message.token]
                context.callback(message)
            else:
                # Signal that a response is available to blocking call
                context.response = message
                context.responded.set()

    def stop(self):
        """
        Stop the client.
        """
        self.protocol.close()
        with self.requests_lock:
            # Unblock/signal waiters
            for token in self.requests:
                context = self.requests[token]
                if hasattr(context, 'callback'):
                    context.callback(None)
                else:
                    context.responded.set()

    def close(self):
        """
        Close the client.
        """
        self.stop()

    def cancel_observe_token(self, token, explicit, timeout=None):  # pragma: no cover
        """
        Delete observing on the remote server.

        :param token: the observe token
        :param explicit: if explicitly cancel
        :type explicit: bool
        """
        with self.requests_lock:
            if token not in self.requests:
                return
            if not hasattr(self.requests[token].request, 'observe'):
                return
            context = self.requests[token]
            del self.requests[token]

        self.protocol.end_observation(token)

        if not explicit:
            return

        request = self.mk_request(defines.Codes.GET, context.request.uri_path)

        # RFC7641 explicit cancel is by sending OBSERVE=1 with the same token,
        # not by an unsolicited RST (which would be ignored)
        request.token = token
        request.observe = 1

        self.send_request(request, callback=None, timeout=timeout)
        
    def cancel_observing(self, response, explicit):  # pragma: no cover
        """
        Delete observing on the remote server.

        :param response: the last received response
        :param explicit: if explicitly cancel using token
        :type send_rst: bool
        """
        self.cancel_observe_token(self, response.token, explicit)

    def get(self, path, proxy_uri=None, callback=None, timeout=None, **kwargs):  # pragma: no cover
        """
        Perform a GET on a certain path.

        :param path: the path
        :param proxy_uri: Proxy-Uri option of a request
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.GET, path)
        request.token = generate_random_token(2)
        if proxy_uri:
            request.proxy_uri = proxy_uri

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def observe(self, path, callback, timeout=None, **kwargs):  # pragma: no cover
        """
        Perform a GET with observe on a certain path.

        :param path: the path
        :param callback: the callback function to invoke upon notifications
        :param timeout: the timeout of the request
        :return: the response to the observe request
        """
        request = self.mk_request(defines.Codes.GET, path)
        request.token = generate_random_token(2)
        request.observe = 0

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def delete(self, path, proxy_uri=None, callback=None, timeout=None, **kwargs):  # pragma: no cover
        """
        Perform a DELETE on a certain path.

        :param path: the path
        :param proxy_uri: Proxy-Uri option of a request
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.DELETE, path)
        request.token = generate_random_token(2)
        if proxy_uri:
            request.proxy_uri = proxy_uri

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def post(self, path, payload, proxy_uri=None, callback=None, timeout=None, **kwargs):  # pragma: no cover
        """
        Perform a POST on a certain path.

        :param path: the path
        :param payload: the request payload
        :param proxy_uri: Proxy-Uri option of a request
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.POST, path)
        request.token = generate_random_token(2)
        request.payload = payload
        if proxy_uri:
            request.proxy_uri = proxy_uri

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def put(self, path, payload, proxy_uri=None, callback=None, timeout=None, **kwargs):  # pragma: no cover
        """
        Perform a PUT on a certain path.

        :param path: the path
        :param payload: the request payload
        :param proxy_uri: Proxy-Uri option of a request
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.PUT, path)
        request.token = generate_random_token(2)
        request.payload = payload
        if proxy_uri:
            request.proxy_uri = proxy_uri

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def discover(self, callback=None, timeout=None, **kwargs):  # pragma: no cover
        """
        Perform a Discover request on the server.

        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response
        """
        request = self.mk_request(defines.Codes.GET, defines.DISCOVERY_URL)
        request.token = generate_random_token(2)

        for k, v in kwargs.iteritems():
            if hasattr(request, k):
                setattr(request, k, v)

        return self.send_request(request, callback, timeout)

    def send_request(self, request, callback=None, timeout=None):  # pragma: no cover
        """
        Send a request to the remote server.

        :param request: the request to send
        :param callback: the callback function to invoke upon response
        :param timeout: the timeout of the request
        :return: the response (synchronous), or the token (for asynchronous callback)
        """

        with self.requests_lock:
            # Same requests from the same endpoint must have different tokens
            # Ensure there is a unique token in case the other side issues a
            # delayed response after a standalone ACK
            while request.token in self.requests:
                request.token = generate_random_token(2)
            context = _RequestContext(request, callback)
            self.requests[request.token] = context
        self.protocol.send_message(request)
        if callback:
            # So that requester can cancel asynchronous OBSERVE
            return request.token

        # Wait for response
        context.responded.wait(timeout)
        del self.requests[request.token]
        return context.response


    def send_empty(self, empty):  # pragma: no cover
        """
        Send empty message.

        :param empty: the empty message
        """
        self.protocol.send_message(empty)

    def mk_request(self, method, path):
        """
        Create a request.

        :param method: the CoAP method
        :param path: the path of the request
        :return:  the request
        """
        request = Request()
        request.destination = self.server
        request.code = method.number
        request.uri_path = path
        return request
class HelperClient(object):
    def __init__(self, server):
        self.server = server
        self.protocol = CoAP(self.server, random.randint(1, 65535), self._wait_response)
        self.queue = Queue()

    def _wait_response(self, message):
        if message.code != defines.Codes.CONTINUE.number:
            self.queue.put(message)

    def stop(self):
        self.protocol.stopped.set()
        self.queue.put(None)

    def _thread_body(self, request, callback):
        self.protocol.send_message(request)
        while not self.protocol.stopped.isSet():
            response = self.queue.get(block=True)
            callback(response)

    def cancel_observing(self, response, send_rst):  # pragma: no cover
        if send_rst:
            message = Message()
            message.destination = self.server
            message.code = defines.Codes.EMPTY.number
            message.type = defines.Types["RST"]
            message.token = response.token
            message.mid = response.mid
            self.protocol.send_message(message)
        self.stop()

    def get(self, path, callback=None, timeout=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.GET.number
        request.uri_path = path

        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True, timeout=timeout)
            return response

    def observe(self, path, callback, timeout=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.GET.number
        request.uri_path = path
        request.observe = 0

        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True, timeout=timeout)
            return response

    def delete(self, path, callback=None, timeout=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.DELETE.number
        request.uri_path = path
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True, timeout=timeout)
            return response

    def post(self, path, payload, callback=None, timeout=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.POST.number
        request.token = generate_random_token(2)
        request.uri_path = path
        request.payload = payload
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True, timeout=timeout)
            return response

    def put(self, path, payload, callback=None, timeout=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.PUT.number
        request.uri_path = path
        request.payload = payload
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True, timeout=timeout)
            return response

    def discover(self, callback=None, timeout=None):  # pragma: no cover
        request = Request()
        request.destination = self.server
        request.code = defines.Codes.GET.number
        request.uri_path = defines.DISCOVERY_URL
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True, timeout=timeout)
            return response

    def send_request(self, request, callback=None, timeout=None):  # pragma: no cover
        if callback is not None:
            thread = threading.Thread(target=self._thread_body, args=(request, callback))
            thread.start()
        else:
            self.protocol.send_message(request)
            response = self.queue.get(block=True, timeout=timeout)
            return response

    def send_empty(self, empty):  # pragma: no cover
        self.protocol.send_message(empty)