Search code examples
androidandroid-studioraspberry-pidlnassdp

Search for raspberry pi on network using Android


I am working on raspberry pi and android application in which everytime application is get opned it search for raspberry pi in network and if raspberry pi is found then do further operation else give aknwolegement to the user. I just need IP address of raspberry pi to do further process.

Solution purposed -

  1. Making raspberry pi IP address static - Not applicable because application distributed from play store and dont have access to router.

  2. Searching for the raspberry pi in network - Working on this.

What i tried is used SSDP, DLNA, UPNP protocol to create a server on raspberry pi and everytime app comes online search for the raspberry pi in network.

Used resourcee

  1. https://github.com/resourcepool/ssdp-client
  2. https://gist.github.com/ismaelgaudioso/4cff466459646e022332
  3. https://gist.githubusercontent.com/ismaelgaudioso/4cff466459646e022332/raw/2f9fb030790102c31bc656a960307028c28bad51/server.py
  4. https://www.javatips.net/api/serket-master/serket-ssdp/src/main/java/org/saintandreas/serket/ssdp/SSDPServer.java

Here is what i have done

private static final String tag = "SSDP";

    private static final String query = "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: 1\r\n" +
            //"ST: urn:schemas-upnp-org:device:MediaServer:1\r\n" +
            "ST: ssdp:all\r\n"+
            "\r\n";

    private static final int port = 1900;

    String request() {

        String response = "";
        byte[] sendData;
        byte[] receiveData = new byte[1024];
        sendData = query.getBytes();
        DatagramPacket sendPacket = null;

        try {
            sendPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("239.255.255.250"), port);
        } catch (UnknownHostException e) {
            Log.d(tag, "Unknown Host Exception Thrown after creating DatagramPacket to send to server");
            e.printStackTrace();
        }

        DatagramSocket clientSocket = null;
        try {
            clientSocket = new DatagramSocket();
        } catch (SocketException e) {
            Log.d(tag, "Socket Exception thrown when creating socket to transport data");
            e.printStackTrace();
        }

        try {
            if (clientSocket != null) {
                clientSocket.setSoTimeout(50000);
                clientSocket.send(sendPacket);
            }
        } catch (IOException e) {
            Log.d(tag, "IOException thrown when sending data to socket");
            e.printStackTrace();
        }

        DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
        try {
            if (clientSocket != null) {
                clientSocket.receive(receivePacket);
            }
        } catch (IOException e) {
            Log.d(tag, "IOException thrown when receiving data");
            e.printStackTrace();
        }
        //the target package should not be empty
        //try three times

        for (int i = 0; i < 3; i++) {
            Log.d(tag, "Checking target package to see if its empty on iteration#: " + i);
            response = new String(receivePacket.getData());
            Log.d(tag, "Response contains: " + response);
            if (response.contains("Location:")) {
                break;
            }
        }


        String adress = "";
        //filter IP address from "Location"
        Matcher ma = Pattern.compile("Location: (.*)").matcher(response);
        if (ma.find()) {
            adress += ma.group(1);
            adress = adress.split("/")[2].split(":")[0];
        }

        return adress;

    }

Using above methode and solution i was able to find out router IP address everytime but not of pi. Also gone through each of every library i can found on internet but not worked. Apart from this method if there any other way suggested will be appraciated.


