Repository: ikalchev/HAP-python

Issue:
module 'asyncio' has no attribute 'SafeChildWatcher'
Upon running any HAP-pyhon service the following exception is caught:
```
[accessory_driver] Loading Accessory state from `busy_home.state`
[accessory_driver] Starting the event loop
[accessory_driver] Closed the event loop
Traceback (most recent call last):
  File "busy_home.py", line 104, in <module>
    driver.start()
  File "C:\Users\LuetzowP\Desktop\HAP-python-2.6.0\pyhap\accessory_driver.py", line 223, in start
    watcher = asyncio.SafeChildWatcher()
AttributeError: module 'asyncio' has no attribute 'SafeChildWatcher'
```
This error was reproduced on a clean install of Windows 10 (1903), with Python 3.8 and HAP-python v2.6.0
As far as I have tested, the error does not occur on Unix.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/ikalchev/HAP-python/pull/343?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#343](https://codecov.io/gh/ikalchev/HAP-python/pull/343?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (e9d0ad6) into [dev](https://codecov.io/gh/ikalchev/HAP-python/commit/1a71c82175e0731c26e2392e2382011cb875d7cb?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (1a71c82) will **not change** coverage.
> The diff coverage is `0.00%`.


```diff
@@           Coverage Diff           @@
##              dev     #343   +/-   ##
=======================================
  Coverage   90.60%   90.60%           
=======================================
  Files          19       19           
  Lines        1894     1894           
  Branches      220      220           
=======================================
  Hits         1716     1716           
  Misses        134      134           
  Partials       44       44           
```

| [Impacted Files](https://codecov.io/gh/ikalchev/HAP-python/pull/343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [pyhap/accessory\_driver.py](https://codecov.io/gh/ikalchev/HAP-python/pull/343/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cHloYXAvYWNjZXNzb3J5X2RyaXZlci5weQ==) | `89.44% <0.00%> (ø)` | |

bdraco: I fixed the pylint issue in https://github.com/ikalchev/HAP-python/pull/342
jslay88: @bdraco @ikalchev Merged your commits in, but not sure how I am to add additional code coverage for a windows only line.

Patch:
diff --git a/pyhap/accessory_driver.py b/pyhap/accessory_driver.py
index 5db6ac2..96c1426 100644
--- a/pyhap/accessory_driver.py
+++ b/pyhap/accessory_driver.py
@@ -19,7 +19,6 @@ import asyncio
 import base64
 from concurrent.futures import ThreadPoolExecutor
 import hashlib
-import json
 import logging
 import os
 import re
@@ -53,7 +52,7 @@ from pyhap.params import get_srp_context
 from pyhap.state import State
 
 from .const import HAP_SERVER_STATUS
-from .util import callback
+from .util import callback, to_hap_json
 
 logger = logging.getLogger(__name__)
 
@@ -239,7 +238,8 @@ class AccessoryDriver:
         """
         try:
             logger.info("Starting the event loop")
-            if threading.current_thread() is threading.main_thread():
+            if threading.current_thread() is threading.main_thread() \
+                    and os.name != "nt":
                 logger.debug("Setting child watcher")
                 watcher = asyncio.SafeChildWatcher()
                 watcher.attach_loop(self.loop)
@@ -460,7 +460,7 @@ class AccessoryDriver:
             return
 
         data = {HAP_REPR_CHARS: [data]}
-        bytedata = json.dumps(data).encode()
+        bytedata = to_hap_json(data)
 
         if threading.current_thread() == self.tid:
             self.async_send_event(topic, bytedata, sender_client_addr)
diff --git a/pyhap/characteristic.py b/pyhap/characteristic.py
index 065e4b6..8693923 100644
--- a/pyhap/characteristic.py
+++ b/pyhap/characteristic.py
@@ -6,62 +6,75 @@ a temperature measuring or a device status.
 """
 import logging
 
-from uuid import UUID
-
 from pyhap.const import (
-    HAP_PERMISSION_READ, HAP_REPR_DESC, HAP_REPR_FORMAT, HAP_REPR_IID,
-    HAP_REPR_MAX_LEN, HAP_REPR_PERM, HAP_REPR_TYPE, HAP_REPR_VALUE,
-    HAP_REPR_VALID_VALUES)
+    HAP_PERMISSION_READ,
+    HAP_REPR_DESC,
+    HAP_REPR_FORMAT,
+    HAP_REPR_IID,
+    HAP_REPR_MAX_LEN,
+    HAP_REPR_PERM,
+    HAP_REPR_TYPE,
+    HAP_REPR_VALID_VALUES,
+    HAP_REPR_VALUE,
+)
+
+from .util import hap_type_to_uuid, uuid_to_hap_type
 
 logger = logging.getLogger(__name__)
 
 # ### HAP Format ###
