Add support for v1 and v2 HomeKit fans. (#30503)

* Add support for v1 and v2 HomeKit fans.

* Lint fix
pull/30505/head
Jc2k 2020-01-05 14:56:46 +00:00 committed by Paulus Schoutsen
parent 8625e962ce
commit fffc5a5fbb
7 changed files with 1856 additions and 0 deletions

View File

@ -27,4 +27,6 @@ HOMEKIT_ACCESSORY_DISPATCH = {
"temperature": "sensor",
"battery": "sensor",
"smoke": "binary_sensor",
"fan": "fan",
"fanv2": "fan",
}

View File

@ -0,0 +1,254 @@
"""Support for Homekit fans."""
import logging
from homekit.model.characteristics import CharacteristicsTypes
from homeassistant.components.fan import (
DIRECTION_FORWARD,
DIRECTION_REVERSE,
SPEED_HIGH,
SPEED_LOW,
SPEED_MEDIUM,
SPEED_OFF,
SUPPORT_DIRECTION,
SUPPORT_OSCILLATE,
SUPPORT_SET_SPEED,
FanEntity,
)
from . import KNOWN_DEVICES, HomeKitEntity
_LOGGER = logging.getLogger(__name__)
# 0 is clockwise, 1 is counter-clockwise. The match to forward and reverse is so that
# its consistent with homeassistant.components.homekit.
DIRECTION_TO_HK = {
DIRECTION_REVERSE: 1,
DIRECTION_FORWARD: 0,
}
HK_DIRECTION_TO_HA = {v: k for (k, v) in DIRECTION_TO_HK.items()}
SPEED_TO_PCNT = {
SPEED_HIGH: 100,
SPEED_MEDIUM: 50,
SPEED_LOW: 25,
SPEED_OFF: 0,
}
class BaseHomeKitFan(HomeKitEntity, FanEntity):
"""Representation of a Homekit fan."""
# This must be set in subclasses to the name of a boolean characteristic
# that controls whether the fan is on or off.
on_characteristic = None
def __init__(self, *args):
"""Initialise the fan."""
self._on = None
self._features = 0
self._rotation_direction = 0
self._rotation_speed = 0
self._swing_mode = 0
super().__init__(*args)
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
return [
CharacteristicsTypes.SWING_MODE,
CharacteristicsTypes.ROTATION_DIRECTION,
CharacteristicsTypes.ROTATION_SPEED,
]
def _setup_rotation_direction(self, char):
self._features |= SUPPORT_DIRECTION
def _setup_rotation_speed(self, char):
self._features |= SUPPORT_SET_SPEED
def _setup_swing_mode(self, char):
self._features |= SUPPORT_OSCILLATE
def _update_rotation_direction(self, value):
self._rotation_direction = value
def _update_rotation_speed(self, value):
self._rotation_speed = value
def _update_swing_mode(self, value):
self._swing_mode = value
@property
def is_on(self):
"""Return true if device is on."""
return self._on
@property
def speed(self):
"""Return the current speed."""
if not self.is_on:
return SPEED_OFF
if self._rotation_speed > SPEED_TO_PCNT[SPEED_MEDIUM]:
return SPEED_HIGH
if self._rotation_speed > SPEED_TO_PCNT[SPEED_LOW]:
return SPEED_MEDIUM
if self._rotation_speed > SPEED_TO_PCNT[SPEED_OFF]:
return SPEED_LOW
return SPEED_OFF
@property
def speed_list(self):
"""Get the list of available speeds."""
if self.supported_features & SUPPORT_SET_SPEED:
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
return []
@property
def current_direction(self):
"""Return the current direction of the fan."""
return HK_DIRECTION_TO_HA[self._rotation_direction]
@property
def oscillating(self):
"""Return whether or not the fan is currently oscillating."""
return self._swing_mode == 1
@property
def supported_features(self):
"""Flag supported features."""
return self._features
async def async_set_direction(self, direction):
"""Set the direction of the fan."""
if self.supported_features & SUPPORT_DIRECTION:
await self._accessory.put_characteristics(
[
{
"aid": self._aid,
"iid": self._chars["rotation.direction"],
"value": DIRECTION_TO_HK[direction],
}
]
)
async def async_set_speed(self, speed):
"""Set the speed of the fan."""
if speed == SPEED_OFF:
return await self.async_turn_off()
if self.supported_features & SUPPORT_SET_SPEED:
await self._accessory.put_characteristics(
[
{
"aid": self._aid,
"iid": self._chars["rotation.speed"],
"value": SPEED_TO_PCNT[speed],
}
]
)
async def async_oscillate(self, oscillating: bool):
"""Oscillate the fan."""
if self.supported_features & SUPPORT_OSCILLATE:
await self._accessory.put_characteristics(
[
{
"aid": self._aid,
"iid": self._chars["swing-mode"],
"value": 1 if oscillating else 0,
}
]
)
async def async_turn_on(self, speed=None, **kwargs):
"""Turn the specified fan on."""
characteristics = []
if not self.is_on:
characteristics.append(
{
"aid": self._aid,
"iid": self._chars[self.on_characteristic],
"value": True,
}
)
if self.supported_features & SUPPORT_SET_SPEED and speed:
characteristics.append(
{
"aid": self._aid,
"iid": self._chars["rotation.speed"],
"value": SPEED_TO_PCNT[speed],
},
)
if not characteristics:
return
await self._accessory.put_characteristics(characteristics)
async def async_turn_off(self, **kwargs):
"""Turn the specified fan off."""
characteristics = [
{
"aid": self._aid,
"iid": self._chars[self.on_characteristic],
"value": False,
}
]
await self._accessory.put_characteristics(characteristics)
class HomeKitFanV1(BaseHomeKitFan):
"""Implement fan support for public.hap.service.fan."""
on_characteristic = "on"
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
return [CharacteristicsTypes.ON] + super().get_characteristic_types()
def _update_on(self, value):
self._on = value == 1
class HomeKitFanV2(BaseHomeKitFan):
"""Implement fan support for public.hap.service.fanv2."""
on_characteristic = "active"
def get_characteristic_types(self):
"""Define the homekit characteristics the entity cares about."""
return [CharacteristicsTypes.ACTIVE] + super().get_characteristic_types()
def _update_active(self, value):
self._on = value == 1
ENTITY_TYPES = {
"fan": HomeKitFanV1,
"fanv2": HomeKitFanV2,
}
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Legacy set up platform."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Homekit fans."""
hkid = config_entry.data["AccessoryPairingID"]
conn = hass.data[KNOWN_DEVICES][hkid]
def async_add_service(aid, service):
entity_class = ENTITY_TYPES.get(service["stype"])
if not entity_class:
return False
info = {"aid": aid, "iid": service["iid"]}
async_add_entities([entity_class(conn, info)], True)
return True
conn.add_listener(async_add_service)

View File

@ -0,0 +1,53 @@
"""Test against characteristics captured from the Home Assistant HomeKit bridge running demo platforms."""
from homeassistant.components.fan import (
SUPPORT_DIRECTION,
SUPPORT_OSCILLATE,
SUPPORT_SET_SPEED,
)
from tests.components.homekit_controller.common import (
Helper,
setup_accessories_from_file,
setup_test_accessories,
)
async def test_homeassistant_bridge_fan_setup(hass):
"""Test that a SIMPLEconnect fan can be correctly setup in HA."""
accessories = await setup_accessories_from_file(
hass, "home_assistant_bridge_fan.json"
)
config_entry, pairing = await setup_test_accessories(hass, accessories)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
# Check that the fan is correctly found and set up
fan_id = "fan.living_room_fan"
fan = entity_registry.async_get(fan_id)
assert fan.unique_id == "homekit-fan.living_room_fan-8"
fan_helper = Helper(
hass, "fan.living_room_fan", pairing, accessories[0], config_entry,
)
fan_state = await fan_helper.poll_and_get_state()
assert fan_state.attributes["friendly_name"] == "Living Room Fan"
assert fan_state.state == "off"
assert fan_state.attributes["supported_features"] == (
SUPPORT_DIRECTION | SUPPORT_SET_SPEED | SUPPORT_OSCILLATE
)
device_registry = await hass.helpers.device_registry.async_get_registry()
device = device_registry.async_get(fan.device_id)
assert device.manufacturer == "Home Assistant"
assert device.name == "Living Room Fan"
assert device.model == "Fan"
assert device.sw_version == "0.104.0.dev0"
bridge = device = device_registry.async_get(device.via_device_id)
assert bridge.manufacturer == "Home Assistant"
assert bridge.name == "Home Assistant Bridge"
assert bridge.model == "Bridge"
assert bridge.sw_version == "0.104.0.dev0"

View File

@ -0,0 +1,46 @@
"""
Test against characteristics captured from a SIMPLEconnect Fan.
https://github.com/home-assistant/home-assistant/issues/26180
"""
from homeassistant.components.fan import SUPPORT_DIRECTION, SUPPORT_SET_SPEED
from tests.components.homekit_controller.common import (
Helper,
setup_accessories_from_file,
setup_test_accessories,
)
async def test_simpleconnect_fan_setup(hass):
"""Test that a SIMPLEconnect fan can be correctly setup in HA."""
accessories = await setup_accessories_from_file(hass, "simpleconnect_fan.json")
config_entry, pairing = await setup_test_accessories(hass, accessories)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
# Check that the fan is correctly found and set up
fan_id = "fan.simpleconnect_fan_06f674"
fan = entity_registry.async_get(fan_id)
assert fan.unique_id == "homekit-1234567890abcd-8"
fan_helper = Helper(
hass, "fan.simpleconnect_fan_06f674", pairing, accessories[0], config_entry,
)
fan_state = await fan_helper.poll_and_get_state()
assert fan_state.attributes["friendly_name"] == "SIMPLEconnect Fan-06F674"
assert fan_state.state == "off"
assert fan_state.attributes["supported_features"] == (
SUPPORT_DIRECTION | SUPPORT_SET_SPEED
)
device_registry = await hass.helpers.device_registry.async_get_registry()
device = device_registry.async_get(fan.device_id)
assert device.manufacturer == "Hunter Fan"
assert device.name == "SIMPLEconnect Fan-06F674"
assert device.model == "SIMPLEconnect"
assert device.sw_version == ""
assert device.via_device_id is None

View File

@ -0,0 +1,407 @@
"""Basic checks for HomeKit motion sensors and contact sensors."""
from tests.components.homekit_controller.common import FakeService, setup_test_component
V1_ON = ("fan", "on")
V1_ROTATION_DIRECTION = ("fan", "rotation.direction")
V1_ROTATION_SPEED = ("fan", "rotation.speed")
V2_ACTIVE = ("fanv2", "active")
V2_ROTATION_DIRECTION = ("fanv2", "rotation.direction")
V2_ROTATION_SPEED = ("fanv2", "rotation.speed")
V2_SWING_MODE = ("fanv2", "swing-mode")
def create_fan_service():
"""
Define fan v1 characteristics as per HAP spec.
This service is no longer documented in R2 of the public HAP spec but existing
devices out there use it (like the SIMPLEconnect fan)
"""
service = FakeService("public.hap.service.fan")
cur_state = service.add_characteristic("on")
cur_state.value = 0
cur_state = service.add_characteristic("rotation.direction")
cur_state.value = 0
cur_state = service.add_characteristic("rotation.speed")
cur_state.value = 0
return service
def create_fanv2_service():
"""Define fan v2 characteristics as per HAP spec."""
service = FakeService("public.hap.service.fanv2")
cur_state = service.add_characteristic("active")
cur_state.value = 0
cur_state = service.add_characteristic("rotation.direction")
cur_state.value = 0
cur_state = service.add_characteristic("rotation.speed")
cur_state.value = 0
cur_state = service.add_characteristic("swing-mode")
cur_state.value = 0
return service
async def test_fan_read_state(hass, utcnow):
"""Test that we can read the state of a HomeKit fan accessory."""
sensor = create_fan_service()
helper = await setup_test_component(hass, [sensor])
helper.characteristics[V1_ON].value = False
state = await helper.poll_and_get_state()
assert state.state == "off"
helper.characteristics[V1_ON].value = True
state = await helper.poll_and_get_state()
assert state.state == "on"
async def test_turn_on(hass, utcnow):
"""Test that we can turn a fan on."""
fan = create_fan_service()
helper = await setup_test_component(hass, [fan])
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": "fan.testdevice", "speed": "high"},
blocking=True,
)
assert helper.characteristics[V1_ON].value == 1
assert helper.characteristics[V1_ROTATION_SPEED].value == 100
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": "fan.testdevice", "speed": "medium"},
blocking=True,
)
assert helper.characteristics[V1_ON].value == 1
assert helper.characteristics[V1_ROTATION_SPEED].value == 50
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": "fan.testdevice", "speed": "low"},
blocking=True,
)
assert helper.characteristics[V1_ON].value == 1
assert helper.characteristics[V1_ROTATION_SPEED].value == 25
async def test_turn_off(hass, utcnow):
"""Test that we can turn a fan off."""
fan = create_fan_service()
helper = await setup_test_component(hass, [fan])
helper.characteristics[V1_ON].value = 1
await hass.services.async_call(
"fan", "turn_off", {"entity_id": "fan.testdevice"}, blocking=True,
)
assert helper.characteristics[V1_ON].value == 0
async def test_set_speed(hass, utcnow):
"""Test that we set fan speed."""
fan = create_fan_service()
helper = await setup_test_component(hass, [fan])
helper.characteristics[V1_ON].value = 1
await hass.services.async_call(
"fan",
"set_speed",
{"entity_id": "fan.testdevice", "speed": "high"},
blocking=True,
)
assert helper.characteristics[V1_ROTATION_SPEED].value == 100
await hass.services.async_call(
"fan",
"set_speed",
{"entity_id": "fan.testdevice", "speed": "medium"},
blocking=True,
)
assert helper.characteristics[V1_ROTATION_SPEED].value == 50
await hass.services.async_call(
"fan",
"set_speed",
{"entity_id": "fan.testdevice", "speed": "low"},
blocking=True,
)
assert helper.characteristics[V1_ROTATION_SPEED].value == 25
await hass.services.async_call(
"fan",
"set_speed",
{"entity_id": "fan.testdevice", "speed": "off"},
blocking=True,
)
assert helper.characteristics[V1_ON].value == 0
async def test_speed_read(hass, utcnow):
"""Test that we can read a fans oscillation."""
fan = create_fan_service()
helper = await setup_test_component(hass, [fan])
helper.characteristics[V1_ON].value = 1
helper.characteristics[V1_ROTATION_SPEED].value = 100
state = await helper.poll_and_get_state()
assert state.attributes["speed"] == "high"
helper.characteristics[V1_ROTATION_SPEED].value = 50
state = await helper.poll_and_get_state()
assert state.attributes["speed"] == "medium"
helper.characteristics[V1_ROTATION_SPEED].value = 25
state = await helper.poll_and_get_state()
assert state.attributes["speed"] == "low"
helper.characteristics[V1_ON].value = 0
helper.characteristics[V1_ROTATION_SPEED].value = 0
state = await helper.poll_and_get_state()
assert state.attributes["speed"] == "off"
async def test_set_direction(hass, utcnow):
"""Test that we can set fan spin direction."""
fan = create_fan_service()
helper = await setup_test_component(hass, [fan])
await hass.services.async_call(
"fan",
"set_direction",
{"entity_id": "fan.testdevice", "direction": "reverse"},
blocking=True,
)
assert helper.characteristics[V1_ROTATION_DIRECTION].value == 1
await hass.services.async_call(
"fan",
"set_direction",
{"entity_id": "fan.testdevice", "direction": "forward"},
blocking=True,
)
assert helper.characteristics[V1_ROTATION_DIRECTION].value == 0
async def test_direction_read(hass, utcnow):
"""Test that we can read a fans oscillation."""
fan = create_fan_service()
helper = await setup_test_component(hass, [fan])
helper.characteristics[V1_ROTATION_DIRECTION].value = 0
state = await helper.poll_and_get_state()
assert state.attributes["direction"] == "forward"
helper.characteristics[V1_ROTATION_DIRECTION].value = 1
state = await helper.poll_and_get_state()
assert state.attributes["direction"] == "reverse"
async def test_fanv2_read_state(hass, utcnow):
"""Test that we can read the state of a HomeKit fan accessory."""
sensor = create_fanv2_service()
helper = await setup_test_component(hass, [sensor])
helper.characteristics[V2_ACTIVE].value = False
state = await helper.poll_and_get_state()
assert state.state == "off"
helper.characteristics[V2_ACTIVE].value = True
state = await helper.poll_and_get_state()
assert state.state == "on"
async def test_v2_turn_on(hass, utcnow):
"""Test that we can turn a fan on."""
fan = create_fanv2_service()
helper = await setup_test_component(hass, [fan])
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": "fan.testdevice", "speed": "high"},
blocking=True,
)
assert helper.characteristics[V2_ACTIVE].value == 1
assert helper.characteristics[V2_ROTATION_SPEED].value == 100
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": "fan.testdevice", "speed": "medium"},
blocking=True,
)
assert helper.characteristics[V2_ACTIVE].value == 1
assert helper.characteristics[V2_ROTATION_SPEED].value == 50
await hass.services.async_call(
"fan",
"turn_on",
{"entity_id": "fan.testdevice", "speed": "low"},
blocking=True,
)
assert helper.characteristics[V2_ACTIVE].value == 1
assert helper.characteristics[V2_ROTATION_SPEED].value == 25
async def test_v2_turn_off(hass, utcnow):
"""Test that we can turn a fan off."""
fan = create_fanv2_service()
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_ACTIVE].value = 1
await hass.services.async_call(
"fan", "turn_off", {"entity_id": "fan.testdevice"}, blocking=True,
)
assert helper.characteristics[V2_ACTIVE].value == 0
async def test_v2_set_speed(hass, utcnow):
"""Test that we set fan speed."""
fan = create_fanv2_service()
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_ACTIVE].value = 1
await hass.services.async_call(
"fan",
"set_speed",
{"entity_id": "fan.testdevice", "speed": "high"},
blocking=True,
)
assert helper.characteristics[V2_ROTATION_SPEED].value == 100
await hass.services.async_call(
"fan",
"set_speed",
{"entity_id": "fan.testdevice", "speed": "medium"},
blocking=True,
)
assert helper.characteristics[V2_ROTATION_SPEED].value == 50
await hass.services.async_call(
"fan",
"set_speed",
{"entity_id": "fan.testdevice", "speed": "low"},
blocking=True,
)
assert helper.characteristics[V2_ROTATION_SPEED].value == 25
await hass.services.async_call(
"fan",
"set_speed",
{"entity_id": "fan.testdevice", "speed": "off"},
blocking=True,
)
assert helper.characteristics[V2_ACTIVE].value == 0
async def test_v2_speed_read(hass, utcnow):
"""Test that we can read a fans oscillation."""
fan = create_fanv2_service()
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_ACTIVE].value = 1
helper.characteristics[V2_ROTATION_SPEED].value = 100
state = await helper.poll_and_get_state()
assert state.attributes["speed"] == "high"
helper.characteristics[V2_ROTATION_SPEED].value = 50
state = await helper.poll_and_get_state()
assert state.attributes["speed"] == "medium"
helper.characteristics[V2_ROTATION_SPEED].value = 25
state = await helper.poll_and_get_state()
assert state.attributes["speed"] == "low"
helper.characteristics[V2_ACTIVE].value = 0
helper.characteristics[V2_ROTATION_SPEED].value = 0
state = await helper.poll_and_get_state()
assert state.attributes["speed"] == "off"
async def test_v2_set_direction(hass, utcnow):
"""Test that we can set fan spin direction."""
fan = create_fanv2_service()
helper = await setup_test_component(hass, [fan])
await hass.services.async_call(
"fan",
"set_direction",
{"entity_id": "fan.testdevice", "direction": "reverse"},
blocking=True,
)
assert helper.characteristics[V2_ROTATION_DIRECTION].value == 1
await hass.services.async_call(
"fan",
"set_direction",
{"entity_id": "fan.testdevice", "direction": "forward"},
blocking=True,
)
assert helper.characteristics[V2_ROTATION_DIRECTION].value == 0
async def test_v2_direction_read(hass, utcnow):
"""Test that we can read a fans oscillation."""
fan = create_fanv2_service()
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_ROTATION_DIRECTION].value = 0
state = await helper.poll_and_get_state()
assert state.attributes["direction"] == "forward"
helper.characteristics[V2_ROTATION_DIRECTION].value = 1
state = await helper.poll_and_get_state()
assert state.attributes["direction"] == "reverse"
async def test_v2_oscillate(hass, utcnow):
"""Test that we can control a fans oscillation."""
fan = create_fanv2_service()
helper = await setup_test_component(hass, [fan])
await hass.services.async_call(
"fan",
"oscillate",
{"entity_id": "fan.testdevice", "oscillating": True},
blocking=True,
)
assert helper.characteristics[V2_SWING_MODE].value == 1
await hass.services.async_call(
"fan",
"oscillate",
{"entity_id": "fan.testdevice", "oscillating": False},
blocking=True,
)
assert helper.characteristics[V2_SWING_MODE].value == 0
async def test_v2_oscillate_read(hass, utcnow):
"""Test that we can read a fans oscillation."""
fan = create_fanv2_service()
helper = await setup_test_component(hass, [fan])
helper.characteristics[V2_SWING_MODE].value = 0
state = await helper.poll_and_get_state()
assert state.attributes["oscillating"] is False
helper.characteristics[V2_SWING_MODE].value = 1
state = await helper.poll_and_get_state()
assert state.attributes["oscillating"] is True

