Поиск raspberry pi в сети с помощью Android

Я работаю над приложением raspberry pi и android, в котором каждый раз, когда приложение открывается, оно ищет raspberry pi в сети, и если raspberry pi найдено, выполните дальнейшую операцию, иначе дайте согласие пользователю. Мне просто нужен IP-адрес raspberry pi для дальнейшего процесса.

Целевое решение -

  1. Статический IP-адрес raspberry pi — неприменимо, поскольку приложение распространяется из игрового магазина и не имеет доступа к маршрутизатору.

  2. Поиск raspberry pi в сети - работаю над этим.

Я пробовал использовать протокол SSDP, DLNA, UPNP для создания сервера на raspberry pi, и каждый раз, когда приложение заходит в онлайн-поиск raspberry pi в сети.

Используемые ресурсы

  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

Вот что я сделал

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;

    }

Используя вышеуказанный метод и решение, я смог узнать IP-адрес маршрутизатора каждый раз, но не pi. Также просмотрел каждую библиотеку, которую я нашел в Интернете, но не работал. Помимо этого метода, если есть какой-либо другой предложенный способ, он будет оценен.


person Kaushik Burkule    schedule 16.09.2019    source источник
comment
и какой код вы запускали на Pi?   -  person Jussi Kukkonen    schedule 16.09.2019
comment
Это просто Java-сервер, который обеспечивает домашнее облако и подачу камер безопасности.   -  person Kaushik Burkule    schedule 17.09.2019
comment
И вы проверили, что он реализует службу SSDP?   -  person Jussi Kukkonen    schedule 17.09.2019
comment
Из других приложений я вижу свой пи, но не из приложения.   -  person Kaushik Burkule    schedule 17.09.2019
comment
Код, который вы показываете, похоже, пытается получить один пакет, это правильно?   -  person Jussi Kukkonen    schedule 17.09.2019
comment
Спасибо за внимание, но я нашел решение и скоро обновлю свой ответ.   -  person Kaushik Burkule    schedule 17.09.2019


Ответы (1)


Теперь, в конце концов, я нашел решение, и я поделюсь своим ответом шаг за шагом. Я использую протокол ssdp, чтобы узнать Pi в сети, где у Pi есть динамический IP-адрес. Итак, я создал сервер на питоне и использовал андроид в качестве клиента. давайте сначала начнем с сервера -

Также вы можете найти серверный скрипт 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()

Это создаст точку выполнения для серверного скрипта.

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)

Класс, реализующий сервер SSDP. Методы notify_received и searchReceived вызываются, когда сервер получает дейтаграмму соответствующего типа.

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()

Обработчик HTTP, обслуживающий XML-файлы UPnP. Давайте просканируем Pi по сети с помощью 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;

Теперь давайте создадим новый asyncTask, потому что это процесс, связанный с сетью, и добавим приведенный ниже код в 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");
            }
        }

С помощью приведенного выше кода вы также можете создать медиасервер на Pi.

person Kaushik Burkule    schedule 18.09.2019