-HAP_FORMAT_BOOL = 'bool'
-HAP_FORMAT_INT = 'int'
-HAP_FORMAT_FLOAT = 'float'
-HAP_FORMAT_STRING = 'string'
-HAP_FORMAT_ARRAY = 'array'
-HAP_FORMAT_DICTIONARY = 'dictionary'
-HAP_FORMAT_UINT8 = 'uint8'
-HAP_FORMAT_UINT16 = 'uint16'
-HAP_FORMAT_UINT32 = 'uint32'
-HAP_FORMAT_UINT64 = 'uint64'
-HAP_FORMAT_DATA = 'data'
-HAP_FORMAT_TLV8 = 'tlv8'
+HAP_FORMAT_BOOL = "bool"
+HAP_FORMAT_INT = "int"
+HAP_FORMAT_FLOAT = "float"
+HAP_FORMAT_STRING = "string"
+HAP_FORMAT_ARRAY = "array"
+HAP_FORMAT_DICTIONARY = "dictionary"
+HAP_FORMAT_UINT8 = "uint8"
+HAP_FORMAT_UINT16 = "uint16"
+HAP_FORMAT_UINT32 = "uint32"
+HAP_FORMAT_UINT64 = "uint64"
+HAP_FORMAT_DATA = "data"
+HAP_FORMAT_TLV8 = "tlv8"
 
 HAP_FORMAT_DEFAULTS = {
     HAP_FORMAT_BOOL: False,
     HAP_FORMAT_INT: 0,
-    HAP_FORMAT_FLOAT: 0.,
-    HAP_FORMAT_STRING: '',
-    HAP_FORMAT_ARRAY: '',
-    HAP_FORMAT_DICTIONARY: '',
+    HAP_FORMAT_FLOAT: 0.0,
+    HAP_FORMAT_STRING: "",
+    HAP_FORMAT_ARRAY: "",
+    HAP_FORMAT_DICTIONARY: "",
     HAP_FORMAT_UINT8: 0,
     HAP_FORMAT_UINT16: 0,
     HAP_FORMAT_UINT32: 0,
     HAP_FORMAT_UINT64: 0,
-    HAP_FORMAT_DATA: '',
-    HAP_FORMAT_TLV8: '',
+    HAP_FORMAT_DATA: "",
+    HAP_FORMAT_TLV8: "",
 }
 
-HAP_FORMAT_NUMERICS = (HAP_FORMAT_INT, HAP_FORMAT_FLOAT, HAP_FORMAT_UINT8,
-                       HAP_FORMAT_UINT16, HAP_FORMAT_UINT32, HAP_FORMAT_UINT64)
+HAP_FORMAT_NUMERICS = (
+    HAP_FORMAT_INT,
+    HAP_FORMAT_FLOAT,
+    HAP_FORMAT_UINT8,
+    HAP_FORMAT_UINT16,
+    HAP_FORMAT_UINT32,
+    HAP_FORMAT_UINT64,
+)
 
 # ### HAP Units ###
-HAP_UNIT_ARC_DEGREE = 'arcdegrees'
-HAP_UNIT_CELSIUS = 'celsius'
-HAP_UNIT_LUX = 'lux'
-HAP_UNIT_PERCENTAGE = 'percentage'
-HAP_UNIT_SECONDS = 'seconds'
+HAP_UNIT_ARC_DEGREE = "arcdegrees"
+HAP_UNIT_CELSIUS = "celsius"
+HAP_UNIT_LUX = "lux"
+HAP_UNIT_PERCENTAGE = "percentage"
+HAP_UNIT_SECONDS = "seconds"
 
 # ### Properties ###
