Skip to content

Add snooze property & get_alarms service #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion custom_components/qingping_alarm_clock/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
CONF_ALARM_TIME = "time"
CONF_ALARM_DAYS = "days"
CONF_ALARM_ENABLED = "enabled"
CONF_ALARM_SNOOZE = "snooze"
CONF_TIME = "time"

SERVICE_SET_ALARM = "set_alarm"
SERVICE_DELETE_ALARM = "delete_alarm"
SERVICE_GET_ALARMS = "get_alarms"
SERVICE_SET_TIME = "set_time"
SERVICE_REFRESH = "refresh"

DISCONNECT_DELAY = 30
CONNECTION_TIMEOUT = 120
CONNECTION_TIMEOUT = 120

ATTR_ONLY_ENABLED = "only_enabled"
2 changes: 1 addition & 1 deletion custom_components/qingping_alarm_clock/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"requirements": [
"bleak>=0.17.0"
],
"version": "0.0.5"
"version": "0.0.6"
}
10 changes: 7 additions & 3 deletions custom_components/qingping_alarm_clock/qingping/alarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Alarm:
hour: int | None = None
minute: int | None = None
days: set[AlarmDay] | None = None
snooze: bool | None = None

def __init__(self, slot: int, alarm_bytes: bytes):
self.slot = slot
Expand All @@ -31,15 +32,17 @@ def __init__(self, slot: int, alarm_bytes: bytes):
self.hour = alarm_bytes[1]
self.minute = alarm_bytes[2]
self.days = self._bitmask_to_days(alarm_bytes[3])
self.snooze = alarm_bytes[4] == 1

_LOGGER.debug(f"Alarm #{self.slot} enabled: {self.is_enabled}, hour: {self.hour}, minute: {self.minute}, days: {self.days}")
_LOGGER.debug(f"Alarm #{self.slot} enabled: {self.is_enabled}, hour: {self.hour}, minute: {self.minute}, days: {self.days}, snooze: {self.snooze}")

@property
def is_configured(self):
return self.is_enabled is not None and \
self.hour is not None and \
self.minute is not None and \
self.days is not None
self.days is not None and \
self.snooze is not None

@property
def time(self):
Expand Down Expand Up @@ -74,7 +77,7 @@ def to_bytes(self) -> bytes:
byte_array.append(self.hour)
byte_array.append(self.minute)
byte_array.append(self._days_to_bitmask(self.days))
byte_array.append(0x00)
byte_array.append(0x01 if self.snooze else 0x00)
else:
byte_array.extend([0xff, 0xff, 0xff, 0xff, 0xff])

Expand All @@ -85,6 +88,7 @@ def deactivate(self):
self.hour = None
self.minute = None
self.days = None
self.snooze = None

def _bitmask_to_days(self, bitmask: int):
bit_to_day = {
Expand Down
6 changes: 4 additions & 2 deletions custom_components/qingping_alarm_clock/qingping/qingping.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ async def set_alarm(
slot: int,
is_enabled: bool | None,
time: dtime | None,
days: set[AlarmDay] | None
days: set[AlarmDay] | None,
snooze: bool | None
) -> bool:
await self._ensure_alarms()
await self._ensure_connected()
Expand All @@ -174,7 +175,8 @@ async def set_alarm(
alarm.time = time
if days is not None:
alarm.days = days

if snooze is not None:
alarm.snooze = snooze
if not alarm.is_configured:
raise ServiceValidationError("Alarm not configured.")

Expand Down
53 changes: 50 additions & 3 deletions custom_components/qingping_alarm_clock/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
import voluptuous as vol
from datetime import datetime

from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.core import HomeAssistant, ServiceCall, ServiceResponse, SupportsResponse, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH
from homeassistant.const import ATTR_DEVICE_ID

from .qingping.util import alarm_days_from_string
from .qingping import Qingping
from .qingping import Qingping, Alarm
from .const import (
DOMAIN,
SERVICE_SET_ALARM,
SERVICE_DELETE_ALARM,
SERVICE_GET_ALARMS,
SERVICE_SET_TIME,
SERVICE_REFRESH,
CONF_TIME,
Expand All @@ -23,6 +24,8 @@
CONF_ALARM_SLOT,
CONF_ALARM_TIME,
CONF_ALARM_DAYS,
CONF_ALARM_SNOOZE,
ATTR_ONLY_ENABLED,
)

_LOGGER = logging.getLogger(__name__)
Expand All @@ -35,13 +38,19 @@
vol.Optional(CONF_ALARM_TIME): cv.time,
vol.Optional(CONF_ALARM_DAYS): vol.All(cv.string, vol.Match(DAYS_REGEX)),
vol.Optional(CONF_ALARM_ENABLED): cv.boolean,
vol.Optional(CONF_ALARM_SNOOZE): cv.boolean,
})