Solution

  • Now eventually i figured out the solution i will share my answer step by step. I am using ssdp protocol to find out the Pi on network where the Pi have dynamic Ip address. So i created the server in python and using android as a client. lets start with server first -

    Also you can find the server script https://github.com/ZeWaren/python-upnp-ssdp-example

    from lib.ssdp import SSDPServer
    from lib.upnp_http_server import UPNPHTTPServer
    import uuid
    import netifaces as ni
    from time import sleep
    import logging
    
    NETWORK_INTERFACE = 'wlp2s0'
    
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    
    
    def get_network_interface_ip_address(interface='eth0'):
    
        while True:
            if NETWORK_INTERFACE not in ni.interfaces():
                logger.error('Could not find interface %s.' % (interface,))
                exit(1)
            interface = ni.ifaddresses(interface)
            if (2 not in interface) or (len(interface[2]) == 0):
                logger.warning('Could not find IP of interface %s. Sleeping.' % (interface,))
                sleep(60)
                continue
            return interface[2][0]['addr']
    
    
    device_uuid = uuid.uuid4()
    local_ip_address = get_network_interface_ip_address(NETWORK_INTERFACE)
    
    http_server = UPNPHTTPServer(8088,
                                 friendly_name="Personal Home",
                                 manufacturer="Home Personal SAS",
                                 manufacturer_url='http://www.example.com/',
                                 model_description='Home Appliance 3000',
                                 model_name="Personal",
                                 model_number="3000",
                                 model_url="http://www.example.com/en/prducts/personal-3000/",
                                 serial_number="PER425133",
                                 uuid=device_uuid,
                                 presentation_url="http://{}:5000/".format(local_ip_address))
    http_server.start()
    
    
    ssdp = SSDPServer()
    ssdp.register('local',
                  'uuid:{}::upnp:rootdevice'.format(device_uuid),
                  'urn:schemas-upnp-org:device:MediaServer:1',
                  'http://{}:8088/jambon-3000.xml'.format(local_ip_address))
    ssdp.run()
    

    This will create an exuction point for the server script.

    import random
    import time
    import socket
    import logging
    from email.utils import formatdate
    from errno import ENOPROTOOPT
    
    SSDP_PORT = 1900
    SSDP_ADDR = '239.255.255.250'
    SERVER_ID = 'Personal Home SSDP Server'
    
    
    logger = logging.getLogger()
    
    
    class SSDPServer:
        known = {}
    
        def __init__(self):
            self.sock = None
    
        def run(self):
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            if hasattr(socket, "SO_REUSEPORT"):
                try:
                    self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
                except socket.error as le:
                    # RHEL6 defines SO_REUSEPORT but it doesn't work
                    if le.errno == ENOPROTOOPT:
                        pass
                    else:
                        raise
    
            addr = socket.inet_aton(SSDP_ADDR)
            interface = socket.inet_aton('0.0.0.0')
            cmd = socket.IP_ADD_MEMBERSHIP
            self.sock.setsockopt(socket.IPPROTO_IP, cmd, addr + interface)
            self.sock.bind(('0.0.0.0', SSDP_PORT))
            self.sock.settimeout(1)
    
            while True:
                try:
                    data, addr = self.sock.recvfrom(1024)
                    self.datagram_received(data, addr)
                except socket.timeout:
                    continue
            self.shutdown()
    
        def shutdown(self):
            for st in self.known:
                if self.known[st]['MANIFESTATION'] == 'local':
                    self.do_byebye(st)
    
        def datagram_received(self, data, host_port):
            """Handle a received multicast datagram."""
    
            (host, port) = host_port
    
            try:
                header, payload = data.decode().split('\r\n\r\n')[:2]
            except ValueError as err:
                logger.error(err)
                return
    
            lines = header.split('\r\n')
            cmd = lines[0].split(' ')
            lines = map(lambda x: x.replace(': ', ':', 1), lines[1:])
            lines = filter(lambda x: len(x) > 0, lines)
    
            headers = [x.split(':', 1) for x in lines]
            headers = dict(map(lambda x: (x[0].lower(), x[1]), headers))
    
            logger.info('SSDP command %s %s - from %s:%d' % (cmd[0], cmd[1], host, port))
            logger.debug('with headers: {}.'.format(headers))
            if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
                # SSDP discovery
                self.discovery_request(headers, (host, port))
            elif cmd[0] == 'NOTIFY' and cmd[1] == '*':
                # SSDP presence
                logger.debug('NOTIFY *')
            else:
                logger.warning('Unknown SSDP command %s %s' % (cmd[0], cmd[1]))
    
        def register(self, manifestation, usn, st, location, server=SERVER_ID, cache_control='max-age=1800', silent=False,
                     host=None):
            """Register a service or device that this SSDP server will
            respond to."""
    
            logging.info('Registering %s (%s)' % (st, location))
    
            self.known[usn] = {}
            self.known[usn]['USN'] = usn
            self.known[usn]['LOCATION'] = location
            self.known[usn]['ST'] = st
            self.known[usn]['EXT'] = ''
            self.known[usn]['SERVER'] = server
            self.known[usn]['CACHE-CONTROL'] = cache_control
    
            self.known[usn]['MANIFESTATION'] = manifestation
            self.known[usn]['SILENT'] = silent
            self.known[usn]['HOST'] = host
            self.known[usn]['last-seen'] = time.time()
    
            if manifestation == 'local' and self.sock:
                self.do_notify(usn)
    
        def unregister(self, usn):
            logger.info("Un-registering %s" % usn)
            del self.known[usn]
    
        def is_known(self, usn):
            return usn in self.known
    
        def send_it(self, response, destination, delay, usn):
            logger.debug('send discovery response delayed by %ds for %s to %r' % (delay, usn, destination))
            try:
                self.sock.sendto(response.encode(), destination)
            except (AttributeError, socket.error) as msg:
                logger.warning("failure sending out byebye notification: %r" % msg)
    
        def discovery_request(self, headers, host_port):
            """Process a discovery request.  The response must be sent to
            the address specified by (host, port)."""
    
            (host, port) = host_port
    
            logger.info('Discovery request from (%s,%d) for %s' % (host, port, headers['st']))
            logger.info('Discovery request for %s' % headers['st'])
    
            # Do we know about this service?
            for i in self.known.values():
                if i['MANIFESTATION'] == 'remote':
                    continue
                if headers['st'] == 'ssdp:all' and i['SILENT']:
                    continue
                if i['ST'] == headers['st'] or headers['st'] == 'ssdp:all':
                    response = ['HTTP/1.1 200 OK']
    
                    usn = None
                    for k, v in i.items():
                        if k == 'USN':
                            usn = v
                        if k not in ('MANIFESTATION', 'SILENT', 'HOST'):
                            response.append('%s: %s' % (k, v))
    
                    if usn:
                        response.append('DATE: %s' % formatdate(timeval=None, localtime=False, usegmt=True))
    
                        response.extend(('', ''))
                        delay = random.randint(0, int(headers['mx']))
    
                        self.send_it('\r\n'.join(response), (host, port), delay, usn)
    
        def do_notify(self, usn):
            """Do notification"""
    
            if self.known[usn]['SILENT']:
                return
            logger.info('Sending alive notification for %s' % usn)
    
            resp = [
                'NOTIFY * HTTP/1.1',
                'HOST: %s:%d' % (SSDP_ADDR, SSDP_PORT),
                'NTS: ssdp:alive',
            ]
            stcpy = dict(self.known[usn].items())
            stcpy['NT'] = stcpy['ST']
            del stcpy['ST']
            del stcpy['MANIFESTATION']
            del stcpy['SILENT']
            del stcpy['HOST']
            del stcpy['last-seen']
    
            resp.extend(map(lambda x: ': '.join(x), stcpy.items()))
            resp.extend(('', ''))
            logger.debug('do_notify content', resp)
            try:
                self.sock.sendto('\r\n'.join(resp).encode(), (SSDP_ADDR, SSDP_PORT))
                self.sock.sendto('\r\n'.join(resp).encode(), (SSDP_ADDR, SSDP_PORT))
            except (AttributeError, socket.error) as msg:
                logger.warning("failure sending out alive notification: %r" % msg)
    
        def do_byebye(self, usn):
            """Do byebye"""
    
            logger.info('Sending byebye notification for %s' % usn)
    
            resp = [
                'NOTIFY * HTTP/1.1',
                'HOST: %s:%d' % (SSDP_ADDR, SSDP_PORT),
                'NTS: ssdp:byebye',
            ]
            try:
                stcpy = dict(self.known[usn].items())
                stcpy['NT'] = stcpy['ST']
                del stcpy['ST']
                del stcpy['MANIFESTATION']
                del stcpy['SILENT']
                del stcpy['HOST']
                del stcpy['last-seen']
                resp.extend(map(lambda x: ': '.join(x), stcpy.items()))
                resp.extend(('', ''))
                logger.debug('do_byebye content', resp)
                if self.sock:
                    try:
                        self.sock.sendto('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT))
                    except (AttributeError, socket.error) as msg:
                        logger.error("failure sending out byebye notification: %r" % msg)
            except KeyError as msg:
                logger.error("error building byebye notification: %r" % msg)
    

    A class implementing a SSDP server. The notify_received and searchReceived methods are called when the appropriate type of datagram is received by the server.

    from http.server import BaseHTTPRequestHandler, HTTPServer
    import threading
    
    PORT_NUMBER = 8080
    
    
    class UPNPHTTPServerHandler(BaseHTTPRequestHandler):
    
        # Handler for the GET requests
        def do_GET(self):
    
            if self.path == '/boucherie_wsd.xml':
                self.send_response(200)
                self.send_header('Content-type', 'application/xml')
                self.end_headers()
                self.wfile.write(self.get_wsd_xml().encode())
                return
            if self.path == '/jambon-3000.xml':
                self.send_response(200)
                self.send_header('Content-type', 'application/xml')
                self.end_headers()
                self.wfile.write(self.get_device_xml().encode())
                return
            else:
                self.send_response(404)
                self.send_header('Content-type', 'text/html')
                self.end_headers()
                self.wfile.write(b"Not found.")
                return
    
        def get_device_xml(self):
            """
            Get the main device descriptor xml file.
            """
            xml = """<root>
        <specVersion>
            <major>1</major>
            <minor>0</minor>
        </specVersion>
        <device>
            <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
            <friendlyName>{friendly_name}</friendlyName>
            <manufacturer>{manufacturer}</manufacturer>
            <manufacturerURL>{manufacturer_url}</manufacturerURL>
            <modelDescription>{model_description}</modelDescription>
            <modelName>{model_name}</modelName>
            <modelNumber>{model_number}</modelNumber>
            <modelURL>{model_url}</modelURL>
            <serialNumber>{serial_number}</serialNumber>
            <UDN>uuid:{uuid}</UDN>
            <serviceList>
                <service>
                    <URLBase>http://xxx.yyy.zzz.aaaa:5000</URLBase>
                    <serviceType>urn:boucherie.example.com:service:Jambon:1</serviceType>
                    <serviceId>urn:boucherie.example.com:serviceId:Jambon</serviceId>
                    <controlURL>/jambon</controlURL>
                    <eventSubURL/>
                    <SCPDURL>/boucherie_wsd.xml</SCPDURL>
                </service>
            </serviceList>
            <presentationURL>{presentation_url}</presentationURL>
        </device>
    </root>"""
            return xml.format(friendly_name=self.server.friendly_name,
                              manufacturer=self.server.manufacturer,
                              manufacturer_url=self.server.manufacturer_url,
                              model_description=self.server.model_description,
                              model_name=self.server.model_name,
                              model_number=self.server.model_number,
                              model_url=self.server.model_url,
                              serial_number=self.server.serial_number,
                              uuid=self.server.uuid,
                              presentation_url=self.server.presentation_url)
    
        @staticmethod
        def get_wsd_xml():
            """
            Get the device WSD file.
            """
            return """<scpd xmlns="urn:schemas-upnp-org:service-1-0">
    <specVersion>
    <major>1</major>
    <minor>0</minor>
    </specVersion>
    </scpd>"""
    
    
    class UPNPHTTPServerBase(HTTPServer):
        """
        A simple HTTP server that knows the information about a UPnP device.
        """
        def __init__(self, server_address, request_handler_class):
            HTTPServer.__init__(self, server_address, request_handler_class)
            self.port = None
            self.friendly_name = None
            self.manufacturer = None
            self.manufacturer_url = None
            self.model_description = None
            self.model_name = None
            self.model_url = None
            self.serial_number = None
            self.uuid = None
            self.presentation_url = None
    
    
    class UPNPHTTPServer(threading.Thread):
        """
        A thread that runs UPNPHTTPServerBase.
        """
    
        def __init__(self, port, friendly_name, manufacturer, manufacturer_url, model_description, model_name,
                     model_number, model_url, serial_number, uuid, presentation_url):
            threading.Thread.__init__(self, daemon=True)
            self.server = UPNPHTTPServerBase(('', port), UPNPHTTPServerHandler)
            self.server.port = port
            self.server.friendly_name = friendly_name
            self.server.manufacturer = manufacturer
            self.server.manufacturer_url = manufacturer_url
            self.server.model_description = model_description
            self.server.model_name = model_name
            self.server.model_number = model_number
            self.server.model_url = model_url
            self.server.serial_number = serial_number
            self.server.uuid = uuid
            self.server.presentation_url = presentation_url
    
        def run(self):
            self.server.serve_forever()
    

    A HTTP handler that serves the UPnP XML files. Lets scan the Pi on network using android.

     private static final String tag = "SearchPi";
    
        private static final String query = "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: 1\r\n" +
                "ST: urn:schemas-upnp-org:device:MediaServer:1\r\n" +
                //"ST: ssdp:all\r\n" +
                "\r\n";
    
        private static final int port = 1900;
    

    Now lets create a new asyncTask because it is network releated process and add the below code in asyncTask

    String response = "";
            byte[] sendData;
            byte[] receiveData = new byte[1024];
            sendData = query.getBytes();
            DatagramPacket sendPacket = null;
    
            try {
                sendPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("239.255.255.250"), port);
            } catch (UnknownHostException e) {
                Log.d(tag, "Unknown Host Exception Thrown after creating DatagramPacket to send to server");
                e.printStackTrace();
            }
    
            DatagramSocket clientSocket = null;
            try {
                clientSocket = new DatagramSocket();
            } catch (SocketException e) {
                Log.d(tag, "Socket Exception thrown when creating socket to transport data");
                e.printStackTrace();
            }
    
            try {
                if (clientSocket != null) {
                    clientSocket.setSoTimeout(1000);
                    clientSocket.send(sendPacket);
                }
            } catch (IOException e) {
                Log.d(tag, "IOException thrown when sending data to socket");
                e.printStackTrace();
            }
    
            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
            try {
                if (clientSocket != null) {
                    clientSocket.receive(receivePacket);
                }
            } catch (IOException e) {
                Log.d(tag, "IOException thrown when receiving data");
                e.printStackTrace();
            }
    
            for (int i = 0; i < 10; i++) {
                response = new String(receivePacket.getData());
                Log.d(tag, "Response contains: " + response);
                if (response.contains("/jambon-3000.xml")) {
                    Log.d("logCat", receivePacket.getAddress().toString());
                    break;
                }
                else {
                    Log.d("logCat", "Popz! Pi not found");
                }
            }
    

    With above code you can also create media server on Pi.