-PROP_FORMAT = 'Format'
-PROP_MAX_VALUE = 'maxValue'
-PROP_MIN_STEP = 'minStep'
-PROP_MIN_VALUE = 'minValue'
-PROP_PERMISSIONS = 'Permissions'
-PROP_UNIT = 'unit'
-PROP_VALID_VALUES = 'ValidValues'
+PROP_FORMAT = "Format"
+PROP_MAX_VALUE = "maxValue"
+PROP_MIN_STEP = "minStep"
+PROP_MIN_VALUE = "minValue"
+PROP_PERMISSIONS = "Permissions"
+PROP_UNIT = "unit"
+PROP_VALID_VALUES = "ValidValues"
 
 PROP_NUMERIC = (PROP_MAX_VALUE, PROP_MIN_VALUE, PROP_MIN_STEP, PROP_UNIT)
 
@@ -79,8 +92,18 @@ class Characteristic:
     like format, min and max values, valid values and others.
     """
 
-    __slots__ = ('broker', 'display_name', 'properties', 'type_id',
-                 'value', 'getter_callback', 'setter_callback', 'service', '_uuid_str')
+    __slots__ = (
+        "broker",
+        "display_name",
+        "properties",
+        "type_id",
+        "value",
+        "getter_callback",
+        "setter_callback",
+        "service",
+        "_uuid_str",
+        "_loader_display_name",
+    )
 
     def __init__(self, display_name, type_id, properties):
         """Initialise with the given properties.
@@ -104,12 +127,14 @@ class Characteristic:
         self.getter_callback = None
         self.setter_callback = None
         self.service = None
-        self._uuid_str = str(type_id).upper()
+        self._uuid_str = uuid_to_hap_type(type_id)
+        self._loader_display_name = None
 
     def __repr__(self):
         """Return the representation of the characteristic."""
-        return '<characteristic display_name={} value={} properties={}>' \
-            .format(self.display_name, self.value, self.properties)
+        return "<characteristic display_name={} value={} properties={}>".format(
+            self.display_name, self.value, self.properties
+        )
 
     def _get_default_value(self):
         """Return default value for format."""
@@ -133,8 +158,9 @@ class Characteristic:
         """Perform validation and conversion to valid value."""
         if self.properties.get(PROP_VALID_VALUES):
             if value not in self.properties[PROP_VALID_VALUES].values():
-                error_msg = '{}: value={} is an invalid value.' \
-                            .format(self.display_name, value)
+                error_msg = "{}: value={} is an invalid value.".format(
+                    self.display_name, value
+                )
                 logger.error(error_msg)
                 raise ValueError(error_msg)
         elif self.properties[PROP_FORMAT] == HAP_FORMAT_STRING:
@@ -143,8 +169,9 @@ class Characteristic:
             value = bool(value)
         elif self.properties[PROP_FORMAT] in HAP_FORMAT_NUMERICS:
             if not isinstance(value, (int, float)):
-                error_msg = '{}: value={} is not a numeric value.' \
-                            .format(self.display_name, value)
+                error_msg = "{}: value={} is not a numeric value.".format(
+                    self.display_name, value
+                )
                 logger.error(error_msg)
                 raise ValueError(error_msg)
             value = min(self.properties.get(PROP_MAX_VALUE, value), value)
@@ -163,8 +190,7 @@ class Characteristic:
         :type valid_values: dict
         """
         if not properties and not valid_values:
-            raise ValueError(
-                'No properties or valid_values specified to override.')
+            raise ValueError("No properties or valid_values specified to override.")
 
         if properties:
             self.properties.update(properties)
@@ -196,7 +222,7 @@ class Characteristic:
             subscribed clients. Notify will be performed if the broker is set.
         :type should_notify: bool
         """
-        logger.debug('set_value: %s to %s', self.display_name, value)
+        logger.debug("set_value: %s to %s", self.display_name, value)
         value = self.to_valid_value(value)
         self.value = value
         if should_notify and self.broker:
