Skip to content

Commit

Permalink
Add support for lock entity
Browse files Browse the repository at this point in the history
  • Loading branch information
AfonsoFGarcia committed Sep 4, 2023
1 parent dfdf46c commit 1cb6a0f
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 0 deletions.
13 changes: 13 additions & 0 deletions custom_components/hass-bluecon/HassDataOAuthTokenStorage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from bluecon import IOAuthTokenStorage, OAuthToken
from homeassistant.core import HomeAssistant
from .const import DOMAIN

class HassDataOAuthTokenStorage(IOAuthTokenStorage):
def __init__(self, hass: HomeAssistant):
self.__hass = hass

def retrieveOAuthToken(self) -> OAuthToken:
return self.__hass.data[DOMAIN]["token"]

def storeOAuthToken(self, oAuthToken: OAuthToken):
self.__hass.data[DOMAIN]["token"] = oAuthToken
57 changes: 57 additions & 0 deletions custom_components/hass-bluecon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from .const import DOMAIN, CONF_USERNAME, CONF_PASSWORD, SIGNAL_CALL_ENDED, SIGNAL_CALL_STARTED
from .HassDataOAuthTokenStorage import HassDataOAuthTokenStorage
from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, Platform
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.discovery as discovery
from homeassistant.helpers.dispatcher import dispatcher_send
import voluptuous as vol
from bluecon import BlueConAPI, INotification, CallNotification, CallEndNotification

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
},
extra = vol.ALLOW_EXTRA
)


async def async_setup(hass, config):
def notification_callback(notification: INotification):
if type(notification) is CallNotification:
dispatcher_send(hass, SIGNAL_CALL_STARTED.format(notification.deviceId, notification.accessDoorKey))
elif type(notification) is CallEndNotification:
dispatcher_send(hass, SIGNAL_CALL_ENDED.format(notification.deviceId))

conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)

hass.data[DOMAIN] = {
"bluecon": None,
"token": None
}

bluecon = await BlueConAPI.create(username, password, notification_callback, HassDataOAuthTokenStorage(hass))

async def cleanup(event):
await bluecon.stopNotificationListener()

async def prepare(event):
await hass.async_add_executor_job(bluecon.startNotificationListener)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cleanup)

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, prepare)
hass.data[DOMAIN]["bluecon"] = bluecon

hass.async_create_task(
discovery.async_load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config)
)

hass.async_create_task(
discovery.async_load_platform(hass, Platform.LOCK, DOMAIN, {}, config)
)

return True
78 changes: 78 additions & 0 deletions custom_components/hass-bluecon/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from homeassistant.components.binary_sensor import BinarySensorDeviceClass, BinarySensorEntity
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo
from .const import DOMAIN, SIGNAL_CALL_ENDED, SIGNAL_CALL_STARTED

async def async_setup_platform(hass, config, async_add_entities, discovery_info = None):
bluecon = hass.data[DOMAIN]["bluecon"]

pairings = await bluecon.getPairings()

sensors = []

for pairing in pairings:
for accessDoorName, accessDoor in pairing.accessDoorMap.items():
sensors.append(
BlueConCallStatusBinarySensor(
pairing.deviceId,
accessDoorName,
accessDoor.block,
accessDoor.subBlock,
accessDoor.number
)
)

async_add_entities(sensors)

class BlueConCallStatusBinarySensor(BinarySensorEntity):
_attr_should_poll = False

def __init__(self, deviceId, accessDoorName, block, subBlock, number):
self.lockId = f'{deviceId}_{accessDoorName}'
self.deviceId = deviceId
self.accessDoorName = accessDoorName
self.block = block
self.subBlock = subBlock
self.number = number
self._attr_unique_id = f'{self.lockId}_call_status'.lower()
self.entity_id = f'{DOMAIN}.{self._attr_unique_id}'.lower()
self._attr_is_on = False

@property
def unique_id(self) -> str | None:
return self.entity_id

@property
def device_class(self) -> BinarySensorDeviceClass | None:
return BinarySensorDeviceClass.CONNECTIVITY

async def async_added_to_hass(self) -> None:
self.async_on_remove(
async_dispatcher_connect(self.hass, SIGNAL_CALL_STARTED.format(self.deviceId, self.accessDoorName), self._call_started_callback)
)
self.async_on_remove(
async_dispatcher_connect(self.hass, SIGNAL_CALL_ENDED.format(self.deviceId), self._call_ended_callback)
)