View File

@ -0,0 +1,325 @@
[
{
"aid": 1,
"services": [
{
"characteristics": [
{
"description": "Identify",
"format": "bool",
"iid": 2,
"perms": [
"pw"
],
"type": "00000014-0000-1000-8000-0026BB765291"
},
{
"description": "Manufacturer",
"format": "string",
"iid": 3,
"perms": [
"pr"
],
"type": "00000020-0000-1000-8000-0026BB765291",
"value": "Home Assistant"
},
{
"description": "Model",
"format": "string",
"iid": 4,
"perms": [
"pr"
],
"type": "00000021-0000-1000-8000-0026BB765291",
"value": "Bridge"
},
{
"description": "Name",
"format": "string",
"iid": 5,
"perms": [
"pr"
],
"type": "00000023-0000-1000-8000-0026BB765291",
"value": "Home Assistant Bridge"
},
{
"description": "SerialNumber",
"format": "string",
"iid": 6,
"perms": [
"pr"
],
"type": "00000030-0000-1000-8000-0026BB765291",
"value": "homekit.bridge"
},
{
"description": "FirmwareRevision",
"format": "string",
"iid": 7,
"perms": [
"pr"
],
"type": "00000052-0000-1000-8000-0026BB765291",
"value": "0.104.0.dev0"
}
],
"iid": 1,
"stype": "accessory-information",
"type": "0000003E-0000-1000-8000-0026BB765291"
}
]
},
{
"aid": 1256851357,
"services": [
{
"characteristics": [
{
"description": "Identify",
"format": "bool",
"iid": 2,
"perms": [
"pw"
],
"type": "00000014-0000-1000-8000-0026BB765291"
},
{
"description": "Manufacturer",
"format": "string",
"iid": 3,
"perms": [
"pr"
],
"type": "00000020-0000-1000-8000-0026BB765291",
"value": "Home Assistant"
},
{
"description": "Model",
"format": "string",
"iid": 4,
"perms": [
"pr"
],
"type": "00000021-0000-1000-8000-0026BB765291",
"value": "Fan"
},
{
"description": "Name",
"format": "string",
"iid": 5,
"perms": [
"pr"
],
"type": "00000023-0000-1000-8000-0026BB765291",
"value": "Living Room Fan"
},
{
"description": "SerialNumber",
"format": "string",
"iid": 6,
"perms": [
"pr"
],
"type": "00000030-0000-1000-8000-0026BB765291",
"value": "fan.living_room_fan"
},
{
"description": "FirmwareRevision",
"format": "string",
"iid": 7,
"perms": [
"pr"
],
"type": "00000052-0000-1000-8000-0026BB765291",
"value": "0.104.0.dev0"
}
],
"iid": 1,
"stype": "accessory-information",
"type": "0000003E-0000-1000-8000-0026BB765291"
},
{
"characteristics": [
{
"description": "Active",
"format": "uint8",
"iid": 9,
"perms": [
"pr",
"pw",
"ev"
],
"type": "000000B0-0000-1000-8000-0026BB765291",
"valid-values": [
0,
1
],
"value": 0
},
{
"description": "RotationDirection",
"format": "int",
"iid": 10,
"perms": [
"pr",
"pw",
"ev"
],
"type": "00000028-0000-1000-8000-0026BB765291",
"valid-values": [
0,
1
],
"value": 0
},
{
"description": "SwingMode",
"format": "uint8",
"iid": 11,
"perms": [
"pr",
"pw",
"ev"
],
"type": "000000B6-0000-1000-8000-0026BB765291",
"valid-values": [
0,
1
],
"value": 0
},
{
"description": "RotationSpeed",
"format": "float",
"iid": 12,
"maxValue": 100,
"minStep": 1,
"minValue": 0,
"perms": [
"pr",
"pw",
"ev"
],
"type": "00000029-0000-1000-8000-0026BB765291",
"unit": "percentage",
"value": 100
}
],
"iid": 8,
"stype": "fanv2",
"type": "000000B7-0000-1000-8000-0026BB765291"
}
]
},
{
"aid": 766313939,
"services": [
{
"characteristics": [
{
"description": "Identify",
"format": "bool",
"iid": 2,
"perms": [
"pw"
],
"type": "00000014-0000-1000-8000-0026BB765291"
},
{
"description": "Manufacturer",
"format": "string",
"iid": 3,
"perms": [
"pr"
],
"type": "00000020-0000-1000-8000-0026BB765291",
"value": "Home Assistant"
},
{
"description": "Model",
"format": "string",
"iid": 4,
"perms": [
"pr"
],
"type": "00000021-0000-1000-8000-0026BB765291",
"value": "Fan"
},
{
"description": "Name",
"format": "string",
"iid": 5,
"perms": [
"pr"
],
"type": "00000023-0000-1000-8000-0026BB765291",
"value": "Ceiling Fan"
},
{
"description": "SerialNumber",
"format": "string",
"iid": 6,
"perms": [
"pr"
],
"type": "00000030-0000-1000-8000-0026BB765291",
"value": "fan.ceiling_fan"
},
{
"description": "FirmwareRevision",
"format": "string",
"iid": 7,
"perms": [
"pr"
],
"type": "00000052-0000-1000-8000-0026BB765291",
"value": "0.104.0.dev0"
}
],
"iid": 1,
"stype": "accessory-information",
"type": "0000003E-0000-1000-8000-0026BB765291"
},
{
"characteristics": [
{
"description": "Active",
"format": "uint8",
"iid": 9,
"perms": [
"pr",
"pw",
"ev"
],
"type": "000000B0-0000-1000-8000-0026BB765291",
"valid-values": [
0,
1
],
"value": 0
},
{
"description": "RotationSpeed",
"format": "float",
"iid": 10,
"maxValue": 100,
"minStep": 1,
"minValue": 0,
"perms": [
"pr",
"pw",
"ev"
],
"type": "00000029-0000-1000-8000-0026BB765291",
"unit": "percentage",
"value": 100
}
],
"iid": 8,
"stype": "fanv2",
"type": "000000B7-0000-1000-8000-0026BB765291"
}
]
}
]