@@ -207,8 +233,12 @@ class Characteristic:
 
         Change self.value to value and call callback.
         """
-        logger.debug('client_update_value: %s to %s from client: %s',
-                     self.display_name, value, sender_client_addr)
+        logger.debug(
+            "client_update_value: %s to %s from client: %s",
+            self.display_name,
+            value,
+            sender_client_addr,
+        )
         self.value = value
         self.notify(sender_client_addr)
         if self.setter_callback:
@@ -235,19 +265,28 @@ class Characteristic:
         hap_rep = {
             HAP_REPR_IID: self.broker.iid_manager.get_iid(self),
             HAP_REPR_TYPE: self._uuid_str,
-            HAP_REPR_DESC: self.display_name,
             HAP_REPR_PERM: self.properties[PROP_PERMISSIONS],
             HAP_REPR_FORMAT: self.properties[PROP_FORMAT],
         }
+        # HAP_REPR_DESC (description) is optional and takes up
+        # quite a bit of space in the payload. Only include it
+        # if it has been changed from the default loader version
+        if (
+            not self._loader_display_name
+            or self._loader_display_name != self.display_name
+        ):
+            hap_rep[HAP_REPR_DESC] = self.display_name
 
         value = self.get_value()
         if self.properties[PROP_FORMAT] in HAP_FORMAT_NUMERICS:
-            hap_rep.update({k: self.properties[k] for k in
-                            self.properties.keys() & PROP_NUMERIC})
+            hap_rep.update(
+                {k: self.properties[k] for k in self.properties.keys() & PROP_NUMERIC}
+            )
 
             if PROP_VALID_VALUES in self.properties:
-                hap_rep[HAP_REPR_VALID_VALUES] = \
-                    sorted(self.properties[PROP_VALID_VALUES].values())
+                hap_rep[HAP_REPR_VALID_VALUES] = sorted(
+                    self.properties[PROP_VALID_VALUES].values()
+                )
         elif self.properties[PROP_FORMAT] == HAP_FORMAT_STRING:
             if len(value) > 64:
                 hap_rep[HAP_REPR_MAX_LEN] = min(len(value), 256)
@@ -257,12 +296,17 @@ class Characteristic:
         return hap_rep
 
     @classmethod
-    def from_dict(cls, name, json_dict):
+    def from_dict(cls, name, json_dict, from_loader=False):
         """Initialize a characteristic object from a dict.
 
         :param json_dict: Dictionary containing at least the keys `Format`,
             `Permissions` and `UUID`
         :type json_dict: dict
         """
-        type_id = UUID(json_dict.pop('UUID'))
-        return cls(name, type_id, properties=json_dict)
+        type_id = hap_type_to_uuid(json_dict.pop("UUID"))
+        char = cls(name, type_id, properties=json_dict)
+        if from_loader:
+            char._loader_display_name = (  # pylint: disable=protected-access
+                char.display_name
+            )
+        return char
diff --git a/pyhap/const.py b/pyhap/const.py
index 2264e75..e8bdfee 100644
--- a/pyhap/const.py
+++ b/pyhap/const.py
@@ -6,6 +6,8 @@ __short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
 __version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
 REQUIRED_PYTHON_VER = (3, 5)
 
+BASE_UUID = "-0000-1000-8000-0026BB765291"
+
 # ### Misc ###
 STANDALONE_AID = 1  # Standalone accessory ID (i.e. not bridged)
 
diff --git a/pyhap/hap_handler.py b/pyhap/hap_handler.py
index 305378d..ffdc4fd 100644
--- a/pyhap/hap_handler.py
+++ b/pyhap/hap_handler.py
@@ -24,6 +24,7 @@ import pyhap.tlv as tlv
 from pyhap.util import long_to_bytes
 
 from .hap_crypto import hap_hkdf, pad_tls_nonce
+from .util import to_hap_json
 
 SNAPSHOT_TIMEOUT = 10
 
@@ -240,7 +241,7 @@ class HAPServerHandler:
         """Send a generic HAP status response."""
         self.send_response(http_code)
         self.send_header("Content-Type", self.JSON_RESPONSE_TYPE)
-        self.end_response(json.dumps({"status": hap_server_status}).encode("utf-8"))
+        self.end_response(to_hap_json({"status": hap_server_status}))
 
     def handle_pairing(self):
         """Handles arbitrary step of the pairing process."""
@@ -553,10 +554,9 @@ class HAPServerHandler:
             raise UnprivilegedRequestException
 
         hap_rep = self.accessory_handler.get_accessories()
-        data = json.dumps(hap_rep).encode("utf-8")
         self.send_response(HTTPStatus.OK)
         self.send_header("Content-Type", self.JSON_RESPONSE_TYPE)
-        self.end_response(data)
+        self.end_response(to_hap_json(hap_rep))
 
     def handle_get_characteristics(self):
         """Handles a client request to get certain characteristics."""
@@ -580,9 +580,8 @@ class HAPServerHandler:
             for result in chars:
                 del result[HAP_REPR_STATUS]
 
-        data = json.dumps(response).encode("utf-8")
         self.send_header("Content-Type", self.JSON_RESPONSE_TYPE)
-        self.end_response(data)
+        self.end_response(to_hap_json(response))
 
     def handle_set_characteristics(self):
         """Handles a client request to update certain characteristics."""
@@ -607,7 +606,7 @@ class HAPServerHandler:
 
         self.send_response(HTTPStatus.MULTI_STATUS)
         self.send_header("Content-Type", self.JSON_RESPONSE_TYPE)
-        self.end_response(json.dumps(response).encode("utf-8"))
+        self.end_response(to_hap_json(response))
 
     def handle_pairings(self):
         """Handles a client request to update or remove a pairing."""
diff --git a/pyhap/loader.py b/pyhap/loader.py
index 620b76a..c566786 100644
--- a/pyhap/loader.py
+++ b/pyhap/loader.py
@@ -45,7 +45,7 @@ class Loader:
             or "UUID" not in char_dict
         ):
             raise KeyError("Could not load char {}!".format(name))
-        return Characteristic.from_dict(name, char_dict)
+        return Characteristic.from_dict(name, char_dict, from_loader=True)
 
     def get_service(self, name):
         """Return new service object."""
diff --git a/pyhap/service.py b/pyhap/service.py
index f2c2d1f..098ed8c 100644
--- a/pyhap/service.py
+++ b/pyhap/service.py
@@ -1,9 +1,14 @@
 """This module implements the HAP Service."""
-from uuid import UUID
 
 from pyhap.const import (
-    HAP_REPR_CHARS, HAP_REPR_IID,
-    HAP_REPR_LINKED, HAP_REPR_PRIMARY, HAP_REPR_TYPE)
+    HAP_REPR_CHARS,
+    HAP_REPR_IID,
+    HAP_REPR_LINKED,
+    HAP_REPR_PRIMARY,
+    HAP_REPR_TYPE,
+)
+
+from .util import hap_type_to_uuid, uuid_to_hap_type
 
 
 class Service:
@@ -13,8 +18,16 @@ class Service:
     TemperatureSensor service has the characteristic CurrentTemperature.
     """
 
