seems to work
This commit is contained in:
5
apps/homekit/accessories/__init__.py
Normal file
5
apps/homekit/accessories/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
HomeKit Accessories Package
|
||||
|
||||
This package contains HomeKit accessory implementations for different device types.
|
||||
"""
|
||||
48
apps/homekit/accessories/contact.py
Normal file
48
apps/homekit/accessories/contact.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Contact Sensor Accessory Implementation for HomeKit
|
||||
|
||||
Implements contact sensor (window/door sensors):
|
||||
- ContactSensorState (read-only): 0=Detected, 1=Not Detected
|
||||
"""
|
||||
|
||||
from pyhap.accessory import Accessory
|
||||
from pyhap.const import CATEGORY_SENSOR
|
||||
|
||||
|
||||
class ContactAccessory(Accessory):
|
||||
"""Contact sensor for doors and windows."""
|
||||
|
||||
category = CATEGORY_SENSOR
|
||||
|
||||
def __init__(self, driver, device, api_client, display_name=None, *args, **kwargs):
|
||||
"""
|
||||
Initialize the contact sensor accessory.
|
||||
|
||||
Args:
|
||||
driver: HAP driver instance
|
||||
device: Device object from DeviceRegistry
|
||||
api_client: ApiClient for sending commands
|
||||
display_name: Optional display name (defaults to device.friendly_name)
|
||||
"""
|
||||
name = display_name or device.friendly_name or device.name
|
||||
super().__init__(driver, name, *args, **kwargs)
|
||||
self.device = device
|
||||
self.api_client = api_client
|
||||
|
||||
# Add ContactSensor service
|
||||
self.contact_service = self.add_preload_service('ContactSensor')
|
||||
|
||||
# Get ContactSensorState characteristic
|
||||
self.contact_state_char = self.contact_service.get_characteristic('ContactSensorState')
|
||||
|
||||
# Initialize with "not detected" (closed)
|
||||
self.contact_state_char.set_value(1)
|
||||
|
||||
def update_state(self, state_payload):
|
||||
"""Update state from API event."""
|
||||
if "contact" in state_payload:
|
||||
# API sends: "open" or "closed"
|
||||
# HomeKit: 0=Contact Detected (closed), 1=Contact Not Detected (open)
|
||||
is_open = state_payload["contact"] == "open"
|
||||
homekit_state = 1 if is_open else 0
|
||||
self.contact_state_char.set_value(homekit_state)
|
||||
177
apps/homekit/accessories/light.py
Normal file
177
apps/homekit/accessories/light.py
Normal file
@@ -0,0 +1,177 @@
|
||||
"""
|
||||
Light Accessory Implementations for HomeKit
|
||||
|
||||
Implements different light types:
|
||||
- OnOffLightAccessory: Simple on/off light
|
||||
- DimmableLightAccessory: Light with brightness control
|
||||
- ColorLightAccessory: RGB light with full color control
|
||||
"""
|
||||
|
||||
from pyhap.accessory import Accessory
|
||||
from pyhap.const import CATEGORY_LIGHTBULB
|
||||
|
||||
|
||||
class OnOffLightAccessory(Accessory):
|
||||
"""Simple On/Off Light without dimming or color."""
|
||||
|
||||
category = CATEGORY_LIGHTBULB
|
||||
|
||||
def __init__(self, driver, device, api_client, display_name=None, *args, **kwargs):
|
||||
"""
|
||||
Initialize the light accessory.
|
||||
|
||||
Args:
|
||||
driver: HAP driver instance
|
||||
device: Device object from DeviceRegistry
|
||||
api_client: ApiClient for sending commands
|
||||
display_name: Optional display name (defaults to device.friendly_name)
|
||||
"""
|
||||
name = display_name or device.friendly_name or device.name
|
||||
super().__init__(driver, name, *args, **kwargs)
|
||||
self.device = device
|
||||
self.api_client = api_client
|
||||
|
||||
# Add Lightbulb service with On characteristic
|
||||
self.lightbulb_service = self.add_preload_service('Lightbulb')
|
||||
|
||||
# Get the On characteristic and set callback
|
||||
self.on_char = self.lightbulb_service.get_characteristic('On')
|
||||
self.on_char.setter_callback = self.set_on
|
||||
|
||||
def set_on(self, value):
|
||||
"""Called when HomeKit wants to turn light on/off."""
|
||||
power_state = "on" if value else "off"
|
||||
payload = {
|
||||
"type": "light",
|
||||
"payload": {"power": power_state}
|
||||
}
|
||||
self.api_client.post_device_set(self.device.device_id, payload["type"], payload["payload"])
|
||||
|
||||
def update_state(self, state_payload):
|
||||
"""Update state from API event."""
|
||||
if "power" in state_payload:
|
||||
is_on = state_payload["power"] == "on"
|
||||
self.on_char.set_value(is_on)
|
||||
|
||||
|
||||
class DimmableLightAccessory(OnOffLightAccessory):
|
||||
"""Dimmable Light with brightness control."""
|
||||
|
||||
def __init__(self, driver, device, api_client, display_name=None, *args, **kwargs):
|
||||
# Don't call super().__init__() yet - we need to set up service first
|
||||
name = display_name or device.friendly_name or device.name
|
||||
Accessory.__init__(self, driver, name, *args, **kwargs)
|
||||
self.device = device
|
||||
self.api_client = api_client
|
||||
self.category = CATEGORY_LIGHTBULB
|
||||
|
||||
# Create Lightbulb service with all characteristics at once
|
||||
from pyhap.loader import Loader
|
||||
loader = Loader()
|
||||
|
||||
# Create the service
|
||||
lightbulb_service = loader.get_service('Lightbulb')
|
||||
|
||||
# Add On characteristic
|
||||
on_char = lightbulb_service.get_characteristic('On')
|
||||
on_char.setter_callback = self.set_on
|
||||
self.on_char = on_char
|
||||
|
||||
# Add Brightness characteristic
|
||||
brightness_char = loader.get_char('Brightness')
|
||||
brightness_char.set_value(0)
|
||||
brightness_char.setter_callback = self.set_brightness
|
||||
lightbulb_service.add_characteristic(brightness_char)
|
||||
self.brightness_char = brightness_char
|
||||
|
||||
# Now add the complete service to the accessory
|
||||
self.add_service(lightbulb_service)
|
||||
self.lightbulb_service = lightbulb_service
|
||||
|
||||
def set_brightness(self, value):
|
||||
"""Called when HomeKit wants to change brightness."""
|
||||
payload = {
|
||||
"type": "light",
|
||||
"payload": {"brightness": int(value)}
|
||||
}
|
||||
self.api_client.post_device_set(self.device.device_id, payload["type"], payload["payload"])
|
||||
|
||||
def update_state(self, state_payload):
|
||||
"""Update state from API event."""
|
||||
super().update_state(state_payload)
|
||||
if "brightness" in state_payload:
|
||||
self.brightness_char.set_value(state_payload["brightness"])
|
||||
|
||||
|
||||
class ColorLightAccessory(DimmableLightAccessory):
|
||||
"""RGB Light with full color control."""
|
||||
|
||||
def __init__(self, driver, device, api_client, display_name=None, *args, **kwargs):
|
||||
# Don't call super().__init__() - build everything from scratch
|
||||
name = display_name or device.friendly_name or device.name
|
||||
Accessory.__init__(self, driver, name, *args, **kwargs)
|
||||
self.device = device
|
||||
self.api_client = api_client
|
||||
self.category = CATEGORY_LIGHTBULB
|
||||
|
||||
# Create Lightbulb service with all characteristics at once
|
||||
from pyhap.loader import Loader
|
||||
loader = Loader()
|
||||
|
||||
# Create the service
|
||||
lightbulb_service = loader.get_service('Lightbulb')
|
||||
|
||||
# Add On characteristic
|
||||
on_char = lightbulb_service.get_characteristic('On')
|
||||
on_char.setter_callback = self.set_on
|
||||
self.on_char = on_char
|
||||
|
||||
# Add Brightness characteristic
|
||||
brightness_char = loader.get_char('Brightness')
|
||||
brightness_char.set_value(0)
|
||||
brightness_char.setter_callback = self.set_brightness
|
||||
lightbulb_service.add_characteristic(brightness_char)
|
||||
self.brightness_char = brightness_char
|
||||
|
||||
# Add Hue characteristic
|
||||
hue_char = loader.get_char('Hue')
|
||||
hue_char.set_value(0)
|
||||
hue_char.setter_callback = self.set_hue
|
||||
lightbulb_service.add_characteristic(hue_char)
|
||||
self.hue_char = hue_char
|
||||
|
||||
# Add Saturation characteristic
|
||||
saturation_char = loader.get_char('Saturation')
|
||||
saturation_char.set_value(0)
|
||||
saturation_char.setter_callback = self.set_saturation
|
||||
lightbulb_service.add_characteristic(saturation_char)
|
||||
self.saturation_char = saturation_char
|
||||
|
||||
# Now add the complete service to the accessory
|
||||
self.add_service(lightbulb_service)
|
||||
self.lightbulb_service = lightbulb_service
|
||||
|
||||
def set_hue(self, value):
|
||||
"""Called when HomeKit wants to change hue."""
|
||||
payload = {
|
||||
"type": "light",
|
||||
"payload": {"hue": int(value)}
|
||||
}
|
||||
self.api_client.post_device_set(self.device.device_id, payload["type"], payload["payload"])
|
||||
|
||||
def set_saturation(self, value):
|
||||
"""Called when HomeKit wants to change saturation."""
|
||||
payload = {
|
||||
"type": "light",
|
||||
"payload": {"sat": int(value)}
|
||||
}
|
||||
self.api_client.post_device_set(self.device.device_id, payload["type"], payload["payload"])
|
||||
|
||||
def update_state(self, state_payload):
|
||||
"""Update state from API event."""
|
||||
super().update_state(state_payload)
|
||||
if "hue" in state_payload:
|
||||
self.hue_char.set_value(state_payload["hue"])
|
||||
if "sat" in state_payload:
|
||||
self.saturation_char.set_value(state_payload["sat"])
|
||||
|
||||
57
apps/homekit/accessories/outlet.py
Normal file
57
apps/homekit/accessories/outlet.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
Outlet/Relay Accessory Implementation for HomeKit
|
||||
|
||||
Implements simple relay/outlet (on/off switch):
|
||||
- On (read/write)
|
||||
- OutletInUse (always true)
|
||||
"""
|
||||
|
||||
from pyhap.accessory import Accessory
|
||||
from pyhap.const import CATEGORY_OUTLET
|
||||
|
||||
|
||||
class OutletAccessory(Accessory):
|
||||
"""Relay/Outlet for simple on/off control."""
|
||||
|
||||
category = CATEGORY_OUTLET
|
||||
|
||||
def __init__(self, driver, device, api_client, display_name=None, *args, **kwargs):
|
||||
"""
|
||||
Initialize the outlet accessory.
|
||||
|
||||
Args:
|
||||
driver: HAP driver instance
|
||||
device: Device object from DeviceRegistry
|
||||
api_client: ApiClient for sending commands
|
||||
display_name: Optional display name (defaults to device.friendly_name)
|
||||
"""
|
||||
name = display_name or device.friendly_name or device.name
|
||||
super().__init__(driver, name, *args, **kwargs)
|
||||
self.device = device
|
||||
self.api_client = api_client
|
||||
|
||||
# Add Outlet service
|
||||
self.outlet_service = self.add_preload_service('Outlet')
|
||||
|
||||
# Get On characteristic and set callback
|
||||
self.on_char = self.outlet_service.get_characteristic('On')
|
||||
self.on_char.setter_callback = self.set_on
|
||||
|
||||
# OutletInUse is always true (relay is always functional)
|
||||
self.in_use_char = self.outlet_service.get_characteristic('OutletInUse')
|
||||
self.in_use_char.set_value(True)
|
||||
|
||||
def set_on(self, value):
|
||||
"""Called when HomeKit wants to turn outlet on/off."""
|
||||
power_state = "on" if value else "off"
|
||||
payload = {
|
||||
"type": "relay",
|
||||
"payload": {"power": power_state}
|
||||
}
|
||||
self.api_client.post_device_set(self.device.device_id, payload["type"], payload["payload"])
|
||||
|
||||
def update_state(self, state_payload):
|
||||
"""Update state from API event."""
|
||||
if "power" in state_payload:
|
||||
is_on = state_payload["power"] == "on"
|
||||
self.on_char.set_value(is_on)
|
||||
46
apps/homekit/accessories/sensor.py
Normal file
46
apps/homekit/accessories/sensor.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Temperature & Humidity Sensor Accessory Implementation for HomeKit
|
||||
|
||||
Implements combined temperature and humidity sensor:
|
||||
- CurrentTemperature (read-only)
|
||||
- CurrentRelativeHumidity (read-only)
|
||||
"""
|
||||
|
||||
from pyhap.accessory import Accessory
|
||||
from pyhap.const import CATEGORY_SENSOR
|
||||
|
||||
|
||||
class TempHumidityAccessory(Accessory):
|
||||
"""Combined temperature and humidity sensor."""
|
||||
|
||||
category = CATEGORY_SENSOR
|
||||
|
||||
def __init__(self, driver, device, api_client, display_name=None, *args, **kwargs):
|
||||
"""
|
||||
Initialize the temp/humidity sensor accessory.
|
||||
|
||||
Args:
|
||||
driver: HAP driver instance
|
||||
device: Device object from DeviceRegistry
|
||||
api_client: ApiClient for sending commands
|
||||
display_name: Optional display name (defaults to device.friendly_name)
|
||||
"""
|
||||
name = display_name or device.friendly_name or device.name
|
||||
super().__init__(driver, name, *args, **kwargs)
|
||||
self.device = device
|
||||
self.api_client = api_client
|
||||
|
||||
# Add TemperatureSensor service
|
||||
self.temp_service = self.add_preload_service('TemperatureSensor')
|
||||
self.current_temp_char = self.temp_service.get_characteristic('CurrentTemperature')
|
||||
|
||||
# Add HumiditySensor service
|
||||
self.humidity_service = self.add_preload_service('HumiditySensor')
|
||||
self.current_humidity_char = self.humidity_service.get_characteristic('CurrentRelativeHumidity')
|
||||
|
||||
def update_state(self, state_payload):
|
||||
"""Update state from API event."""
|
||||
if "temperature" in state_payload:
|
||||
self.current_temp_char.set_value(float(state_payload["temperature"]))
|
||||
if "humidity" in state_payload:
|
||||
self.current_humidity_char.set_value(float(state_payload["humidity"]))
|
||||
72
apps/homekit/accessories/thermostat.py
Normal file
72
apps/homekit/accessories/thermostat.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Thermostat Accessory Implementation for HomeKit
|
||||
|
||||
Implements thermostat control according to homekit_mapping.md:
|
||||
- CurrentTemperature (read-only)
|
||||
- TargetTemperature (read/write)
|
||||
- CurrentHeatingCoolingState (fixed: 1 = heating)
|
||||
- TargetHeatingCoolingState (fixed: 3 = auto)
|
||||
"""
|
||||
|
||||
from pyhap.accessory import Accessory
|
||||
from pyhap.const import CATEGORY_THERMOSTAT
|
||||
|
||||
|
||||
class ThermostatAccessory(Accessory):
|
||||
"""Thermostat with temperature control."""
|
||||
|
||||
category = CATEGORY_THERMOSTAT
|
||||
|
||||
def __init__(self, driver, device, api_client, display_name=None, *args, **kwargs):
|
||||
"""
|
||||
Initialize the thermostat accessory.
|
||||
|
||||
Args:
|
||||
driver: HAP driver instance
|
||||
device: Device object from DeviceRegistry
|
||||
api_client: ApiClient for sending commands
|
||||
display_name: Optional display name (defaults to device.friendly_name)
|
||||
"""
|
||||
name = display_name or device.friendly_name or device.name
|
||||
super().__init__(driver, name, *args, **kwargs)
|
||||
self.device = device
|
||||
self.api_client = api_client
|
||||
|
||||
# Add Thermostat service
|
||||
self.thermostat_service = self.add_preload_service('Thermostat')
|
||||
|
||||
# Get characteristics
|
||||
self.current_temp_char = self.thermostat_service.get_characteristic('CurrentTemperature')
|
||||
self.target_temp_char = self.thermostat_service.get_characteristic('TargetTemperature')
|
||||
self.current_heating_cooling_char = self.thermostat_service.get_characteristic('CurrentHeatingCoolingState')
|
||||
self.target_heating_cooling_char = self.thermostat_service.get_characteristic('TargetHeatingCoolingState')
|
||||
|
||||
# Set callback for target temperature
|
||||
self.target_temp_char.setter_callback = self.set_target_temperature
|
||||
|
||||
# Set fixed heating/cooling states (mode is always "auto")
|
||||
# CurrentHeatingCoolingState: 0=Off, 1=Heat, 2=Cool
|
||||
self.current_heating_cooling_char.set_value(1) # Always heating
|
||||
|
||||
# TargetHeatingCoolingState: 0=Off, 1=Heat, 2=Cool, 3=Auto
|
||||
self.target_heating_cooling_char.set_value(3) # Always auto
|
||||
|
||||
# Set temperature range (5-30°C as per UI)
|
||||
self.target_temp_char.properties['minValue'] = 5
|
||||
self.target_temp_char.properties['maxValue'] = 30
|
||||
self.target_temp_char.properties['minStep'] = 0.5
|
||||
|
||||
def set_target_temperature(self, value):
|
||||
"""Called when HomeKit wants to change target temperature."""
|
||||
payload = {
|
||||
"type": "thermostat",
|
||||
"payload": {"target": float(value)}
|
||||
}
|
||||
self.api_client.post_device_set(self.device.device_id, payload["type"], payload["payload"])
|
||||
|
||||
def update_state(self, state_payload):
|
||||
"""Update state from API event."""
|
||||
if "current" in state_payload:
|
||||
self.current_temp_char.set_value(float(state_payload["current"]))
|
||||
if "target" in state_payload:
|
||||
self.target_temp_char.set_value(float(state_payload["target"]))
|
||||
Reference in New Issue
Block a user