mos.ru power sensors
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
"""Define the ability to send single and repeating notifications."""
|
||||
# pylint: disable=too-few-public-methods
|
||||
from datetime import datetime
|
||||
from typing import Callable, List, Optional, Union
|
||||
from uuid import uuid4
|
||||
|
||||
import attr
|
||||
|
||||
from core import Base # pylint: disable=no-name-in-module
|
||||
from notification.target import Target, get_targets_from_string
|
||||
|
||||
CONF_NOTIFICATION_HANDLES = "notification_handles"
|
||||
|
||||
|
||||
@attr.s(slots=True, auto_attribs=True)
|
||||
class Notification:
|
||||
"""Define a base notification object."""
|
||||
|
||||
# App reference:
|
||||
_app: Base
|
||||
|
||||
# Raw targets, message, and optional title:
|
||||
targets: Union[str, List[str]]
|
||||
message: str
|
||||
title: Optional[str] = None
|
||||
|
||||
# Scheduling properties:
|
||||
urgent: bool = False
|
||||
repeat: bool = False
|
||||
when: Optional[datetime] = None
|
||||
interval: Optional[int] = None
|
||||
|
||||
iterations: Optional[int] = None
|
||||
_iteration_count: int = 0
|
||||
|
||||
# Message data
|
||||
data: Optional[dict] = None
|
||||
|
||||
# "Auto-generated" properties:
|
||||
id: str = attr.Factory(lambda: uuid4().hex)
|
||||
|
||||
def __attrs_post_init__(self):
|
||||
"""Perform some post-__init__ initialization."""
|
||||
# Initialize a shared data space for handlers:
|
||||
if CONF_NOTIFICATION_HANDLES not in self._app.global_vars:
|
||||
self._app.global_vars[CONF_NOTIFICATION_HANDLES] = {}
|
||||
|
||||
# Give every notification a parameter that will allow it to be
|
||||
# threaded on iOS; this shouldn't hurt any non-iOS notifier:
|
||||
if not self.data:
|
||||
self.data = {}
|
||||
self.data.setdefault("push", {"thread-id": self.id})
|
||||
|
||||
def _cancel(self) -> None:
|
||||
"""Cancel the notification."""
|
||||
if self.id not in self._app.global_vars[CONF_NOTIFICATION_HANDLES]:
|
||||
return
|
||||
|
||||
handle = self._app.global_vars[CONF_NOTIFICATION_HANDLES].pop(self.id)
|
||||
if handle:
|
||||
self._app.cancel_timer(handle)
|
||||
|
||||
def _on_send(self, kwargs: dict) -> None:
|
||||
"""Send a single (immediate or scheduled) notification."""
|
||||
# If this is a repeating notification, it's already been sent once, and
|
||||
# we've exceeded our iterations, cancel right away:
|
||||
if self.iterations and self._iteration_count == self.iterations:
|
||||
self._cancel()
|
||||
return
|
||||
|
||||
if isinstance(self.targets, str):
|
||||
self.targets = [self.targets]
|
||||
|
||||
targets = [] # type: List[Target]
|
||||
for target_str in self.targets:
|
||||
targets += get_targets_from_string(self._app, target_str)
|
||||
|
||||
for target in targets:
|
||||
target.payload["data"] = self.data
|
||||
if self.title:
|
||||
target.payload["title"] = self.title
|
||||
if target.payload.get("message"):
|
||||
target.payload["message"] += self.message
|
||||
else:
|
||||
target.payload["message"] = self.message
|
||||
|
||||
self._app.call_service(target.service_call, **target.payload)
|
||||
|
||||
if self.iterations:
|
||||
self._iteration_count += 1
|
||||
|
||||
def send(self) -> Callable:
|
||||
"""Send the notification."""
|
||||
handle = None
|
||||
if self.when and self.interval:
|
||||
handle = self._app.run_every(self._on_send, self.when, self.interval)
|
||||
elif self.when:
|
||||
handle = self._app.run_at(self._on_send, self.when)
|
||||
else:
|
||||
self._on_send({})
|
||||
|
||||
self._app.global_vars[CONF_NOTIFICATION_HANDLES][self.id] = handle
|
||||
return self._cancel
|
||||
|
||||
|
||||
def send_notification(
|
||||
app: Base,
|
||||
targets: Union[str, List[str]],
|
||||
message: str,
|
||||
title: str = None,
|
||||
urgent: bool = False,
|
||||
when: datetime = None,
|
||||
interval: int = None,
|
||||
iterations: int = None,
|
||||
data: dict = None,
|
||||
) -> Callable:
|
||||
"""Send/schedule a notification and return a method to cancel it."""
|
||||
notification = Notification(
|
||||
app=app,
|
||||
targets=targets,
|
||||
message=message,
|
||||
title=title,
|
||||
urgent=urgent,
|
||||
when=when,
|
||||
interval=interval,
|
||||
iterations=iterations,
|
||||
data=data,
|
||||
)
|
||||
return notification.send()
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Define various notification target types."""
|
||||
# pylint: disable=too-few-public-methods
|
||||
from typing import Dict, List
|
||||
|
||||
from const import CONF_PEOPLE
|
||||
from core import Base # pylint: disable=no-name-in-module
|
||||
|
||||
|
||||
class Target:
|
||||
"""Define a target for generic notifications."""
|
||||
|
||||
def __init__(self, notify_service: str) -> None:
|
||||
"""Initialize."""
|
||||
self.payload = {} # type: Dict[str, str]
|
||||
self.service_call = "notify/{0}".format(notify_service)
|
||||
|
||||
|
||||
class SlackTarget(Target):
|
||||
"""Define a target for Slack notifications."""
|
||||
|
||||
def __init__(self, channel: str = None, mention: str = None) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__("slack")
|
||||
|
||||
if channel:
|
||||
self.payload["target"] = "#{0}".format(channel)
|
||||
if mention:
|
||||
self.payload["message"] = "{0}: ".format(mention)
|
||||
|
||||
|
||||
class TargetFactory:
|
||||
"""Define an abstract factory."""
|
||||
|
||||
def __init__(self, app: Base, target: str) -> None:
|
||||
"""Initialization."""
|
||||
self._app = app
|
||||
self._target = target
|
||||
|
||||
def build(self) -> List:
|
||||
"""Build notification target objects from a string representation."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class NotifierFactory(TargetFactory):
|
||||
"""Define a factory to build generic notification targets."""
|
||||
|
||||
def build(self) -> List[Target]:
|
||||
"""Build notification target objects from a string representation."""
|
||||
return [Target(self._target)]
|
||||
|
||||
|
||||
class PersonFactory(TargetFactory):
|
||||
"""Define a factory to build notification targets specific to a person."""
|
||||
|
||||
def build(self) -> List[Target]:
|
||||
"""Build notification target objects from a string representation."""
|
||||
name = self._target.split("person:")[1]
|
||||
|
||||
try:
|
||||
person = next(
|
||||
(p for p in self._app.global_vars[CONF_PEOPLE] if p.first_name == name)
|
||||
)
|
||||
except StopIteration:
|
||||
self._app.error("Unknown person: {0}".format(self._target))
|
||||
return []
|
||||
|
||||
targets = [] # type: List[Target]
|
||||
for target in person.notifiers:
|
||||
targets += get_targets_from_string(self._app, target)
|
||||
|
||||
return targets
|
||||
|
||||
|
||||
class PresenceFactory(TargetFactory):
|
||||
"""Define a factory to build notification targets specific to presence."""
|
||||
|
||||
def build(self) -> List[Target]:
|
||||
"""Build notification target objects from a string representation."""
|
||||
presence = self._target.split(":")[1]
|
||||
presence_manager = self._app.get_app("presence_manager")
|
||||
|
||||
try:
|
||||
presence_method = getattr(presence_manager, "whos_{0}".format(presence))
|
||||
except AttributeError:
|
||||
self._app.error("Unknown presence target: {0}".format(presence))
|
||||
return []
|
||||
|
||||
targets = [] # type: List[Target]
|
||||
for person in presence_method():
|
||||
for target in person.notifiers:
|
||||
targets += get_targets_from_string(self._app, target)
|
||||
|
||||
return targets
|
||||
|
||||
|
||||
class SlackFactory(TargetFactory):
|
||||
"""Define a factory to build Slack notification targets."""
|
||||
|
||||
def build(self) -> List[SlackTarget]:
|
||||
"""Build notification target objects from a string representation."""
|
||||
data = self._target.split(":")
|
||||
|
||||
if len(data) == 1:
|
||||
return [SlackTarget(None, None)]
|
||||
|
||||
splits = data[1].split("/")
|
||||
|
||||
if len(splits) == 2:
|
||||
return [SlackTarget(splits[0], splits[1])]
|
||||
|
||||
if "@" in splits[0]:
|
||||
return [SlackTarget(None, splits[0])]
|
||||
|
||||
return [SlackTarget(splits[0], None)]
|
||||
|
||||
|
||||
def get_targets_from_string(app: Base, target: str) -> List[Target]:
|
||||
"""Return the appropriate factory for the passed target."""
|
||||
if target.startswith("person:"):
|
||||
factory = PersonFactory(app, target)
|
||||
elif target.startswith("presence:"):
|
||||
factory = PresenceFactory(app, target) # type: ignore
|
||||
elif target.startswith("slack:"):
|
||||
factory = SlackFactory(app, target) # type: ignore
|
||||
else:
|
||||
factory = NotifierFactory(app, target) # type: ignore
|
||||
|
||||
return factory.build()
|
||||
Reference in New Issue
Block a user