-    __slots__ = ('broker', 'characteristics', 'display_name', 'type_id',
-                 'linked_services', 'is_primary_service', 'setter_callback')
+    __slots__ = (
+        "broker",
+        "characteristics",
+        "display_name",
+        "type_id",
+        "linked_services",
+        "is_primary_service",
+        "setter_callback",
+        "_uuid_str",
+    )
 
     def __init__(self, type_id, display_name=None):
         """Initialize a new Service object."""
@@ -25,25 +38,30 @@ class Service:
         self.type_id = type_id
         self.is_primary_service = None
         self.setter_callback = None
+        self._uuid_str = uuid_to_hap_type(type_id)
 
     def __repr__(self):
         """Return the representation of the service."""
-        return '<service display_name={} chars={}>' \
-            .format(self.display_name,
-                    {c.display_name: c.value for c in self.characteristics})
+        return "<service display_name={} chars={}>".format(
+            self.display_name, {c.display_name: c.value for c in self.characteristics}
+        )
 
     def add_linked_service(self, service):
         """Add the given service as "linked" to this Service."""
-        if not any(self.broker.iid_manager.get_iid(service) ==
-                   self.broker.iid_manager.get_iid(original_service)
-                   for original_service in self.linked_services):
+        if not any(
+            self.broker.iid_manager.get_iid(service)
+            == self.broker.iid_manager.get_iid(original_service)
+            for original_service in self.linked_services
+        ):
             self.linked_services.append(service)
 
     def add_characteristic(self, *chars):
         """Add the given characteristics as "mandatory" for this Service."""
         for char in chars:
-            if not any(char.type_id == original_char.type_id
-                       for original_char in self.characteristics):
+            if not any(
+                char.type_id == original_char.type_id
+                for original_char in self.characteristics
+            ):
                 char.service = self
                 self.characteristics.append(char)
 
@@ -61,10 +79,17 @@ class Service:
         for char in self.characteristics:
             if char.display_name == name:
                 return char
