Compare commits
10 Commits
6970bab14e
...
4ff1ac573f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ff1ac573f | ||
|
|
6fd891b0da | ||
|
|
28f2c4d094 | ||
|
|
ea5a9542e1 | ||
|
|
3781a36be6 | ||
|
|
92b100e0ce | ||
|
|
2c89a044d4 | ||
|
|
96e22ac46d | ||
|
|
e933e1c2b5 | ||
|
|
72b91c1c76 |
@@ -1,10 +0,0 @@
|
||||
FROM python:3.10
|
||||
|
||||
# install order matters because of some weird dependency stuff with websocket-client
|
||||
# install appdaemon first because it's versioning is more restrictive
|
||||
RUN pip install git+https://github.com/AppDaemon/appdaemon@dev
|
||||
|
||||
ENV CONF=/conf
|
||||
RUN mkdir $CONF
|
||||
COPY ./requirements.txt ${CONF}
|
||||
RUN --mount=type=cache,target=/root/.cache/pip pip install -r ${CONF}/requirements.txt
|
||||
@@ -1,49 +0,0 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
|
||||
{
|
||||
"name": "Appdaemon",
|
||||
"build": {
|
||||
// Sets the run context to one level up instead of the .devcontainer folder.
|
||||
"context": "..",
|
||||
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
|
||||
"dockerfile": "../.devcontainer/Dockerfile"
|
||||
},
|
||||
|
||||
"mounts": [
|
||||
"source=/etc/localtime,target=/etc/localtime,type=bind,consistency=cached",
|
||||
"source=/etc/timezone,target=/etc/timezone,type=bind,consistency=cached",
|
||||
"source=${localEnv:HOME}/.ssh,target=/root/.ssh,type=bind,consistency=cached"
|
||||
],
|
||||
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/conf,type=bind",
|
||||
"workspaceFolder": "/conf",
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/git:1": {},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Uncomment the next line to run commands after the container is created.
|
||||
// "postCreateCommand": "cat /etc/os-release",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-toolsai.jupyter",
|
||||
"mhutchie.git-graph",
|
||||
"ms-python.isort",
|
||||
"ms-python.autopep8"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "devcontainer"
|
||||
}
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -2,7 +2,3 @@
|
||||
path = apps/room_control
|
||||
url = ssh://gitea/john/room_control
|
||||
branch = main
|
||||
[submodule "docker-observation"]
|
||||
path = docker-observation
|
||||
url = ssh://gitea/john/docker-observation
|
||||
branch = main
|
||||
@@ -31,7 +31,7 @@ appdaemon:
|
||||
admin:
|
||||
api:
|
||||
http:
|
||||
url: http://127.0.0.1:5050
|
||||
url: http://0.0.0.0:5050
|
||||
|
||||
logs:
|
||||
main_log:
|
||||
|
||||
68
apps/examples/critical.py
Normal file
68
apps/examples/critical.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from appdaemon import Hass
|
||||
from appdaemon.entity import Entity
|
||||
from appdaemon.plugins.hass.notifications import AndroidNotification
|
||||
|
||||
|
||||
class CriticalAlert(Hass):
|
||||
msg: str
|
||||
alert_active: bool = False
|
||||
alert_handle: str = None
|
||||
context: str = None
|
||||
|
||||
def initialize(self):
|
||||
self.set_log_level('DEBUG')
|
||||
self.critical_sensor.listen_state(self.alert)
|
||||
self.listen_notification_action(self.clear_alert, 'clear')
|
||||
|
||||
@property
|
||||
def msg(self) -> str:
|
||||
return self.args['msg']
|
||||
|
||||
@property
|
||||
def device(self) -> str:
|
||||
return self.args['device']
|
||||
|
||||
@property
|
||||
def critical_sensor(self) -> Entity:
|
||||
return self.get_entity(self.args['sensor'])
|
||||
|
||||
@property
|
||||
def critical_notification(self) -> AndroidNotification:
|
||||
n = AndroidNotification(
|
||||
device=self.args['device'],
|
||||
message=self.msg,
|
||||
tag=self.name,
|
||||
)
|
||||
n.color = 'red'
|
||||
n.icon = 'fire-alert'
|
||||
n.add_action('clear', 'Clear Alert')
|
||||
return n
|
||||
|
||||
def alert(self, entity: str, attribute: str, old: str, new: str, **kwargs):
|
||||
self.alert_active = new == 'on'
|
||||
|
||||
if self.alert_active:
|
||||
self.alert_handle = self.run_every(
|
||||
self.repeat_alert,
|
||||
start='now', interval=2.0
|
||||
)
|
||||
self.log(f'Alert Handle: {self.alert_handle}', level='DEBUG')
|
||||
else:
|
||||
if self.alert_handle:
|
||||
self.clear_alert()
|
||||
|
||||
def repeat_alert(self, **kwargs):
|
||||
if self.alert_active:
|
||||
self.android_tts(
|
||||
device=self.device,
|
||||
tts_text=self.msg,
|
||||
critical=True
|
||||
)
|
||||
self.call_service(**self.critical_notification.to_service_call())
|
||||
|
||||
def clear_alert(self, event_name: str = None, data: dict = None, **cb_args: dict):
|
||||
self.log(event_name, level='DEBUG')
|
||||
if self.alert_active:
|
||||
self.alert_active = False
|
||||
self.cancel_timer(self.alert_handle)
|
||||
self.alert_handle = None
|
||||
5
apps/examples/weather/weather.yaml
Normal file
5
apps/examples/weather/weather.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
weather:
|
||||
module: weather
|
||||
class: Weather
|
||||
# log_level: DEBUG
|
||||
location: 78704 US
|
||||
Submodule apps/room_control updated: 2180e544c0...e3186f1b5e
@@ -18,7 +18,7 @@ class SceneDetector(Hass):
|
||||
)
|
||||
self.log(f"Waiting for scene '{self.scene_entity.friendly_name}' to activate")
|
||||
|
||||
async def event_callback(self, event_name, data, cb_args):
|
||||
async def event_callback(self, event_name, data, **kwargs):
|
||||
entity_id = data['service_data']['entity_id']
|
||||
if entity_id == self.scene_entity.entity_id:
|
||||
await self.scene_detected()
|
||||
|
||||
@@ -1,14 +1,45 @@
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
from appdaemon.entity import Entity
|
||||
from appdaemon.plugins.hass.hassapi import Hass
|
||||
from appdaemon.plugins.mqtt.mqttapi import Mqtt
|
||||
from appdaemon.adbase import ADBase
|
||||
|
||||
|
||||
class SleepTV(ADBase):
|
||||
handle: str = None
|
||||
|
||||
def initialize(self):
|
||||
self.adapi = self.get_ad_api()
|
||||
self.adapi.set_log_level('DEBUG')
|
||||
self.sleep_time.listen_state(self.handle_sleep_time_change)
|
||||
|
||||
@property
|
||||
def sleep_time(self) -> Entity:
|
||||
return self.adapi.get_entity(self.args['sleep_time'])
|
||||
|
||||
@property
|
||||
def tv(self) -> Entity:
|
||||
return self.adapi.get_entity(self.args['tv'])
|
||||
|
||||
def handle_sleep_time_change(self, entity: str, attribute: str, old: str, new: str, **kwargs):
|
||||
now: datetime = self.adapi.get_now()
|
||||
dt = datetime.strptime(new, '%H:%M:%S')
|
||||
dt = datetime.combine(now.date(), dt.time())
|
||||
|
||||
if dt.time() < now.time():
|
||||
dt += timedelta(days=1)
|
||||
|
||||
self.adapi.cancel_timer(self.handle, silent=True)
|
||||
self.handle = self.adapi.run_at(lambda **kwargs: self.tv.turn_off(), dt)
|
||||
self.adapi.log(f'Turning TV off: {dt.strftime("%a %I:%M:%S %p")}')
|
||||
|
||||
|
||||
class SleepSetter(Hass, Mqtt):
|
||||
def initialize(self):
|
||||
assert self.entity_exists(entity_id=self.variable), f'{self.variable} does not exist'
|
||||
self.listen_state(callback=self.handle_state, entity_id=self.variable)
|
||||
self.variable_entity.listen_state(self.handle_state)
|
||||
self.setup_buttons()
|
||||
|
||||
def setup_buttons(self):
|
||||
@@ -20,7 +51,7 @@ class SleepSetter(Hass, Mqtt):
|
||||
|
||||
def setup_button(self, name: str):
|
||||
topic = f'zigbee2mqtt/{name}'
|
||||
self.mqtt_subscribe(topic, namespace='mqtt')
|
||||
# self.mqtt_subscribe(topic, namespace='mqtt')
|
||||
self.listen_event(
|
||||
self.handle_button,
|
||||
'MQTT_MESSAGE',
|
||||
|
||||
@@ -9,6 +9,7 @@ sleep:
|
||||
- Bedroom Button 2
|
||||
- Living Room Button
|
||||
- Bathroom Button
|
||||
|
||||
off_apps:
|
||||
# - bedroom
|
||||
- living_room
|
||||
@@ -21,3 +22,9 @@ sleep:
|
||||
on_apps:
|
||||
- living_room
|
||||
- bedroom
|
||||
|
||||
sleep_tv:
|
||||
module: sleep
|
||||
class: SleepTV
|
||||
sleep_time: input_datetime.tv_sleep_time
|
||||
tv: media_player.bedroom_vizio
|
||||
|
||||
64
apps/traffic/traffic.py
Normal file
64
apps/traffic/traffic.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from appdaemon import ADAPI
|
||||
from appdaemon.adbase import ADBase
|
||||
from appdaemon.entity import Entity
|
||||
from appdaemon.models.notification import AndroidData
|
||||
|
||||
|
||||
class TrafficAlert(ADAPI):
|
||||
notified: bool = False
|
||||
|
||||
def initialize(self):
|
||||
self.log(f'Traffic time: {self.traffic_time}')
|
||||
self.traffic_entity.listen_state(
|
||||
self.handle_state_change,
|
||||
attribute='duration',
|
||||
# constrain_state=lambda d: d > 30.0
|
||||
)
|
||||
self.handle_state_change(new=self.traffic_time.total_seconds() / 60)
|
||||
|
||||
@property
|
||||
def traffic_entity(self) -> Entity:
|
||||
return self.get_entity('sensor.work_to_home')
|
||||
|
||||
@property
|
||||
def traffic_time(self) -> timedelta:
|
||||
return timedelta(minutes=float(self.traffic_entity.get_state('duration')))
|
||||
|
||||
def notify_android(self, device: str, **data):
|
||||
model = AndroidData.model_validate(data)
|
||||
res = self.call_service(
|
||||
f'notify/mobile_app_{device}', **model.model_dump())
|
||||
return res
|
||||
|
||||
def handle_state_change(self,
|
||||
entity: str = None,
|
||||
attribute: str = None,
|
||||
old: str = None,
|
||||
new: str = None,
|
||||
**kwargs: dict):
|
||||
self.log(f'Travel time changed: {new}')
|
||||
|
||||
if not self.notified:
|
||||
actions = [
|
||||
{
|
||||
'action': 'URI',
|
||||
'title': 'See travel times',
|
||||
'uri': "https://grafana.john-stream.com/d/f4ff212e-c786-40eb-b615-205121f482e3/travel-time-details?orgId=1&from=1727927004390&to=1728013404390"
|
||||
}
|
||||
]
|
||||
|
||||
if new > 40.0:
|
||||
self.notify_android('pixel_5', **{
|
||||
'title': 'Heavy Traffic',
|
||||
'message': 'Get moving!',
|
||||
'data': {'tag': 'traffic', 'color': 'red', 'actions': actions},
|
||||
})
|
||||
elif new > 30.0:
|
||||
self.notify_android('pixel_5', **{
|
||||
'title': 'Increaing Traffic',
|
||||
'message': 'Something needs to happen',
|
||||
'data': {'tag': 'traffic', 'color': 'yellow', 'actions': actions},
|
||||
})
|
||||
self.notified = True
|
||||
3
apps/traffic/traffic.yaml
Normal file
3
apps/traffic/traffic.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
traffic:
|
||||
module: traffic
|
||||
class: TrafficAlert
|
||||
Submodule docker-observation deleted from 1ee44b7b80
Reference in New Issue
Block a user