DELETE_ALARM_SCHEMA = vol.Schema({
vol.Required(ATTR_DEVICE_ID): str,
vol.Required(CONF_ALARM_SLOT): vol.All(vol.Coerce(int), vol.Range(min=0, max=ALARM_SLOTS_COUNT)),
})

GET_ALARMS_SCHEMA = vol.Schema({
vol.Required(ATTR_DEVICE_ID): str,
vol.Optional(ATTR_ONLY_ENABLED, default=False): cv.boolean
})

SET_TIME_SCHEMA = vol.Schema({
vol.Required(ATTR_DEVICE_ID): str,
vol.Required(CONF_TIME): cv.datetime
Expand All @@ -66,14 +75,44 @@ async def async_set_alarm(call: ServiceCall) -> None:
is_enabled = call.data.get(CONF_ALARM_ENABLED)
time = call.data.get(CONF_ALARM_TIME)
days = alarm_days_from_string(call.data.get(CONF_ALARM_DAYS))
snooze = call.data.get(CONF_ALARM_SNOOZE)

await instance.set_alarm(
slot,
is_enabled,
time,
days
days,
snooze
)

async def async_get_alarms(call: ServiceCall) -> ServiceResponse:
"""Get the list of alarms."""
mac = _get_device_mac(hass, call)
only_enabled = call.data[ATTR_ONLY_ENABLED] | False
for entry in hass.config_entries.async_entries(DOMAIN):
instance: Qingping = entry.runtime_data
if instance.mac != mac:
continue

responseAlarms = []
for alarm in instance.alarms:
if not alarm.is_configured:
continue
if only_enabled and not alarm.is_enabled:
continue

alarm_dict = {
CONF_ALARM_SLOT: alarm.slot,
CONF_ALARM_ENABLED: alarm.is_enabled,
CONF_ALARM_TIME: alarm.time,
CONF_ALARM_DAYS: alarm.days_string,
CONF_ALARM_SNOOZE: alarm.snooze
}
responseAlarms.append(alarm_dict)

responseAlarms.sort(key=lambda a: a[CONF_ALARM_SLOT]) # order by slot number
return {"alarms": responseAlarms}

async def async_delete_alarm(call: ServiceCall) -> None:
"""Delete alarm at the specified slot."""
mac = _get_device_mac(hass, call)
Expand Down Expand Up @@ -135,6 +174,14 @@ def _get_device_mac(hass, call):
schema=SET_ALARM_SCHEMA
)

hass.services.async_register(
DOMAIN,
SERVICE_GET_ALARMS,
async_get_alarms,
schema=GET_ALARMS_SCHEMA,
supports_response=SupportsResponse.ONLY
)

hass.services.async_register(
DOMAIN,
SERVICE_DELETE_ALARM,
Expand Down
20 changes: 20 additions & 0 deletions custom_components/qingping_alarm_clock/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ set_alarm:
required: false
selector:
boolean:
snooze:
description: "Whether the alarm has snooze enabled."
example: true
required: false
selector:
boolean:
get_alarms:
description: "Get the list of alarms."
fields:
device_id:
required: true
selector:
device:
integration: qingping_alarm_clock
only_enabled:
description: "Whether to only get enabled alarms."
example: true
required: false
selector:
boolean:
delete_alarm:
description: "Delete an alarm."
fields:
Expand Down