-        raise ValueError('Characteristic not found')
-
-    def configure_char(self, char_name, properties=None, valid_values=None,
-                       value=None, setter_callback=None, getter_callback=None):
+        raise ValueError("Characteristic not found")
+
+    def configure_char(
+        self,
+        char_name,
+        properties=None,
+        valid_values=None,
+        value=None,
+        setter_callback=None,
+        getter_callback=None,
+    ):
         """Helper method to return fully configured characteristic."""
         char = self.get_characteristic(char_name)
         if properties or valid_values:
@@ -86,7 +111,7 @@ class Service:
         """
         hap = {
             HAP_REPR_IID: self.broker.iid_manager.get_iid(self),
-            HAP_REPR_TYPE: str(self.type_id).upper(),
+            HAP_REPR_TYPE: self._uuid_str,
             HAP_REPR_CHARS: [c.to_HAP() for c in self.characteristics],
         }
 
@@ -97,7 +122,8 @@ class Service:
             hap[HAP_REPR_LINKED] = []
             for linked_service in self.linked_services:
                 hap[HAP_REPR_LINKED].append(
-                    linked_service.broker.iid_manager.get_iid(linked_service))
+                    linked_service.broker.iid_manager.get_iid(linked_service)
+                )
 
         return hap
 
@@ -109,8 +135,8 @@ class Service:
             `RequiredCharacteristics`
         :type json_dict: dict
         """
-        type_id = UUID(json_dict.pop('UUID'))
+        type_id = hap_type_to_uuid(json_dict.pop("UUID"))
         service = cls(type_id, name)
-        for char_name in json_dict['RequiredCharacteristics']:
+        for char_name in json_dict["RequiredCharacteristics"]:
             service.add_characteristic(loader.get_char(char_name))
         return service
diff --git a/pyhap/util.py b/pyhap/util.py
index 4c5d2e2..1696a03 100644
--- a/pyhap/util.py
+++ b/pyhap/util.py
@@ -1,8 +1,12 @@
 import asyncio
 import base64
+import functools
+import json
 import random
 import socket
-import functools
+from uuid import UUID
+
+from .const import BASE_UUID
 
 ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 HEX_DIGITS = "0123456789ABCDEF"
@@ -134,3 +138,23 @@ async def event_wait(event, timeout):
     except asyncio.TimeoutError:
         pass
     return event.is_set()
+
+
+def uuid_to_hap_type(uuid):
+    """Convert a UUID to a HAP type."""
+    long_type = str(uuid).upper()
+    if not long_type.endswith(BASE_UUID):
+        return long_type
+    return long_type.split("-", 1)[0].lstrip("0")
+
+
+def hap_type_to_uuid(hap_type):
+    """Convert a HAP type to a UUID."""
+    if "-" in hap_type:
+        return UUID(hap_type)
+    return UUID("0" * (8 - len(hap_type)) + hap_type + BASE_UUID)
+
+
+def to_hap_json(dump_obj):
+    """Convert an object to HAP json."""
+    return json.dumps(dump_obj, separators=(",", ":")).encode("utf-8")


Test patch:
diff --git a/tests/test_accessory.py b/tests/test_accessory.py
index eeb9b0a..23a032d 100644
--- a/tests/test_accessory.py
+++ b/tests/test_accessory.py
@@ -226,57 +226,51 @@ def test_to_hap(mock_driver):
             "aid": 1,
             "services": [
                 {
-                    "iid": 1,
-                    "type": "0000003E-0000-1000-8000-0026BB765291",
                     "characteristics": [
                         {
+                            "format": "bool",
                             "iid": 2,
-                            "type": "00000014-0000-1000-8000-0026BB765291",
-                            "description": "Identify",
                             "perms": ["pw"],
-                            "format": "bool",
+                            "type": "14",
                         },
                         {
+                            "format": "string",
                             "iid": 3,
-                            "type": "00000020-0000-1000-8000-0026BB765291",
-                            "description": "Manufacturer",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "20",
                             "value": "",
                         },
                         {
+                            "format": "string",
                             "iid": 4,
-                            "type": "00000021-0000-1000-8000-0026BB765291",
-                            "description": "Model",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "21",
                             "value": "",
                         },
                         {
+                            "format": "string",
                             "iid": 5,
-                            "type": "00000023-0000-1000-8000-0026BB765291",
-                            "description": "Name",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "23",
                             "value": "Test Bridge",
                         },
                         {
+                            "format": "string",
                             "iid": 6,
-                            "type": "00000030-0000-1000-8000-0026BB765291",
-                            "description": "SerialNumber",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "30",
                             "value": "default",
                         },
                         {
+                            "format": "string",
                             "iid": 7,
-                            "type": "00000052-0000-1000-8000-0026BB765291",
-                            "description": "FirmwareRevision",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "52",
                             "value": "",
                         },
                     ],
+                    "iid": 1,
+                    "type": "3E",
                 }
             ],
         },
