1 """Support UPNP discovery method that mimics Hue hubs."""
3 from __future__
import annotations
6 from contextlib
import suppress
9 from typing
import cast
11 from aiohttp
import web
13 from homeassistant
import core
16 from .config
import Config
17 from .const
import HUE_SERIAL_NUMBER, HUE_UUID
19 _LOGGER = logging.getLogger(__name__)
22 BROADCAST_ADDR =
"239.255.255.250"
26 """Handles requests for the description.xml file."""
28 url =
"/description.xml"
29 name =
"description:xml"
33 """Initialize the instance of the view."""
37 def get(self, request: web.Request) -> web.Response:
38 """Handle a GET request."""
39 resp_text = f
"""<?xml version="1.0" encoding="UTF-8" ?>
40 <root xmlns="urn:schemas-upnp-org:device-1-0">
45 <URLBase>http://{self.config.advertise_ip}:{self.config.advertise_port}/</URLBase>
47 <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
48 <friendlyName>Home Assistant Bridge ({self.config.advertise_ip})</friendlyName>
49 <manufacturer>Royal Philips Electronics</manufacturer>
50 <manufacturerURL>http://www.philips.com</manufacturerURL>
51 <modelDescription>Philips hue Personal Wireless Lighting</modelDescription>
52 <modelName>Philips hue bridge 2015</modelName>
53 <modelNumber>BSB002</modelNumber>
54 <modelURL>http://www.meethue.com</modelURL>
55 <serialNumber>{HUE_SERIAL_NUMBER}</serialNumber>
56 <UDN>uuid:{HUE_UUID}</UDN>
61 return web.Response(text=resp_text, content_type=
"text/xml")
65 """Handle responding to UPNP/SSDP discovery requests."""
69 loop: asyncio.AbstractEventLoop,
70 ssdp_socket: socket.socket,
74 """Initialize the class."""
75 self.
transporttransport: asyncio.DatagramTransport |
None =
None
81 "upnp:rootdevice", f
"uuid:{HUE_UUID}::upnp:rootdevice"
84 "urn:schemas-upnp-org:device:basic:1", f
"uuid:{HUE_UUID}"
88 """Set the transport."""
89 self.
transporttransport = cast(asyncio.DatagramTransport, transport)
92 """Handle connection lost."""
95 """Respond to msearch packets."""
96 decoded_data = data.decode(
"utf-8", errors=
"ignore")
98 if "M-SEARCH" not in decoded_data:
101 _LOGGER.debug(
"UPNP Responder M-SEARCH method received: %s", data)
104 _LOGGER.debug(
"UPNP Responder responding with: %s", response)
105 assert self.
transporttransport
is not None
106 self.
transporttransport.sendto(response, addr)
109 """Log UPNP errors."""
110 _LOGGER.error(
"UPNP Error received: %s", exc)
113 """Stop the server."""
114 _LOGGER.info(
"UPNP responder shutting down")
117 self.
_loop_loop.remove_writer(self.
_sock_sock.fileno())
118 self.
_loop_loop.remove_reader(self.
_sock_sock.fileno())
122 if "upnp:rootdevice" in decoded_data:
130 response = f
"""HTTP/1.1 200 OK
131 CACHE-CONTROL: max-age=60
133 LOCATION: http://{self.advertise_ip}:{self.advertise_port}/description.xml
134 SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.16.0
135 hue-bridgeid: {HUE_SERIAL_NUMBER}
137 USN: {unique_service_name}
140 return response.replace(
"\n",
"\r\n").encode(
"utf-8")
145 upnp_bind_multicast: bool,
148 ) -> UPNPResponderProtocol:
149 """Create the UPNP socket and protocol."""
151 ssdp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
152 ssdp_socket.setblocking(
False)
156 ssdp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
157 with suppress(AttributeError):
158 ssdp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
160 ssdp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
162 ssdp_socket.setsockopt(
163 socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(host_ip_addr)
166 ssdp_socket.setsockopt(
168 socket.IP_ADD_MEMBERSHIP,
169 socket.inet_aton(BROADCAST_ADDR) + socket.inet_aton(host_ip_addr),
172 ssdp_socket.bind((
"" if upnp_bind_multicast
else host_ip_addr, BROADCAST_PORT))
174 loop = asyncio.get_event_loop()
176 transport_protocol = await loop.create_datagram_endpoint(
180 return transport_protocol[1]
web.Response get(self, web.Request request)
None __init__(self, Config config)
bytes _prepare_response(self, str search_target, str unique_service_name)
None datagram_received(self, bytes data, tuple[str, int] addr)
None __init__(self, asyncio.AbstractEventLoop loop, socket.socket ssdp_socket, str advertise_ip, int advertise_port)
bytes _handle_request(self, str decoded_data)
None connection_made(self, asyncio.BaseTransport transport)
None connection_lost(self, Exception|None exc)
None error_received(self, Exception exc)
UPNPResponderProtocol async_create_upnp_datagram_endpoint(str host_ip_addr, bool upnp_bind_multicast, str advertise_ip, int advertise_port)