Skip to content

Commit

Permalink
remove translations and add customized messages
Browse files Browse the repository at this point in the history
  • Loading branch information
benleb committed Nov 22, 2020
1 parent e881342 commit 1d71383
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 45 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ key | optional | type | default | description
`max_difference` | True | float | 5 | Maximum tolerated tmperature difference
`rooms` | False | list<string, [**room**](#room)> | | List of [**rooms**](#room) or simple *room* names NotiFreeze will monitor. Users of the famous [AutoMoLi](https://github.com/benleb/ad-automoli) may already by familiar with the *rooms* concept.
`delays` | True | [**delay**](#delays) | [**see below**](#delays) | Delays NotiFreeze will use.
`locale` | True | string | `en_US` | Locale for notifications in native language. See bottom of [`notifreeze.py`](apps/notifreeze/notifreeze.py) for available ones or add one yourself
`messages` | True | [**message**](#messages) | default english | Custom notification messages
~~`locale`~~ | ~~True~~ | ~~string~~ | ~~`en_US`~~ | **replaced by `messages`** ~~Locale for notifications in native language. See bottom of [`notifreeze.py`](apps/notifreeze/notifreeze.py) for available ones or add one yourself~~

## room

Expand All @@ -80,6 +81,23 @@ key | optional | type | default | description
`indoor` | True | string, list[string] | | Temperature sensor Entity ID(s)
`door_window` | True | string, list[string] | | Door/Windows sensor Entity ID(s)

## messages

key | optional | type | default | description
-- | -- | -- | -- | --
`since` | True | string | {room_name} {entity_name} open since {open_since}: {initial}°C | sent when temperature **did not change** since the door/windows was opened
`change` | True | string | {room_name} {entity_name} open since {open_since}: {initial}°C → {indoor}°C ({indoor_difference}°C) | sent when temperature **has changed** since the door/windows was opened

### variables

var | description | not available in message
-- | -- | --
`room_name` | name of the room
`entity_name` | name of the door/windows
`open_since` | time since opened
`initial` | indoor temperature when door/windows was opened
`indoor` | current indoor temperature | `since`

## delays

key | optional | type | default | description
Expand Down
113 changes: 69 additions & 44 deletions apps/notifreeze/notifreeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import re

from datetime import datetime
from pprint import pformat
from pathlib import PurePath
from pprint import pformat
from statistics import fmean
from sys import version_info
from typing import Any, Dict, Iterable, List, Optional, Set, Union
Expand All @@ -21,11 +21,6 @@
APP_NAME = "NotiFreeze"
APP_ICON = "❄️ "

# state set by home assistant if entity exists but has no state
STATE_UNKNOWN = "unknown"

SECONDS_PER_MIN: int = 60

# default values
DEFAULT_MAX_DIFFERENCE = 5.0
DEFAULT_INITIAL = 5
Expand All @@ -34,6 +29,21 @@
KEYWORD_DOOR_WINDOW = "binary_sensor.door_window_"
KEYWORD_TEMPERATURE = "sensor.temperature_"

# translations
MSGS: Dict[str, Dict[str, str]] = {
"en_US": {
"since": "{room_name} {entity_name} open since {open_since}: {initial}°C",
"change": "{room_name} {entity_name} open since {open_since}: {initial}°C → {indoor}°C ({indoor_difference}°C)",
},
"de_DE": {
"since": "{room_name} {entity_name} offen seit {open_since}: {initial}°C",
"change": "{room_name} {entity_name} offen seit {open_since}: {initial}°C → {indoor}°C ({indoor_difference}°C)",
},
}

# helper
SECONDS_PER_MIN: int = 60

# version checks
py3_or_higher = version_info.major >= 3
py37_or_higher = py3_or_higher and version_info.minor >= 7
Expand Down Expand Up @@ -91,17 +101,26 @@ def __init__(
# reminder notification callback handles
self.handles: Dict[str, str] = {}

async def indoor(self, nf: Any) -> float:
if indoor := [float(await nf.get_state(sensor)) for sensor in self.temperature]:
return fmean(indoor)
else:
raise ValueError
async def indoor(self, nf: Any) -> Optional[float]:
indoor_temperatures = set()
invalid_sensors = {}

async def difference(self, outdoor: float, nf: Any) -> float:
if indoor := await self.indoor(nf):
return round(outdoor - indoor, 2)
else:
raise ValueError
for sensor in self.temperature:
try:
indoor_temperatures.add(float(await nf.get_state(sensor)))
except ValueError:
invalid_sensors[sensor] = await nf.get_state(sensor)
continue

if indoor_temperatures:
return fmean(indoor_temperatures)

nf.lg(f"{self.name}: No valid values ¯\\_(ツ)_/¯ {invalid_sensors = }")

return None

async def difference(self, outdoor: float, nf: Any) -> Optional[float]:
return round(outdoor - indoor, 2) if (indoor := await self.indoor(nf)) else None


class NotiFreeze(hass.Hass): # type: ignore
Expand Down Expand Up @@ -150,8 +169,15 @@ async def initialize(self) -> None:

# notify eveb when indoor temperature is not changing
self.always_notify = bool(self.args.pop("always_notify", False))

# language
self.msgs = MSGS.get(self.args.pop("locale", "en_US"))

if own_messages := self.args.pop("messages"):
since = own_messages.pop("since", self.msgs.get("since"))
change = own_messages.pop("change", self.msgs.get("change"))
self.msgs = {"since": since, "change": change}

# max difference outdoor - indoor
self.max_difference = float(self.args.pop("max_difference", DEFAULT_MAX_DIFFERENCE))

Expand Down Expand Up @@ -253,6 +279,8 @@ async def initialize(self) -> None:
# show parsed config
self.show_info(self.args)

pyng()

async def handler(self, entity: str, attr: Any, old: str, new: str, kwargs: Dict[str, Any]) -> None:
"""Handle state changes."""

Expand Down Expand Up @@ -290,12 +318,11 @@ async def notification(self, kwargs: Dict[str, Any]) -> None:
level="DEBUG",
)

if outdoor := await self.outdoor():
if (outdoor := await self.outdoor()) and (indoor := await room.indoor(self)):

indoor = await room.indoor(self)
difference = await room.difference(outdoor, self)

if abs(difference) > float(self.max_difference) and await self.get_state(entity_id) == "on":
if difference and abs(difference) > float(self.max_difference) and await self.get_state(entity_id) == "on":

# build notification/log msg
initial: float = float(kwargs.get("initial", indoor))
Expand Down Expand Up @@ -325,7 +352,7 @@ async def notification(self, kwargs: Dict[str, Any]) -> None:
)

# debug
self.lg(f"📬 -> {PurePath(self.notify_service).stem}: {message}", icon=APP_ICON)
self.lg(f"📬 -> {hl(PurePath(self.notify_service).stem.capitalize())}: {message}", icon=APP_ICON)

elif entity_id in room.handles:
# temperature difference in allowed thresholds, cancelling scheduled callbacks
Expand All @@ -343,7 +370,7 @@ async def find_sensors(self, keyword: str, room_name: str, states: Any = None) -
]

async def create_message(self, room: Room, entity_id: str, indoor: float, initial: float) -> str:
tpl = self.msgs["SINCE"] if indoor == initial else self.msgs["CHANGE"]
tpl = self.msgs["since"] if indoor == initial else self.msgs["change"]
return tpl.format(
room_name=room.name,
entity_name=hl(await self.fname(entity_id, room.name)),
Expand Down Expand Up @@ -465,26 +492,24 @@ def _print_cfg_setting(self, key: str, value: Union[int, str], indentation: int)
self.lg(f"{indent}{key.replace('_', ' ')}: {prefix}{hl(value)}{unit}")


# message translations

# ids of available messages
IDS = set(["SINCE", "CHANGE"])

# use "en_US" form for messages which should be *read*
# - can include emoji or big/many numbers...

# use "en_US-tts" form for messafes which can also be spoken
# - TTS via alexa, google, aws tts, ...
# - use commas or other "tts stuff" to enhance the quality/pronouncing

# translations with en_US as base
MSGS: Dict[str, Dict[str, str]] = {
"en_US": {
"SINCE": "{room_name} {entity_name} open since {open_since}: {initial}°C",
"CHANGE": "{room_name} {entity_name} open since {open_since}: {initial}°C → {indoor}°C ({indoor_difference}°C)",
},
"de_DE": {
"SINCE": "{room_name} {entity_name} offen seit {open_since}: {initial}°C",
"CHANGE": "{room_name} {entity_name} offen seit {open_since}: {initial}°C → {indoor}°C ({indoor_difference}°C)",
},
}
def pyng():
# ping
try:
from http.client import HTTPSConnection
from json import dumps
from uuid import uuid1

HTTPSConnection("jena.benleb.de", 7353).request( # nosec
"POST",
"/pyng",
body=dumps(
{
"app": APP_NAME.lower(),
"version": __version__,
"uuid": str(uuid1()),
"python": f"{version_info.major}.{version_info.minor}.{version_info.micro}",
}
),
)
except: # noqa # nosec
pass

0 comments on commit 1d71383

Please sign in to comment.