@@ -284,57 +278,51 @@ def test_to_hap(mock_driver):
             "aid": 2,
             "services": [
                 {
-                    "iid": 1,
-                    "type": "0000003E-0000-1000-8000-0026BB765291",
                     "characteristics": [
                         {
+                            "format": "bool",
                             "iid": 2,
-                            "type": "00000014-0000-1000-8000-0026BB765291",
-                            "description": "Identify",
                             "perms": ["pw"],
-                            "format": "bool",
+                            "type": "14",
                         },
                         {
+                            "format": "string",
                             "iid": 3,
-                            "type": "00000020-0000-1000-8000-0026BB765291",
-                            "description": "Manufacturer",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "20",
                             "value": "",
                         },
                         {
+                            "format": "string",
                             "iid": 4,
-                            "type": "00000021-0000-1000-8000-0026BB765291",
-                            "description": "Model",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "21",
                             "value": "",
                         },
                         {
+                            "format": "string",
                             "iid": 5,
-                            "type": "00000023-0000-1000-8000-0026BB765291",
-                            "description": "Name",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "23",
                             "value": "Test Accessory",
                         },
                         {
+                            "format": "string",
                             "iid": 6,
-                            "type": "00000030-0000-1000-8000-0026BB765291",
-                            "description": "SerialNumber",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "30",
                             "value": "default",
                         },
                         {
+                            "format": "string",
                             "iid": 7,
-                            "type": "00000052-0000-1000-8000-0026BB765291",
-                            "description": "FirmwareRevision",
                             "perms": ["pr"],
-                            "format": "string",
+                            "type": "52",
                             "value": "",
                         },
                     ],
+                    "iid": 1,
+                    "type": "3E",
                 }
             ],
         },
@@ -343,57 +331,105 @@ def test_to_hap(mock_driver):
         "aid": 2,
         "services": [
             {
-                "iid": 1,
-                "type": "0000003E-0000-1000-8000-0026BB765291",
                 "characteristics": [
                     {
+                        "format": "bool",
                         "iid": 2,
-                        "type": "00000014-0000-1000-8000-0026BB765291",
-                        "description": "Identify",
                         "perms": ["pw"],
-                        "format": "bool",
+                        "type": "14",
                     },
                     {
+                        "format": "string",
                         "iid": 3,
-                        "type": "00000020-0000-1000-8000-0026BB765291",
-                        "description": "Manufacturer",
                         "perms": ["pr"],
-                        "format": "string",
+                        "type": "20",
                         "value": "",
                     },
                     {
+                        "format": "string",
                         "iid": 4,
-                        "type": "00000021-0000-1000-8000-0026BB765291",
-                        "description": "Model",
                         "perms": ["pr"],
-                        "format": "string",
+                        "type": "21",
                         "value": "",
                     },
                     {
+                        "format": "string",
                         "iid": 5,
-                        "type": "00000023-0000-1000-8000-0026BB765291",
-                        "description": "Name",
                         "perms": ["pr"],
-                        "format": "string",
+                        "type": "23",
                         "value": "Test Accessory",
                     },
                     {
+                        "format": "string",
                         "iid": 6,
-                        "type": "00000030-0000-1000-8000-0026BB765291",
-                        "description": "SerialNumber",
                         "perms": ["pr"],
-                        "format": "string",
+                        "type": "30",
                         "value": "default",
                     },
                     {
+                        "format": "string",
                         "iid": 7,
-                        "type": "00000052-0000-1000-8000-0026BB765291",
-                        "description": "FirmwareRevision",
                         "perms": ["pr"],
+                        "type": "52",
+                        "value": "",
+                    },
+                ],
+                "iid": 1,
+                "type": "3E",
+            }
+        ],
+    }
+    bridge.get_characteristic(2, 2).display_name = "Custom Name Identify"
+    assert acc.to_HAP() == {
+        "aid": 2,
+        "services": [
+            {
+                "characteristics": [
+                    {
+                        "description": "Custom Name Identify",
+                        "format": "bool",
+                        "iid": 2,
+                        "perms": ["pw"],
+                        "type": "14",
+                    },
+                    {
+                        "format": "string",
+                        "iid": 3,
+                        "perms": ["pr"],
+                        "type": "20",
+                        "value": "",
+                    },
+                    {
                         "format": "string",
+                        "iid": 4,
+                        "perms": ["pr"],
+                        "type": "21",
+                        "value": "",
+                    },
+                    {
+                        "format": "string",
+                        "iid": 5,
+                        "perms": ["pr"],
+                        "type": "23",
+                        "value": "Test Accessory",
+                    },
+                    {
+                        "format": "string",
+                        "iid": 6,
+                        "perms": ["pr"],
+                        "type": "30",
+                        "value": "default",
+                    },
+                    {
+                        "format": "string",
+                        "iid": 7,
+                        "perms": ["pr"],
+                        "type": "52",
                         "value": "",
                     },
                 ],
+                "iid": 1,
+                "type": "3E",
             }
         ],
     }