View File

@ -0,0 +1,769 @@
[
{
"aid": 1,
"services": [
{
"characteristics": [
{
"format": "string",
"iid": 2,
"perms": [
"pr"
],
"type": "00000023-0000-1000-8000-0026BB765291",
"value": "SIMPLEconnect Fan-06F674"
},
{
"format": "string",
"iid": 3,
"perms": [
"pr"
],
"type": "00000020-0000-1000-8000-0026BB765291",
"value": "Hunter Fan"
},
{
"format": "string",
"iid": 4,
"perms": [
"pr"
],
"type": "00000021-0000-1000-8000-0026BB765291",
"value": "SIMPLEconnect"
},
{
"format": "string",
"iid": 5,
"perms": [
"pr"
],
"type": "00000030-0000-1000-8000-0026BB765291",
"value": "1234567890abcd"
},
{
"format": "bool",
"iid": 6,
"perms": [
"pw"
],
"type": "00000014-0000-1000-8000-0026BB765291"
},
{
"format": "string",
"iid": 7,
"perms": [
"pr"
],
"type": "54",
"value": "0.22"
}
],
"iid": 1,
"stype": "accessory-information",
"type": "0000003E-0000-1000-8000-0026BB765291"
},
{
"characteristics": [
{
"ev": false,
"format": "bool",
"iid": 9,
"perms": [
"pr",
"pw",
"ev"
],
"type": "00000025-0000-1000-8000-0026BB765291",
"value": false
},
{
"format": "string",
"iid": 10,
"perms": [
"pr"
],
"type": "00000023-0000-1000-8000-0026BB765291",
"value": "Hunter Fan"
},
{
"ev": false,
"format": "float",
"iid": 11,
"maxValue": 100.0,
"minStep": 25.0,
"minValue": 0.0,
"perms": [
"pr",
"pw",
"ev"
],
"type": "00000029-0000-1000-8000-0026BB765291",
"value": 0.0
},
{
"ev": false,
"format": "int",
"iid": 12,
"maxValue": 1,
"minValue": 0,
"perms": [
"pr",
"pw",
"ev"
],
"type": "00000028-0000-1000-8000-0026BB765291",
"value": 0
},
{
"description": "Set Fan Fast On",
"ev": false,
"format": "bool",
"iid": 13,
"perms": [
"pr",
"pw",
"ev"
],
"type": "2BD83CC0-6C60-11E5-A837-0800200C9A66",
"value": false
},
{
"description": "Set Fan Fast Off",
"ev": false,
"format": "bool",
"iid": 14,
"perms": [
"pr",
"pw",
"ev"
],
"type": "2BD83CC1-6C60-11E5-A837-0800200C9A66",
"value": false
},
{
"description": "Is BLDC in Scope",
"ev": false,
"format": "bool",
"iid": 15,
"perms": [
"pr",
"ev"
],
"type": "2BD83CC5-6C60-11E5-A837-0800200C9A66",
"value": false
},
{
"ev": false,
"format": "bool",
"iid": 16,
"perms": [
"pr",
"pw",
"ev"
],
"type": "00000005-0000-1000-8000-0026BB765291",
"value": false
},
{
"ev": false,
"format": "int",
"iid": 17,
"perms": [
"pr",
"ev"
],
"type": "2BD83CC4-6C60-11E5-A837-0800200C9A66",
"value": 341
},
{
"ev": false,
"format": "int",
"iid": 18,
"perms": [
"pr",
"ev"
],
"type": "2BD83CC3-6C60-11E5-A837-0800200C9A66",
"value": 0
}
],
"iid": 8,
"stype": "fan",
"type": "00000040-0000-1000-8000-0026BB765291"
},
{
"characteristics": [
{
"description": "FW Upgrade supported types",
"format": "string",
"iid": 20,
"perms": [
"pr"
],
"type": "151909D2-3802-11E4-916C-0800200C9A66",
"value": "url,data"
},
{
"description": "FW Upgrade URL",
"format": "string",
"iid": 21,
"maxLen": 256,
"perms": [
"pw"
],
"type": "151909D1-3802-11E4-916C-0800200C9A66"
},
{
"description": "FW Upgrade Status",
"ev": false,
"format": "int",
"iid": 22,
"perms": [
"pr",
"ev"
],
"type": "151909D6-3802-11E4-916C-0800200C9A66",
"value": 0
},
{
"description": "FW Upgrade Data",
"format": "data",
"iid": 23,
"perms": [
"pw"
],
"type": "151909D7-3802-11E4-916C-0800200C9A66"
}
],
"iid": 19,
"stype": "Unknown Service: 151909D0-3802-11E4-916C-0800200C9A66",
"type": "151909D0-3802-11E4-916C-0800200C9A66"
},
{
"characteristics": [
{
"description": "FW Upgrade supported types",
"format": "string",
"iid": 25,
"perms": [
"pr"
],
"type": "151909D2-3802-11E4-916C-0800200C9A66",
"value": "url,data"
},
{
"description": "FW Upgrade URL",
"format": "string",
"iid": 26,
"maxLen": 256,
"perms": [
"pw"
],
"type": "151909D1-3802-11E4-916C-0800200C9A66"
},
{
"description": "FW Upgrade Status",
"ev": false,
"format": "int",
"iid": 27,
"perms": [
"pr",
"ev"
],
"type": "151909D6-3802-11E4-916C-0800200C9A66",
"value": 0
},
{
"description": "FW Upgrade Data",
"format": "data",
"iid": 28,
"perms": [
"pw"
],
"type": "151909D7-3802-11E4-916C-0800200C9A66"
}
],
"iid": 24,
"stype": "Unknown Service: 151909D8-3802-11E4-916C-0800200C9A66",
"type": "151909D8-3802-11E4-916C-0800200C9A66"
},
{
"characteristics": [
{
"ev": false,
"format": "bool",
"iid": 30,
"perms": [
"pr",
"pw",
"ev"
],
"type": "00000025-0000-1000-8000-0026BB765291",
"value": true
},
{
"format": "string",
"iid": 31,
"perms": [
"pr"
],
"type": "00000023-0000-1000-8000-0026BB765291",
"value": "Hunter Light"
},
{
"ev": false,
"format": "int",
"iid": 32,
"maxValue": 100,
"minStep": 10,
"minValue": 0,
"perms": [
"pr",
"pw",
"ev"
],
"type": "00000008-0000-1000-8000-0026BB765291",
"value": 30
},
{
"description": "Set Light Dimming",
"ev": false,
"format": "bool",
"iid": 33,
"perms": [
"pr",
"pw",
"ev"
],
"type": "151909DC-3802-11E4-916C-0800200C9A66",
"value": true
},
{
"description": "Set Light Security",
"ev": false,
"format": "bool",
"iid": 34,
"perms": [
"pr",
"pw",
"ev"
],
"type": "2BD815B0-6C60-11E5-A837-0800200C9A66",
"value": false
},
{
"description": "Get Light Power",
"ev": false,
"format": "bool",
"iid": 35,
"perms": [
"pr",
"ev"
],
"type": "2BD815B5-6C60-11E5-A837-0800200C9A66",
"value": true
}
],
"iid": 29,
"stype": "lightbulb",
"type": "00000043-0000-1000-8000-0026BB765291"
},
{
"characteristics": [
{
"ev": false,
"format": "int",
"iid": 37,
"perms": [
"pr",
"ev"
],
"type": "2BD83CC6-6C60-11E5-A837-0800200C9A66",
"value": -65
},
{
"ev": false,
"format": "bool",
"iid": 38,
"perms": [
"pr",
"pw",
"ev"
],
"type": "2BD83CC7-6C60-11E5-A837-0800200C9A66",
"value": false
},
{
"ev": false,
"format": "string",
"iid": 39,
"maxLen": 256,
"perms": [
"pr",
"pw",
"ev"
],
"type": "0049CFF1-4B37-11E5-B970-0800200C9A66",
"value": "url, data"
},
{
"ev": false,
"format": "string",
"iid": 40,
"maxLen": 256,
"perms": [
"pr",
"pw",
"ev"
],
"type": "2BD83CC2-6C60-11E5-A837-0800200C9A66",
"value": "url, data"
},
{
"ev": false,
"format": "int",
"iid": 41,
"maxValue": 110,
"minValue": 0,
"perms": [
"pr",
"ev"
],
"type": "0049CFF2-4B37-11E5-B970-0800200C9A66",
"value": 0
},
{
"ev": false,
"format": "bool",
"iid": 42,
"perms": [
"pr",
"ev"
],
"type": "0049CFF3-4B37-11E5-B970-0800200C9A66",
"value": false
}
],
"iid": 36,
"stype": "Unknown Service: 0049CFF0-4B37-11E5-B970-0800200C9A66",
"type": "0049CFF0-4B37-11E5-B970-0800200C9A66"
},
{
"characteristics": [
{
"ev": false,
"format": "string",
"iid": 44,
"perms": [
"pr",
"pw",
"ev"
],
"type": "2BD815B2-6C60-11E5-A837-0800200C9A66",
"value": "NULL"
},
{
"ev": false,
"format": "int",
"iid": 45,
"maxValue": 63,
"minValue": 0,
"perms": [
"pr",
"pw",
"ev"
],
"type": "2BD815B4-6C60-11E5-A837-0800200C9A66",
"value": 7
},
{
"ev": false,
"format": "bool",
"iid": 46,
"perms": [
"pr",
"pw",
"ev"
],
"type": "2BD815B3-6C60-11E5-A837-0800200C9A66",
"value": true
}
],
"iid": 43,
"stype": "Unknown Service: 2BD815B1-6C60-11E5-A837-0800200C9A66",
"type": "2BD815B1-6C60-11E5-A837-0800200C9A66"
},
{
"characteristics": [
{
"ev": false,
"format": "int",
"iid": 48,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC61-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 49,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC62-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 50,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC63-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 51,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC64-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 52,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC65-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 53,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC66-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 54,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC67-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 55,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC68-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 56,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC69-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 57,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC6A-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 58,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC6B-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 59,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC6C-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 60,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC6D-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 61,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC6E-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 62,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC6F-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 63,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC70-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 64,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC71-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 65,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC72-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 66,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC73-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 67,
"perms": [
"pr",
"pw",
"ev"
],
"type": "E836DC74-6C6E-11E5-A837-0800200C9A66",
"value": 4294967295
},
{
"ev": false,
"format": "int",
"iid": 68,
"perms": [
"pr",
"pw",
"ev"
],
"type": "CC9EA121-FC1C-11E5-A837-0800200C9A66",
"value": 4294901760
},
{
"ev": false,
"format": "int",
"iid": 69,
"perms": [
"pr",
"pw",
"ev"
],
"type": "CC9EA120-FC1C-11E5-A837-0800200C9A66",
"value": 4294901760
}
],
"iid": 47,
"stype": "Unknown Service: E836DC60-6C6E-11E5-A837-0800200C9A66",
"type": "E836DC60-6C6E-11E5-A837-0800200C9A66"
}
]
}
]