hat.drivers.upnp.description
WIP UPnP description
1"""WIP UPnP description""" 2 3import collections 4import io 5import typing 6import xml.sax.handler 7 8import aiohttp 9 10 11class Icon(typing.NamedTuple): 12 mimetype: str 13 width: int 14 height: int 15 depth: int 16 url: str 17 18 19class DeviceDescription(typing.NamedTuple): 20 url: str | None 21 dev_type: str 22 dev_name: str 23 manufacturer: str 24 manufacturer_url: str | None 25 model_desc: str | None 26 model_name: str 27 model_number: str | None 28 model_url: str | None 29 serial_number: str | None 30 unique_dev_name: str 31 icons: list[Icon] 32 33 34async def get_description(location: str) -> DeviceDescription: 35 async with aiohttp.ClientSession() as session: 36 async with session.get(location) as res: 37 text = await res.text() 38 data = _decode_xml(text) 39 version = data['root']['specVersion'] 40 if version['major'] != '1': 41 raise Exception(f"unsupported version {version}") 42 device = data['root']['device'] 43 device_icons = device.get('iconList', {}).get('icon', []) 44 if not isinstance(device_icons, list): 45 device_icons = [device_icons] 46 icons = [] 47 for i in device_icons: 48 icons.append(Icon(mimetype=i['mimetype'], 49 width=i['width'], 50 height=i['height'], 51 depth=i['depth'], 52 url=i['url'])) 53 return DeviceDescription( 54 url=data['root'].get('URLBase'), 55 dev_type=device['deviceType'], 56 dev_name=device['friendlyName'], 57 manufacturer=device['deviceType'], 58 manufacturer_url=device.get('manufacturerURL'), 59 model_desc=device.get('modelDescription'), 60 model_name=device['modelName'], 61 model_number=device.get('modelNumber'), 62 model_url=device.get('modelURL'), 63 serial_number=device.get('serialNumber'), 64 unique_dev_name=device['UDN'], 65 icons=icons) 66 67 68def _decode_xml(text): 69 handler = _XmlContentHandler() 70 parser = xml.sax.make_parser() 71 parser.setContentHandler(handler) 72 parser.setFeature(xml.sax.handler.feature_external_ges, False) 73 parser.setFeature(xml.sax.handler.feature_external_pes, False) 74 parser.parse(io.StringIO(text)) 75 return handler.root 76 77 78class _XmlContentHandler(xml.sax.ContentHandler): 79 80 def __init__(self): 81 self._root = {} 82 self._stack = collections.deque([self._root]) 83 84 @property 85 def root(self): 86 return self._root 87 88 def startElement(self, name, attrs): 89 self._stack.append({}) 90 91 def endElement(self, name): 92 el = self._stack.pop() 93 text = el.get('', '') 94 children = {k: v for k, v in el.items() if k} 95 top = self._stack[-1] 96 if name in top: 97 if not isinstance(top[name], list): 98 top[name] = [top[name]] 99 top[name].append(children or text) 100 else: 101 top[name] = children or text 102 103 def characters(self, content): 104 el = self._stack[-1] 105 el[''] = el.get('', '') + content
class
Icon(typing.NamedTuple):
12class Icon(typing.NamedTuple): 13 mimetype: str 14 width: int 15 height: int 16 depth: int 17 url: str
Icon(mimetype, width, height, depth, url)
class
DeviceDescription(typing.NamedTuple):
20class DeviceDescription(typing.NamedTuple): 21 url: str | None 22 dev_type: str 23 dev_name: str 24 manufacturer: str 25 manufacturer_url: str | None 26 model_desc: str | None 27 model_name: str 28 model_number: str | None 29 model_url: str | None 30 serial_number: str | None 31 unique_dev_name: str 32 icons: list[Icon]
DeviceDescription(url, dev_type, dev_name, manufacturer, manufacturer_url, model_desc, model_name, model_number, model_url, serial_number, unique_dev_name, icons)
DeviceDescription( url: str | None, dev_type: str, dev_name: str, manufacturer: str, manufacturer_url: str | None, model_desc: str | None, model_name: str, model_number: str | None, model_url: str | None, serial_number: str | None, unique_dev_name: str, icons: list[Icon])
Create new instance of DeviceDescription(url, dev_type, dev_name, manufacturer, manufacturer_url, model_desc, model_name, model_number, model_url, serial_number, unique_dev_name, icons)
35async def get_description(location: str) -> DeviceDescription: 36 async with aiohttp.ClientSession() as session: 37 async with session.get(location) as res: 38 text = await res.text() 39 data = _decode_xml(text) 40 version = data['root']['specVersion'] 41 if version['major'] != '1': 42 raise Exception(f"unsupported version {version}") 43 device = data['root']['device'] 44 device_icons = device.get('iconList', {}).get('icon', []) 45 if not isinstance(device_icons, list): 46 device_icons = [device_icons] 47 icons = [] 48 for i in device_icons: 49 icons.append(Icon(mimetype=i['mimetype'], 50 width=i['width'], 51 height=i['height'], 52 depth=i['depth'], 53 url=i['url'])) 54 return DeviceDescription( 55 url=data['root'].get('URLBase'), 56 dev_type=device['deviceType'], 57 dev_name=device['friendlyName'], 58 manufacturer=device['deviceType'], 59 manufacturer_url=device.get('manufacturerURL'), 60 model_desc=device.get('modelDescription'), 61 model_name=device['modelName'], 62 model_number=device.get('modelNumber'), 63 model_url=device.get('modelURL'), 64 serial_number=device.get('serialNumber'), 65 unique_dev_name=device['UDN'], 66 icons=icons)