diff --git a/tests/test_hap_handler.py b/tests/test_hap_handler.py
index ce0e324..4d80bb4 100644
--- a/tests/test_hap_handler.py
+++ b/tests/test_hap_handler.py
@@ -413,7 +413,7 @@ def test_handle_get_characteristics_encrypted(driver):
     decoded_response = json.loads(response.body.decode())
     assert "characteristics" in decoded_response
     assert "status" not in decoded_response["characteristics"][0]
-    assert b'"value": 0' in response.body
+    assert b'"value":0' in response.body
 
     with patch.object(acc.iid_manager, "get_obj", side_effect=CharacteristicError):
         response = hap_handler.HAPResponse()
diff --git a/tests/test_service.py b/tests/test_service.py
index eae0b75..e17fc0c 100644
--- a/tests/test_service.py
+++ b/tests/test_service.py
@@ -166,13 +166,20 @@ def test_linked_service_to_HAP():
         mock_char_HAP.side_effect = ("Char 1", "Char 2")
         hap_repr = service.to_HAP()
         mock_iid.assert_called_with(service)
-
-    assert hap_repr == {
-        "iid": 2,
-        "type": str(uuid).upper(),
-        "characteristics": ["Char 1", "Char 2"],
-        "linked": [mock_linked_iid()],
-    }
+        assert hap_repr == {
+            "iid": 2,
+            "type": str(uuid).upper(),
+            "characteristics": ["Char 1", "Char 2"],
+            "linked": [mock_linked_iid()],
+        }
+        # Verify we can readd it without dupes
+        service.add_linked_service(linked_service)
+        assert hap_repr == {
+            "iid": 2,
+            "type": str(uuid).upper(),
+            "characteristics": ["Char 1", "Char 2"],
+            "linked": [mock_linked_iid()],
+        }
 
 
 def test_is_primary_service_to_HAP():
diff --git a/tests/test_util.py b/tests/test_util.py
index 984b4ab..2b78757 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -1,5 +1,6 @@
 """Test for pyhap.util."""
 import functools
+from uuid import UUID
 
 from pyhap import util
 
@@ -29,3 +30,19 @@ def test_iscoro():
     assert util.iscoro(functools.partial(async_function)) is True
     assert util.iscoro(async_is_callback) is False
     assert util.iscoro(async_not_callback) is False
+
+
+def test_generate_setup_id():
+    """Test generate_setup_id."""
+    assert len(util.generate_setup_id()) == 4
+
+
+def test_hap_type_to_uuid():
+    """Test we can convert short types to UUIDs."""
+    assert util.hap_type_to_uuid("32") == UUID("00000032-0000-1000-8000-0026bb765291")
+    assert util.hap_type_to_uuid("00000032") == UUID(
+        "00000032-0000-1000-8000-0026bb765291"
+    )
+    assert util.hap_type_to_uuid("00000032-0000-1000-8000-0026bb765291") == UUID(
+        "00000032-0000-1000-8000-0026bb765291"
+    )