mos.ru power sensors

This commit is contained in:
Your Name
2019-10-31 11:33:20 +03:00
parent 4bf01f2873
commit 94778c039e
24 changed files with 930 additions and 73 deletions
+5 -2
View File
@@ -2,6 +2,9 @@ ARG VERSION
FROM acockburn/appdaemon:${VERSION}
RUN pip3 install \
attrs==19.1.0 \
emp_mos_api==0.10 \
sqlalchemy \
mysqlclient
packaging==16.8 \
python-Levenshtein==0.12.0 \
voluptuous==0.11.5 \
noolite==1.4.0 && apt-get update -y && apt-get install -y libusb-1.0-0-dev && apt-get clean
+57
View File
@@ -0,0 +1,57 @@
"""Define various constants."""
# Top level-related config keys:
CONF_ENTITY_IDS = "entity_ids"
CONF_PROPERTIES = "properties"
# Entity-related config keys:
CONF_DEVICE_TRACKERS = "device_trackers"
CONF_ENTITY_ID = "entity_id"
CONF_ICON = "icon"
# Name-related config keys:
CONF_FRIENDLY_NAME = "friendly_name"
# State-related config keys:
CONF_STATE = "state"
# Time-related config keys:
CONF_DELAY = "delay"
CONF_DURATION = "duration"
CONF_END_TIME = "end_time"
CONF_START_TIME = "start_time"
CONF_UPDATE_INTERVAL = "update_interval"
# Constraint-related config keys:
CONF_CONSTRAIN_CLOUDY = "constrain_cloudy"
CONF_CONSTRAIN_SUN = "constrain_sun"
# Notification-related config keys:
CONF_NOTIFIERS = "notifiers"
CONF_NOTIFICATION_INTERVAL = "notification_interval"
CONF_NOTIFICATION_TARGET = "notification_target"
# Comparison-related config keys:
CONF_ABOVE = "above"
CONF_BELOW = "below"
CONF_EQUAL_TO = "equal_to"
COMPARITORS = (CONF_ABOVE, CONF_BELOW, CONF_EQUAL_TO)
# Collections of valid inputs:
OPERATOR_ALL = "all"
OPERATOR_ANY = "any"
OPERATORS = (OPERATOR_ALL, OPERATOR_ANY)
TOGGLE_STATES = ("closed", "off", "on", "open")
# Misc. config keys:
CONF_PEOPLE = "people"
CONF_TRIGGER_FIRST = "trigger_on_first_only"
# Events:
EVENT_ALARM_CHANGE = "ALARM_CHANGE"
EVENT_MODE_CHANGE = "MODE_CHANGE"
EVENT_PRESENCE_CHANGE = "PRESENCE_CHANGE"
EVENT_PROXIMITY_CHANGE = "PROXIMITY_CHANGE"
EVENT_VACUUM_START = "VACUUM_START"
# Misc.:
THRESHOLD_CLOUDY = 70.0
+325
View File
@@ -0,0 +1,325 @@
"""Define a generic object which all apps/automations inherit from."""
from typing import Callable, Dict, List, Union
import voluptuous as vol
from appdaemon.plugins.hass.hassapi import Hass # pylint: disable=no-name-in-module
from const import (
CONF_ENTITY_IDS,
CONF_PROPERTIES,
EVENT_MODE_CHANGE,
OPERATOR_ALL,
OPERATORS,
THRESHOLD_CLOUDY,
)
CONF_CLASS = "class"
CONF_MODULE = "module"
CONF_DEPENDENCIES = "dependencies"
CONF_APP = "app"
CONF_CONSTRAINTS = "constraints"
CONF_MODE_ALTERATIONS = "mode_alterations"
CONF_ENABLED_TOGGLE_ENTITY_ID = "enabled_toggle_entity_id"
CONF_INITIAL = "initial"
CONF_NAME = "name"
CONF_OPERATOR = "operator"
SENSOR_CLOUD_COVER = "sensor.dark_sky_cloud_coverage"
APP_SCHEMA = vol.Schema(
{
vol.Required(CONF_MODULE): str,
vol.Required(CONF_CLASS): str,
vol.Optional(CONF_APP): str,
vol.Optional(CONF_CONSTRAINTS): vol.Schema(
{
vol.Optional(CONF_OPERATOR, default=OPERATOR_ALL): vol.In(OPERATORS),
vol.Required(CONF_CONSTRAINTS): dict,
}
),
vol.Optional(CONF_ENABLED_TOGGLE_ENTITY_ID): str,
},
extra=vol.ALLOW_EXTRA,
)
class Base(Hass):
"""Define a base app/automation object."""
APP_SCHEMA = APP_SCHEMA
def initialize(self) -> None:
"""Initialize."""
try:
self.APP_SCHEMA(self.args)
except vol.Invalid as err:
self.error("Invalid app schema: {0}".format(err), level="ERROR")
return
# Define a holding place for HASS entity IDs:
self.entity_ids = self.args.get(CONF_ENTITY_IDS, {})
# Define a holding place for any scheduler handles that the app wants to keep
# track of:
self.handles = {} # type: Dict[str, Callable]
# Define a holding place for key/value properties for this app:
self.properties = self.args.get(CONF_PROPERTIES, {})
# Define a holding place for any mode alterations for this app:
self.mode_alterations = self.args.get(CONF_MODE_ALTERATIONS, [])
# Take every dependecy and create a reference to it:
for app in self.args.get(CONF_DEPENDENCIES, []):
if not getattr(self, app, None):
setattr(self, app, self.get_app(app))
# Define a reference to the "manager app" – for example, a trash-
# related app might carry a reference to TrashManager:
if self.args.get(CONF_APP):
self.app = getattr(self, self.args[CONF_APP])
# Set the entity ID of the input boolean that will control whether
# this app is enabled or not:
if self.args.get(CONF_ENABLED_TOGGLE_ENTITY_ID):
self._enabled_toggle_entity_id = self.args[CONF_ENABLED_TOGGLE_ENTITY_ID]
else:
self._enabled_toggle_entity_id = "input_boolean.{0}".format(self.name)
# Register custom constraints:
self.register_constraint("constrain_anyone")
self.register_constraint("constrain_cloudy")
self.register_constraint("constrain_enabled")
self.register_constraint("constrain_everyone")
self.register_constraint("constrain_in_blackout")
self.register_constraint("constrain_noone")
self.register_constraint("constrain_sun")
# Run any initial configuration:
if hasattr(self, "configure"):
self.configure()
# Set up connections to the automation being disabled/enabled:
if self._enabled_entity_exists():
# Listen and track mode changes so that the app can respond as needed:
self.mode_events = [] # type: List[str]
self.listen_event(self._on_mode_change, EVENT_MODE_CHANGE)
# If the app has defined callbacks fror when the app is enabled or disabled,
# attach them to listeners. Note that we utilize `_on_*` here
# (leading underscore) – we do this so automations don't have to remember
# callback method signatures:
if hasattr(self, "on_disable"):
super().listen_state(
self._on_disable, self._enabled_toggle_entity_id, new="off"
)
if hasattr(self, "on_enable"):
super().listen_state(
self._on_enable, self._enabled_toggle_entity_id, new="on"
)
@property
def enabled(self) -> bool:
"""Return whether the app is enabled."""
if not self._enabled_entity_exists():
return True
return self.get_state(self._enabled_toggle_entity_id) == "on"
def _attach_constraints(
self, method: Callable, callback: Callable, *args: list, **kwargs: dict
) -> Union[str, list]:
"""Attach the constraint mechanism to an AppDaemon listener."""
if not self.args.get(CONF_CONSTRAINTS):
return method(callback, *args, **kwargs)
constraints = self.args[CONF_CONSTRAINTS][CONF_CONSTRAINTS]
if self.args[CONF_CONSTRAINTS].get(CONF_OPERATOR) == OPERATOR_ALL:
return method(callback, *args, **constraints, **kwargs)
handles = [] # type: List[str]
for name, value in constraints.items():
method(callback, *args, **{name: value}, **kwargs)
return handles
def _constrain_presence(self, method: str, value: Union[str, None]) -> bool:
"""Constrain presence in a generic fashion."""
if not value:
return True
return getattr(self.presence_manager, method)(
*[self.presence_manager.HomeStates[s] for s in value.split(",")]
)
def _enabled_entity_exists(self) -> bool:
"""Return True if the enabled entity exists."""
return self.entity_exists(self._enabled_toggle_entity_id)
def _on_disable(
self, entity: Union[str, dict], attribute: str, old: str, new: str, kwargs: dict
) -> None:
"""Set a listener for when the automation is disabled."""
self.on_disable()
def _on_enable(
self, entity: Union[str, dict], attribute: str, old: str, new: str, kwargs: dict
) -> None:
"""Set a listener for when the automation is enabled."""
self.on_enable()
def _on_mode_change(self, event_name: str, data: dict, kwargs: dict) -> None:
"""Compare mode changes to registered mode alterations."""
mode = data["name"]
if data["state"] == "on":
self.mode_events.append(mode)
elif mode in self.mode_events:
self.mode_events.remove(mode)
try:
primary = max(
(m for m in self.mode_alterations if m["mode"] in self.mode_events),
key=lambda m: m["priority"],
)
except ValueError:
try:
primary = next((m for m in self.mode_alterations if m["mode"] == mode))
except StopIteration:
return
if primary["action"] == "enable":
primary["action"] = "disable"
else:
primary["action"] = "enable"
# If the primary mode alteration prescribes an action that matches the state the
# app is already in, return:
if (self.enabled and primary["action"] == "enable") or (
not self.enabled and primary["action"] == "disable"
):
return
if primary["action"] == "enable":
self.enable()
else:
self.disable()
def constrain_anyone(self, value: str) -> bool:
"""Constrain execution to whether anyone is in a state."""
return self._constrain_presence("anyone", value)
def constrain_enabled(self, value: bool) -> bool:
"""Constrain execution to whether anyone is in a state."""
if value:
return self.enabled
return False
def constrain_cloudy(self, value: bool) -> bool:
"""Constrain execution based whether it's cloudy or not."""
cloud_cover = float(self.get_state(SENSOR_CLOUD_COVER))
if (value and cloud_cover >= THRESHOLD_CLOUDY) or (
not value and cloud_cover < THRESHOLD_CLOUDY
):
return True
return False
def constrain_everyone(self, value: str) -> bool:
"""Constrain execution to whether everyone is in a state."""
return self._constrain_presence("everyone", value)
def constrain_in_blackout(self, state: str) -> bool:
"""Constrain execution based on blackout state."""
if state is True:
return self.get_state("input_boolean.blackout_mode") == "on"
return self.get_state("input_boolean.blackout_mode") == "off"
def constrain_noone(self, value: str) -> bool:
"""Constrain execution to whether no one is in a state."""
return self._constrain_presence("noone", value)
def constrain_sun(self, position: str) -> bool:
"""Constrain execution to the location of the sun."""
if (position == "up" and self.sun_up()) or (
position == "down" and self.sun_down()
):
return True
return False
def disable(self) -> None:
"""Disable the app."""
if not self.entity_exists(self._enabled_toggle_entity_id):
return
self.turn_off(self._enabled_toggle_entity_id)
def enable(self) -> None:
"""Enable the app."""
if not self.entity_exists(self._enabled_toggle_entity_id):
return
self.turn_on(self._enabled_toggle_entity_id)
def listen_ios_event(self, callback: Callable, action: str) -> None:
"""Register a callback for an iOS event."""
self.listen_event(
callback,
"ios.notification_action_fired",
actionName=action,
constrain_enabled=True,
)
def listen_event(self, callback, event=None, auto_constraints=False, **kwargs):
"""Wrap AppDaemon's `listen_event` with the constraint mechanism."""
if not auto_constraints:
return super().listen_event(callback, event, **kwargs)
return self._attach_constraints(super().listen_event, callback, event, **kwargs)
def listen_state(self, callback, entity=None, auto_constraints=False, **kwargs):
"""Wrap AppDaemon's `listen_state` with the constraint mechanism."""
if not auto_constraints:
return super().listen_state(callback, entity, **kwargs)
return self._attach_constraints(
super().listen_state, callback, entity, **kwargs
)
def run_daily(self, callback, start, auto_constraints=False, **kwargs):
"""Wrap AppDaemon's `run_daily` with the constraint mechanism."""
if not auto_constraints:
return super().run_daily(callback, start, **kwargs)
return self._attach_constraints(super().run_daily, callback, start, **kwargs)
def run_at_sunrise(self, callback, *args, auto_constraints=False, **kwargs):
"""Wrap AppDaemon's `run_at_sunrise` with the constraint mechanism."""
if not auto_constraints:
return super().run_at_sunrise(callback, **kwargs)
return self._attach_constraints(super().run_at_sunrise, callback, **kwargs)
def run_at_sunset(
self,
callback: Callable[..., None],
*args: list,
auto_constraints=False,
**kwargs: dict
):
"""Wrap AppDaemon's `run_at_sunset` with the constraint mechanism."""
if not auto_constraints:
return super().run_at_sunset(callback, **kwargs)
return self._attach_constraints(super().run_at_sunset, callback, **kwargs)
def run_every(self, callback, start, interval, auto_constraints=False, **kwargs):
"""Wrap AppDaemon's `run_every` with the constraint mechanism."""
if not auto_constraints:
return super().run_every(callback, start, interval, **kwargs)
return self._attach_constraints(
super().run_every, callback, start, interval, **kwargs
)
@@ -0,0 +1,38 @@
import appdaemon.plugins.hass.hassapi as hass
import noolite
class Noolite(hass.Hass):
noolite = noolite.NooLite()
brightness = 25
force = False
channel = 0
def initialize(self):
self.run_at_sunrise(self.sunrise_cb)
self.run_at_sunset(self.sunset_cb)
self.listen_state(self.light_change, "group.mainroom_light")
self.listen_event(self.away_mode, "away_mode")
def away_mode(self, event_id, event_args, kwargs):
self.noolite_off()
def light_change(self, entity, attribute, old, new, kwargs):
sun_down = self.sun_down()
if new == "off" and (sun_down or self.force):
self.noolite_on(self.brightness)
else:
self.noolite_off()
def noolite_off(self):
self.log("Turning off salt lamp.")
self.noolite.off(self.channel)
def noolite_on(self, brightness=120):
self.log("Turning on salt lamp for brightness {}.".format(brightness))
self.noolite.set(self.channel, brightness)
def sunset_cb(self, kwargs):
self.noolite_on(self.brightness)
def sunrise_cb(self, kwargs):
self.noolite_off()
@@ -0,0 +1,3 @@
salt_lamp:
module: salt_lamp
class: Noolite
@@ -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()
+20 -1
View File
@@ -36,6 +36,12 @@ class AliceScenes(hass.Hass):
self.turn_off_light()
if (entity_name == "night_mode"):
self.night_mode()
if (entity_name == "turn_on_coffeemachine"):
self.turn_on_coffeemachine()
if (entity_name == "turn_on_coffeemachine"):
self.turn_on_coffeemachine()
if (entity_name == "make_coffee"):
self.make_coffee()
def terminate(self):
if self.listen_event_handle_list != None:
@@ -48,4 +54,17 @@ class AliceScenes(hass.Hass):
def night_mode(self):
self.turn_off('switch.plug_158d0001104a0c') #tv
self.turn_off('group.all_lights')
self.call_service("cover/close_cover", entity_id = 'cover.curtain_158d0002b0c46a')
self.call_service("cover/close_cover", entity_id = 'cover.curtain_158d0002b0c46a')
def turn_on_coffeemachine(self):
self.log('Turn on coffeemachine')
self.call_service("mqtt/publish", topic = 'home/coffee/coffee/power/set', payload = 'on')
def make_coffee(self):
self.log('Make coffee')
self.call_service("mqtt/publish", topic = 'home/coffee/coffee/power/set', payload = 'on')
self.timer = self.run_in(self.run_in_make_coffee, 30)
self.log('Waiting for power on.')
def run_in_make_coffee(self, args):
self.call_service("mqtt/publish", topic = 'home/coffee/coffee/coffee/set', payload = 'on')
@@ -13,14 +13,16 @@ import globals
# Initial Version
class CurtainSecurity(hass.Hass):
listen_event_handle_list = []
def initialize(self):
if not globals.check_args(self, ['curtain', 'door']):
return
self.listen_event(self.curtain_changed, event='call_service', entity_id = self.args['curtain'])
# self.listen_event_handle_list = []
# locks = globals.get_group_entities(self, "group.all_locks")
# for lock in locks:
# self.listen_event_handle_list.append(self.listen_state(self.lock_state_changed, lock))
self.listen_event_handle_list.append(self.listen_event(self.curtain_changed, event='call_service', entity_id = self.args['curtain']))
def terminate(self):
if self.listen_event_handle_list != None:
for listen_event_handle in self.listen_event_handle_list:
self.cancel_listen_event(listen_event_handle)
def curtain_changed(self, event_name, data, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
@@ -36,6 +36,14 @@ class SecuritySensorsReport(hass.Hass):
alarm_panel_state = self.get_state(self.args['ha_panel'])
if alarm_panel_state != "armed_away":
return
vacuum_state = "docked"
if "vacuum" in self.args:
vacuum_state = self.get_state(self.args["vacuum"])
#Если работает пылесос и сработал датчик движения
if vacuum_state == 'cleaning' and 'binary_sensor.motion' in entity:
self.log('Motion sensor is triggered, but vacuum is cleaning.')
return
self.alarm_triggered(entity, old, new)
def alarm_triggered(self, entity, old, new):
@@ -4,6 +4,7 @@ security_sensors:
sensor: group.security_sensors
constraint: input_boolean.security_sensors_report
ha_panel: alarm_control_panel.ha_alarm
vacuum: vacuum.xiaomi_vacuum_cleaner
notify: telegram
global_dependencies:
- globals
@@ -58,8 +58,8 @@ class MosEnergoSbytUpdater(Automation):
self.set_state(entity_id, state = balance, attributes = attributes)
class MosEnergoSbytCounter(Base):
_version = 11
_device = "ios/12.1.2/2.14.14(221)"
_version = 13
_device = "android/11.2.6/2.9.15(105)"
_session = None
_profile = None
@@ -132,6 +132,8 @@ class MosEnergoSbytCounter(Base):
url = "https://lkkbyt.mosenergosbyt.ru/gate/do?api_version={}&id_device={}&phone={}&ls={}&process=login&pw_abn={}&type=1".format(self._version, self._device, phone, ls, pass_md5)
response = requests.get(url).text
session_response = self._parse_response(response)
if ('ID_SESSION' not in session_response):
return
session = session_response['ID_SESSION']
return session
@@ -144,9 +146,19 @@ class MosEnergoSbytCounter(Base):
headers.append(node.text)
for node in et.findall('./b/p'):
values.append(node.text)
result = {}
if len(headers) == 0:
return result
for i in range(0, len(headers)):
if i>=len(headers) or i>=len(values):
continue
header = headers[i]
value = values[i]
result[header] = value
if 'NM_RESULT' in result and 'Вышла новая версия приложения' in result['NM_RESULT']:
self.error('[!] new version is available. Please update: {}.'.format(result))
return result
@@ -1,47 +1,47 @@
mosenergo_counter:
module: mosenergosbyt
class: MosEnergoSbytCounter
properties:
login: !secret mosenergo_main_login
pwd: !secret mosenergo_main_pwd
# mosenergo_counter:
# module: mosenergosbyt
# class: MosEnergoSbytCounter
# properties:
# login: !secret mosenergo_main_login
# pwd: !secret mosenergo_main_pwd
mosenergo_counter_updater:
module: mosenergosbyt
class: MosEnergoSbytUpdater
entity_ids:
mosenergosbyt_total: sensor.power_mosenergosbyt_total
mosenergosbyt_balance: sensor.power_mosenergosbyt_balance
app: mosenergo_counter
dependencies:
- mosenergo_counter
global_dependencies:
- globals
# mosenergo_counter_updater:
# module: mosenergosbyt
# class: MosEnergoSbytUpdater
# entity_ids:
# mosenergosbyt_total: sensor.power_mosenergosbyt_total
# mosenergosbyt_balance: sensor.power_mosenergosbyt_balance
# app: mosenergo_counter
# dependencies:
# - mosenergo_counter
# global_dependencies:
# - globals
mosenergo_counter_sender:
module: mosenergosbyt
class: MosEnergoSbytSender
entity_ids:
home_power_total: sensor.power_main_total
app: mosenergo_counter
dependencies:
- mosenergo_counter
global_dependencies:
- globals
# mosenergo_counter_sender:
# module: mosenergosbyt
# class: MosEnergoSbytSender
# entity_ids:
# home_power_total: sensor.power_main_total
# app: mosenergo_counter
# dependencies:
# - mosenergo_counter
# global_dependencies:
# - globals
mosenergo_alt:
module: mosenergosbyt
class: MosEnergoSbytCounter
properties:
login: !secret mosenergo_alt_login
pwd: !secret mosenergo_alt_pwd
# mosenergo_alt:
# module: mosenergosbyt
# class: MosEnergoSbytCounter
# properties:
# login: !secret mosenergo_alt_login
# pwd: !secret mosenergo_alt_pwd
mosenergo_alt_updater:
module: mosenergosbyt
class: MosEnergoSbytUpdater
app: mosenergo_alt
dependencies:
- automation
- mosenergo_alt
global_dependencies:
- globals
# mosenergo_alt_updater:
# module: mosenergosbyt
# class: MosEnergoSbytUpdater
# app: mosenergo_alt
# dependencies:
# - automation
# - mosenergo_alt
# global_dependencies:
# - globals
+62 -1
View File
@@ -12,6 +12,8 @@ class WaterCounterSender(Automation):
self.run_daily(self.send_mosru_counters, dt.time(12, 00, 0))
def send_mosru_counters(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
self.log('Starting sending counters value to mos.ru')
new_values = []
self.app.update_watercounters()
@@ -47,11 +49,38 @@ class WaterCounterSender(Automation):
self.log('error sending: {}'.format(err))
class PowerCounterSender(Automation):
timezone = '+03:00'
def initialize(self):
super().initialize()
if 'power_total' not in self.entity_ids:
self.log('Please specify power_total for PowerCounterSender.')
return
self.run_daily(self.send_mosru_counters, dt.time(14, 00, 0))
# self.send_mosru_counters("")
def send_mosru_counters(self, kwargs):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
return
today = datetime.today()
if today.day < 15 or today.day > 26:
self.log("We can send power values between days 15 and 26.")
return
power_total = round(float(self.get_state(self.entity_ids['power_total'])))
period = datetime.now().strftime('%Y-%m-%d')
self.log('Starting sending power counters value ({}) to mosru.'.format(power_total))
new_values = [{'counter_id': 'T1', 'indication': power_total, 'period': period}]
try:
result = self.app.send_electrocounters(new_values)
self.log(result)
except EmpServerException as err:
self.log('error sending: {}'.format(err))
class WaterCounterSensor(Automation):
timezone = '+03:00'
def initialize(self):
super().initialize()
self.timer = self.run_every(self.update_sensors, self.datetime(), 1*60*60)
self.timer = self.run_every(self.update_sensors, self.datetime()+timedelta(seconds=10), 1*60*60)
def update_sensors(self, args) -> None:
self.log('Updating water counters')
@@ -102,6 +131,33 @@ class WaterCounterSensor(Automation):
self.set_state(entity_id, state = indication_max, attributes = attributes)
class PowerCounterSensor(Automation):
timezone = '+03:00'
def initialize(self):
super().initialize()
self.timer = self.run_every(self.update_sensors, self.datetime()+timedelta(seconds=30), 1*60*60)
def update_sensors(self, args) -> None:
self.log('Updating power counters')
self.app.update_power()
if 'power_total' in self.entity_ids:
power_total = self.app.power_total
self.create_power_entity(self.entity_ids['power_total'], power_total, 'kWh', 'Счетчик')
if 'power_balance' in self.entity_ids:
power_balance = self.app.power_balance
self.create_power_entity(self.entity_ids['power_balance'], power_balance, '₽', 'Баланс')
def create_power_entity(self, entity_id, value, unit, description):
icon = "mdi:av-timer"
attributes = {}
attributes['unit_of_measurement'] = unit
attributes['icon'] = icon
attributes['friendly_name'] = "{}(mos.ru)".format(description)
attributes['description'] = 'Created and updated from appdaemon ({})'.format(__name__)
self.set_state(entity_id, state = value, attributes = attributes)
class EpdBalanceSensor(Automation):
def initialize(self):
super().initialize()
@@ -184,6 +240,11 @@ class MosruPower(MosruBase):
self._check_login()
self._power_counters = self._api.get_electrocounters(self._flat_id)
def send_electrocounters(self, new_values) -> None:
self._check_login()
self._api.send_electrocounters(self._flat_id, new_values)
class MosruWater(MosruBase):
_water_counters = None
@@ -25,6 +25,19 @@ epd_sensor:
global_dependencies:
- globals
power_sensor:
class: PowerCounterSensor
module: mosru
entity_ids:
power_balance: sensor.power_mosenergosbyt_balance
power_total: sensor.power_mosenergosbyt_total
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
water_sensor:
class: WaterCounterSensor
module: mosru
@@ -49,3 +62,14 @@ water_sender:
global_dependencies:
- globals
power_sender:
class: PowerCounterSender
module: mosru
entity_ids:
power_total: sensor.power_main_total
app: mosru_client
dependencies:
- mosru_client
global_dependencies:
- globals
+26
View File
@@ -0,0 +1,26 @@
import appdaemon.plugins.hass.hassapi as hass
from notification import send_notification
import datetime
# from automation import Automation # type: ignore
"""
Monitor events and output changes to the verbose_log. Nice for debugging purposes.
Arguments:
- events: List of events to monitor
"""
class Test(hass.Hass):
def initialize(self) -> None:
self.log('test')
# id = send_notification(
# self, targets="telegram_monster", message="Repeated message", title="title", when=datetime.datetime.now(), interval=10, iterations=2
# )
# self.log("id:{}".format(id))
# id = send_notification(
# self, targets="telegram_monster", message="Dummy", title="title"
# )
def terminate(self):
self.log('terminate')
# for listen_event_handle in self.listen_event_handle_list:
# self.cancel_listen_event(listen_event_handle)
+3
View File
@@ -0,0 +1,3 @@
test:
module: test
class: Test
@@ -7,14 +7,14 @@ led_timer:
global_dependencies:
- globals
led_window_timer:
module: entity_timer
class: EntityTimer
on_time: "09:00:00"
off_time: "23:00:00"
entity: switch.wifi_socket
global_dependencies:
- globals
# led_window_timer:
# module: entity_timer
# class: EntityTimer
# on_time: "09:00:00"
# off_time: "23:00:00"
# entity: switch.wifi_socket
# global_dependencies:
# - globals
xmas_tree_timer:
module: entity_timer
+4 -1
View File
@@ -46,10 +46,13 @@ services:
build:
context: ./appdaemon/docker
args:
VERSION: 3.0.2
VERSION: 3.0.5
container_name: appdaemon
depends_on:
- hass
privileged: true
devices:
- /dev/bus/usb:/dev/bus/usb
volumes:
- ./appdaemon/settings:/conf
- ./hass/settings/secrets.yaml:/conf/secrets.yaml:ro
+2
View File
@@ -9,6 +9,8 @@ filter:
include_entities:
- input_boolean.alice_night_mode
- input_boolean.alice_turn_off_light
- input_boolean.alice_turn_on_coffeemachine
- input_boolean.alice_make_coffee
exclude_entities:
- switch.plug_158d00020f3a29
@@ -6,3 +6,10 @@ alice_turn_off_light:
name: Выключи свет
initial: off
alice_turn_on_coffeemachine:
name: Включи кофемашину
initial: off
alice_make_coffee:
name: Сделай кофе
initial: off
@@ -0,0 +1,6 @@
# - platform: noolite
# channel: 0
# name: "salt_lamp"
# scan_interval: 60
# type: dimmer
# mode: noolite
@@ -1,10 +1,10 @@
# water_counter_set:
# sequence:
# - service: mqtt.publish
# data:
# topic: 'home/bath/bath/cold/set'
# payload: 127110
# - service: mqtt.publish
# data:
# topic: 'home/bath/bath/hot/set'
# payload: 67270
water_counter_set:
sequence:
- service: mqtt.publish
data:
topic: 'home/bath/bath/cold/set'
payload: 199820
- service: mqtt.publish
data:
topic: 'home/bath/bath/hot/set'
payload: 104171
Submodule hass/settings/custom_components/yandex_smart_home updated: d9ef78dfc5...faa54aed7f