@callback
def _call_started_callback(self) -> None:
self._attr_is_on = True
self.async_schedule_update_ha_state(True)

@callback
def _call_ended_callback(self) -> None:
self._attr_is_on = False
self.async_schedule_update_ha_state(True)

@property
def device_info(self) -> DeviceInfo | None:
return DeviceInfo(
identifiers = {
(DOMAIN, self.lockId)
},
name = f'Fermax Blue {self.lockId}',
manufacturer = 'Fermax',
model = 'Blue',
sw_version = '0.0.1'
)
7 changes: 7 additions & 0 deletions custom_components/hass-bluecon/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DOMAIN = "bluecon"

CONF_USERNAME = "username"
CONF_PASSWORD = "password"

SIGNAL_CALL_STARTED = "bluecon_call_started_{}_{}"
SIGNAL_CALL_ENDED = "bluecon_call_ended_{}"
96 changes: 96 additions & 0 deletions custom_components/hass-bluecon/lock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import asyncio
from .const import DOMAIN
from homeassistant.components.lock import LockEntity
from homeassistant.const import (
STATE_JAMMED,
STATE_LOCKED,
STATE_LOCKING,
STATE_UNLOCKED,
STATE_UNLOCKING,
)
from homeassistant.helpers.entity import DeviceInfo
from bluecon import BlueConAPI

LOCK_UNLOCK_DELAY = 5

async def async_setup_platform(hass, config, async_add_entities, discovery_info = None):
bluecon = hass.data[DOMAIN]["bluecon"]

pairings = await bluecon.getPairings()

locks = []

for pairing in pairings:
for accessDoorName, accessDoor in pairing.accessDoorMap.items():
locks.append(
BlueConLock(
bluecon,
pairing.deviceId,
accessDoorName,
accessDoor
)
)

async_add_entities(locks)

class BlueConLock(LockEntity):
_attr_should_poll = False

def __init__(self, bluecon: BlueConAPI, deviceId, accessDoorName, accessDoor):
self.bluecon = bluecon
self.lockId = f'{deviceId}_{accessDoorName}'
self.deviceId = deviceId
self.accessDoorName = accessDoorName
self.accessDoor = accessDoor
self._attr_unique_id = f'{self.lockId}_door_lock'.lower()
self.entity_id = f'{DOMAIN}.{self._attr_unique_id}'.lower()
self._state = STATE_LOCKED

@property
def is_locking(self) -> bool:
"""Return true if lock is locking."""
return self._state == STATE_LOCKING

@property
def is_unlocking(self) -> bool:
"""Return true if lock is unlocking."""
return self._state == STATE_UNLOCKING

@property
def is_jammed(self) -> bool:
"""Return true if lock is jammed."""
return self._state == STATE_JAMMED

@property
def is_locked(self) -> bool:
"""Return true if lock is locked."""
return self._state == STATE_LOCKED

async def async_lock(self) -> None:
pass

async def async_unlock(self) -> None:
"""Unlock the device."""
self._state = STATE_UNLOCKING
self.async_schedule_update_ha_state(True)
await self.bluecon.openDoor(self.deviceId, self.accessDoor)
self._state = STATE_UNLOCKED
self.async_schedule_update_ha_state(True)
await asyncio.sleep(LOCK_UNLOCK_DELAY)
self._state = STATE_LOCKED
self.async_schedule_update_ha_state(True)

async def async_open(self) -> None:
pass

@property
def device_info(self) -> DeviceInfo | None:
return DeviceInfo(
identifiers = {
(DOMAIN, self.lockId)
},
name = f'Fermax Blue {self.lockId}',
manufacturer = 'Fermax',
model = 'Blue',
sw_version = '0.0.1'
)
8 changes: 8 additions & 0 deletions custom_components/hass-bluecon/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"domain": "bluecon",
"name": "Fermax Blue",
"version": "0.0.1",
"documentation": "https://hass-bluecon.afonsogarcia.dev/",
"requirements": ["bluecon==0.0.1a5", "aiohttp", "oscrypto", "protobuf", "http-ece"],
"codeowners": ["@AfonsoFGarcia"]
}
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bluecon

0 comments on commit 1cb6a0f

Please sign in to comment.