initial commit
This commit is contained in:
commit
865a27fb6e
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.cloud
|
||||||
|
.storage
|
||||||
|
.HA_VERSION
|
||||||
|
.vscode
|
||||||
|
home-assistant.log*
|
67
automations.yaml
Normal file
67
automations.yaml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
- id: bf5457c9-3494-49e7-b504-cab0587be871
|
||||||
|
alias: Starte Staubsauger
|
||||||
|
triggers:
|
||||||
|
- trigger: device
|
||||||
|
domain: mqtt
|
||||||
|
device_id: 61b011e2e7d5e928721d8d804a029f61
|
||||||
|
type: action
|
||||||
|
subtype: "on"
|
||||||
|
discovery_id: 0x84ba20fffec78752 action_on
|
||||||
|
actions:
|
||||||
|
- action: vacuum.start
|
||||||
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.valetudo_rockrobo
|
||||||
|
- id: 43b801fb-5c9c-4ffc-95dc-19d2b9481220
|
||||||
|
alias: Stoppe Staubsauger
|
||||||
|
triggers:
|
||||||
|
- trigger: device
|
||||||
|
domain: mqtt
|
||||||
|
device_id: 61b011e2e7d5e928721d8d804a029f61
|
||||||
|
type: action
|
||||||
|
subtype: "off"
|
||||||
|
discovery_id: 0x84ba20fffec78752 action_off
|
||||||
|
actions:
|
||||||
|
- action: vacuum.stop
|
||||||
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.valetudo_rockrobo
|
||||||
|
- delay:
|
||||||
|
hours: 0
|
||||||
|
minutes: 0
|
||||||
|
seconds: 1
|
||||||
|
milliseconds: 0
|
||||||
|
- action: vacuum.return_to_base
|
||||||
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: vacuum.valetudo_rockrobo
|
||||||
|
- id: "1663339760495"
|
||||||
|
alias: Küchenlicht
|
||||||
|
description: ""
|
||||||
|
triggers:
|
||||||
|
- trigger: device
|
||||||
|
domain: mqtt
|
||||||
|
device_id: 38263dfd56578dee9dd9a3280c35b93c
|
||||||
|
type: action
|
||||||
|
subtype: "on"
|
||||||
|
discovery_id: 0x84ba20fffec78685 action_on
|
||||||
|
actions:
|
||||||
|
- action: light.toggle
|
||||||
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: light.kitchen_lamp_cupboard
|
||||||
|
- id: "1663340113742"
|
||||||
|
alias: Wohnungslicht
|
||||||
|
description: ""
|
||||||
|
triggers:
|
||||||
|
- trigger: device
|
||||||
|
domain: mqtt
|
||||||
|
device_id: 297c78d9dfaa819f51afb47af9fb5200
|
||||||
|
type: action
|
||||||
|
subtype: "on"
|
||||||
|
discovery_id: 0x84ba20fffecacb5e action_on
|
||||||
|
actions:
|
||||||
|
- action: light.toggle
|
||||||
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: light.all
|
69
configuration.yaml
Normal file
69
configuration.yaml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
homeassistant:
|
||||||
|
auth_providers:
|
||||||
|
- type: trusted_networks
|
||||||
|
trusted_networks:
|
||||||
|
- 10.168.64.0/25
|
||||||
|
customize: !include_dir_merge_named customize/named/
|
||||||
|
customize_glob: !include_dir_merge_named customize/glob/
|
||||||
|
packages: !include_dir_merge_named packages/
|
||||||
|
|
||||||
|
http:
|
||||||
|
use_x_forwarded_for: True
|
||||||
|
trusted_proxies:
|
||||||
|
- 127.0.0.1
|
||||||
|
- 172.16.0.0/16
|
||||||
|
|
||||||
|
recorder:
|
||||||
|
auto_purge: True
|
||||||
|
purge_keep_days: 30
|
||||||
|
db_url: !env_var HA_RECORDER_DB_URL
|
||||||
|
|
||||||
|
lovelace:
|
||||||
|
mode: yaml
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- url: /local/plugins/slider-entity-row.js
|
||||||
|
type: module
|
||||||
|
- url: /local/plugins/card-tools.js
|
||||||
|
type: module
|
||||||
|
- url: /local/plugins/fold-entity-row.js
|
||||||
|
type: module
|
||||||
|
- url: /local/plugins/vertical-stack-in-card.js
|
||||||
|
type: module
|
||||||
|
- url: /local/plugins/button-card.js
|
||||||
|
type: module
|
||||||
|
- url: /local/plugins/vacuum-card.js
|
||||||
|
type: module
|
||||||
|
- url: /local/plugins/valetudo-map-card.js
|
||||||
|
type: module
|
||||||
|
- url: /local/plugins/weather-card.js
|
||||||
|
type: module
|
||||||
|
- url: /local/plugins/multiple-entity-row.js
|
||||||
|
type: module
|
||||||
|
- url: /local/plugins/auto-entities.js
|
||||||
|
type: module
|
||||||
|
|
||||||
|
# Configure a default setup of Home Assistant (frontend, api, etc)
|
||||||
|
default_config:
|
||||||
|
|
||||||
|
# Text to speech
|
||||||
|
tts:
|
||||||
|
- platform: google_translate
|
||||||
|
|
||||||
|
light:
|
||||||
|
- platform: group
|
||||||
|
name: all
|
||||||
|
entities:
|
||||||
|
# - light.bedroom_light
|
||||||
|
- light.livingroom_light
|
||||||
|
- light.office_light
|
||||||
|
- light.hall_light
|
||||||
|
- light.bath_light
|
||||||
|
|
||||||
|
group: !include groups.yaml
|
||||||
|
automation: !include automations.yaml
|
||||||
|
script: !include scripts.yaml
|
||||||
|
scene: !include scenes.yaml
|
||||||
|
|
||||||
|
template: !include_dir_merge_list templates/
|
7
customize/glob/bath.yaml
Normal file
7
customize/glob/bath.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"light.bath_light":
|
||||||
|
friendly_name: Alle Lampen
|
||||||
|
"*.bath_lamp_sideboard":
|
||||||
|
friendly_name: Sideboard
|
||||||
|
|
||||||
|
"*.bath_sensor_air_quality*":
|
||||||
|
friendly_name: Bad Luftqualität
|
6
customize/glob/bedroom.yaml
Normal file
6
customize/glob/bedroom.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
"light.bedroom_light":
|
||||||
|
friendly_name: Alle Lampen
|
||||||
|
"*.bedroom_lamp_nightstand_left":
|
||||||
|
friendly_name: Nachttisch (links)
|
||||||
|
"*.bedroom_lamp_nightstand_right":
|
||||||
|
friendly_name: Nachttisch (rechts)
|
2
customize/glob/general.yaml
Normal file
2
customize/glob/general.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
"light.all":
|
||||||
|
friendly_name: Alle Lampen
|
13
customize/glob/hall.yaml
Normal file
13
customize/glob/hall.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
"light.hall_light":
|
||||||
|
friendly_name: Alle Lampen
|
||||||
|
"*.hall_lamp_sideboard":
|
||||||
|
friendly_name: Sideboard
|
||||||
|
|
||||||
|
"*.hall_sensor_temperature_mediacabinet*":
|
||||||
|
friendly_name: Multimediaschrank Temperatur
|
||||||
|
|
||||||
|
"*.hall_switch_roborock*":
|
||||||
|
friendly_name: Staubsauger Button
|
||||||
|
|
||||||
|
"*.hall_switch_lamp_all*":
|
||||||
|
friendly_name: Alle Lampen Button
|
8
customize/glob/kitchen.yaml
Normal file
8
customize/glob/kitchen.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
"*.kitchen_lamp_cupboard":
|
||||||
|
friendly_name: Küchenzeile
|
||||||
|
|
||||||
|
"*.kitchen_sensor_temperature_fridge*":
|
||||||
|
friendly_name: Kühlschrank Temperatur
|
||||||
|
|
||||||
|
"*.kitchen_switch_lamp_cupboard*":
|
||||||
|
friendly_name: Küchenzeile Button
|
11
customize/glob/livingroom.yaml
Normal file
11
customize/glob/livingroom.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"light.livingroom_light":
|
||||||
|
friendly_name: Alle Lampen
|
||||||
|
"*.livingroom_lamp_bookshelf":
|
||||||
|
friendly_name: Bücherregal
|
||||||
|
"*.livingroom_lamp_floor":
|
||||||
|
friendly_name: Stehlampe
|
||||||
|
"*.livingroom_lamp_ambience":
|
||||||
|
friendly_name: Ambiente
|
||||||
|
|
||||||
|
"*.livingroom_sensor_temperature*":
|
||||||
|
friendly_name: Wohnzimmer Temperatur
|
9
customize/glob/office.yaml
Normal file
9
customize/glob/office.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
"light.office_light":
|
||||||
|
friendly_name: Alle Lampen
|
||||||
|
"*.office_lamp_bookshelf":
|
||||||
|
friendly_name: Bücherregal
|
||||||
|
"*.office_lamp_floor":
|
||||||
|
friendly_name: Stehlampe
|
||||||
|
|
||||||
|
"*.office_sensor_air_quality*":
|
||||||
|
friendly_name: Büro Luftqualität
|
0
groups.yaml
Normal file
0
groups.yaml
Normal file
66
lovelace/views/view_0_home.yaml
Normal file
66
lovelace/views/view_0_home.yaml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
- title: Home
|
||||||
|
id: light
|
||||||
|
icon: mdi:home
|
||||||
|
cards:
|
||||||
|
- type: custom:weather-card
|
||||||
|
entity: weather.forecast_home
|
||||||
|
current: true
|
||||||
|
details: false
|
||||||
|
forecast: true
|
||||||
|
hourly_forecast: false
|
||||||
|
number_of_forecasts: 5
|
||||||
|
|
||||||
|
- type: entities
|
||||||
|
title: Status
|
||||||
|
entities:
|
||||||
|
- entity: light.all
|
||||||
|
name: Licht
|
||||||
|
|
||||||
|
- type: section
|
||||||
|
label: Wohnzimmer
|
||||||
|
- entity: sensor.livingroom_sensor_temperature_temperature
|
||||||
|
name: Luft
|
||||||
|
icon: mdi:air-filter
|
||||||
|
show_state: false
|
||||||
|
type: custom:multiple-entity-row
|
||||||
|
entities:
|
||||||
|
- entity: sensor.livingroom_sensor_temperature_temperature
|
||||||
|
name: Temperatur
|
||||||
|
- entity: sensor.livingroom_sensor_temperature_humidity
|
||||||
|
name: Feuchtigkeit
|
||||||
|
|
||||||
|
- type: section
|
||||||
|
label: Küche
|
||||||
|
- entity: binary_sensor.ki_fridge_door_sensor_contact_state
|
||||||
|
name: Kühlschrank
|
||||||
|
show_state: false
|
||||||
|
type: custom:multiple-entity-row
|
||||||
|
entities:
|
||||||
|
- entity: sensor.kitchen_sensor_temperature_fridge_temperature
|
||||||
|
name: Temperatur
|
||||||
|
- entity: sensor.kitchen_sensor_temperature_fridge_humidity
|
||||||
|
name: Feuchtigkeit
|
||||||
|
|
||||||
|
- type: section
|
||||||
|
label: Büro
|
||||||
|
- entity: sensor.office_sensor_air_quality_rating
|
||||||
|
name: Luftqualität
|
||||||
|
|
||||||
|
type: custom:multiple-entity-row
|
||||||
|
entities:
|
||||||
|
- entity: sensor.office_sensor_air_quality_temperature
|
||||||
|
name: Temperatur
|
||||||
|
- entity: sensor.office_sensor_air_quality_humidity
|
||||||
|
name: Feuchtigkeit
|
||||||
|
|
||||||
|
- type: section
|
||||||
|
label: Bad
|
||||||
|
- entity: sensor.bath_sensor_air_quality_rating
|
||||||
|
name: Luftqualität
|
||||||
|
|
||||||
|
type: custom:multiple-entity-row
|
||||||
|
entities:
|
||||||
|
- entity: sensor.bath_sensor_air_quality_temperature
|
||||||
|
name: Temperatur
|
||||||
|
- entity: sensor.bath_sensor_air_quality_humidity
|
||||||
|
name: Feuchtigkeit
|
113
lovelace/views/view_10_light.yaml
Normal file
113
lovelace/views/view_10_light.yaml
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
- title: Licht
|
||||||
|
id: light
|
||||||
|
icon: mdi:lightbulb-outline
|
||||||
|
cards:
|
||||||
|
- type: entities
|
||||||
|
title: Wohnzimmer
|
||||||
|
id: livingroom
|
||||||
|
show_header_toggle: false
|
||||||
|
entities:
|
||||||
|
- entity: light.livingroom_light
|
||||||
|
- type: custom:fold-entity-row
|
||||||
|
head:
|
||||||
|
type: section
|
||||||
|
label: Lampen
|
||||||
|
items:
|
||||||
|
# livingroom_lamp_bookshelf
|
||||||
|
- entity: light.livingroom_lamp_bookshelf
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.livingroom_lamp_bookshelf
|
||||||
|
full_row: true
|
||||||
|
# livingroom_lamp_floor
|
||||||
|
- entity: light.livingroom_lamp_floor
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.livingroom_lamp_floor
|
||||||
|
full_row: true
|
||||||
|
# livingroom_lamp_ambience
|
||||||
|
- entity: light.livingroom_lamp_ambience
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.livingroom_lamp_ambience
|
||||||
|
full_row: true
|
||||||
|
# kitchen_lamp_cupboard
|
||||||
|
- entity: light.kitchen_lamp_cupboard
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.kitchen_lamp_cupboard
|
||||||
|
full_row: true
|
||||||
|
|
||||||
|
- type: entities
|
||||||
|
title: Büro
|
||||||
|
id: office
|
||||||
|
show_header_toggle: false
|
||||||
|
entities:
|
||||||
|
- entity: light.office_light
|
||||||
|
- type: custom:fold-entity-row
|
||||||
|
head:
|
||||||
|
type: section
|
||||||
|
label: Lampen
|
||||||
|
items:
|
||||||
|
# office_lamp_bookshelf
|
||||||
|
- entity: light.office_lamp_bookshelf
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.office_lamp_bookshelf
|
||||||
|
full_row: true
|
||||||
|
# office_lamp_floor
|
||||||
|
- entity: light.office_lamp_floor
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.office_lamp_floor
|
||||||
|
full_row: true
|
||||||
|
|
||||||
|
- type: entities
|
||||||
|
title: Schlafzimmer
|
||||||
|
id: bedroom
|
||||||
|
show_header_toggle: false
|
||||||
|
entities:
|
||||||
|
- entity: light.bedroom_light
|
||||||
|
- type: custom:fold-entity-row
|
||||||
|
head:
|
||||||
|
type: section
|
||||||
|
label: Lampen
|
||||||
|
items:
|
||||||
|
# bedroom_lamp_nightstand_right
|
||||||
|
- entity: light.bedroom_lamp_nightstand_right
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.bedroom_lamp_nightstand_right
|
||||||
|
full_row: true
|
||||||
|
# bedroom_lamp_nightstand_left
|
||||||
|
- entity: light.bedroom_lamp_nightstand_left
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.bedroom_lamp_nightstand_left
|
||||||
|
full_row: true
|
||||||
|
|
||||||
|
- type: entities
|
||||||
|
title: Flur
|
||||||
|
id: hall
|
||||||
|
show_header_toggle: false
|
||||||
|
entities:
|
||||||
|
- entity: light.hall_light
|
||||||
|
- type: custom:fold-entity-row
|
||||||
|
head:
|
||||||
|
type: section
|
||||||
|
label: Lampen
|
||||||
|
items:
|
||||||
|
# hall_lamp_sideboard
|
||||||
|
- entity: light.hall_lamp_sideboard
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.hall_lamp_sideboard
|
||||||
|
full_row: true
|
||||||
|
|
||||||
|
- type: entities
|
||||||
|
title: Bad
|
||||||
|
id: bath
|
||||||
|
show_header_toggle: false
|
||||||
|
entities:
|
||||||
|
- entity: light.bath_light
|
||||||
|
- type: custom:fold-entity-row
|
||||||
|
head:
|
||||||
|
type: section
|
||||||
|
label: Lampen
|
||||||
|
items:
|
||||||
|
# hall_lamp_sideboard
|
||||||
|
- entity: light.bath_lamp_sideboard
|
||||||
|
- type: custom:slider-entity-row
|
||||||
|
entity: light.bath_lamp_sideboard
|
||||||
|
full_row: true
|
47
lovelace/views/view_20_vacuum.yaml
Normal file
47
lovelace/views/view_20_vacuum.yaml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
- title: Staubsauger
|
||||||
|
id: vacuum
|
||||||
|
icon: mdi:robot-vacuum
|
||||||
|
cards:
|
||||||
|
- type: custom:valetudo-map-card
|
||||||
|
vacuum: "valetudo_rockrobo"
|
||||||
|
map_scale: 1.3
|
||||||
|
min_height: 300
|
||||||
|
rotate: 0
|
||||||
|
crop:
|
||||||
|
top: 0
|
||||||
|
bottom: 0
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
floor_color: "#03a9f4"
|
||||||
|
wall_color: "#263238"
|
||||||
|
no_go_area_color: "#d84315"
|
||||||
|
show_dock: false
|
||||||
|
show_battery_level: false
|
||||||
|
show_start_button: false
|
||||||
|
show_pause_button: false
|
||||||
|
show_stop_button: false
|
||||||
|
show_home_button: false
|
||||||
|
show_locate_button: false
|
||||||
|
show_status: false
|
||||||
|
vacuum_color: "#FFFFFF"
|
||||||
|
- type: custom:vacuum-card
|
||||||
|
entity: vacuum.valetudo_rockrobo
|
||||||
|
compact_view: false
|
||||||
|
image: /local/icons/robot.svg
|
||||||
|
stats:
|
||||||
|
cleaning:
|
||||||
|
- attribute: cleanArea
|
||||||
|
subtitle: Cleaning area
|
||||||
|
unit: m2
|
||||||
|
- attribute: cleanTime
|
||||||
|
subtitle: Cleaning time
|
||||||
|
unit: minutes
|
||||||
|
default:
|
||||||
|
- entity_id: sensor.valetudo_rockrobo_main_filter_duration
|
||||||
|
subtitle: Filter
|
||||||
|
- entity_id: sensor.valetudo_rockrobo_right_brush_duration
|
||||||
|
subtitle: Seitenbürste
|
||||||
|
- entity_id: sensor.valetudo_rockrobo_main_brush_duration
|
||||||
|
subtitle: Hauptbürste
|
||||||
|
- entity_id: sensor.valetudo_rockrobo_sensor_duration
|
||||||
|
subtitle: Sensors
|
62
lovelace/views/view_90_maintenance.yaml
Normal file
62
lovelace/views/view_90_maintenance.yaml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
- title: Wartung
|
||||||
|
id: maintenance
|
||||||
|
icon: mdi:tools
|
||||||
|
cards:
|
||||||
|
- type: custom:auto-entities
|
||||||
|
card:
|
||||||
|
type: entities
|
||||||
|
title: Batteriewarnung
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- entity_id: "sensor.*_battery"
|
||||||
|
state: "<= 10"
|
||||||
|
sort:
|
||||||
|
method: attribute
|
||||||
|
attribute: "battery"
|
||||||
|
reverse: false
|
||||||
|
show_empty: false
|
||||||
|
|
||||||
|
- type: custom:auto-entities
|
||||||
|
card:
|
||||||
|
type: entities
|
||||||
|
title: Firmware Updates
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- entity_id: "update.*"
|
||||||
|
attributes:
|
||||||
|
update:state: available
|
||||||
|
options:
|
||||||
|
icon: mdi:update
|
||||||
|
state_color: false
|
||||||
|
- entity_id: "update.*"
|
||||||
|
attributes:
|
||||||
|
update:state: updating
|
||||||
|
options:
|
||||||
|
icon: mdi:progress-wrench
|
||||||
|
state_color: false
|
||||||
|
sort:
|
||||||
|
method: attribute
|
||||||
|
attribute: "battery"
|
||||||
|
reverse: false
|
||||||
|
show_empty: false
|
||||||
|
|
||||||
|
- type: entities
|
||||||
|
title: Zigbee2MQTT
|
||||||
|
show_header_toggle: False
|
||||||
|
entities:
|
||||||
|
- entity: sensor.zigbee2mqtt_bridge_state
|
||||||
|
- entity: sensor.zigbee2mqtt_version
|
||||||
|
- entity: sensor.zigbee2mqtt_coordinator_version
|
||||||
|
- entity: input_select.zigbee2mqtt_log_level
|
||||||
|
- type: divider
|
||||||
|
- entity: switch.zigbee2mqtt_main_join
|
||||||
|
- entity: input_number.zigbee2mqtt_join_minutes
|
||||||
|
- entity: timer.zigbee_permit_join
|
||||||
|
- type: divider
|
||||||
|
- entity: input_text.zigbee2mqtt_old_name
|
||||||
|
- entity: input_text.zigbee2mqtt_new_name
|
||||||
|
- entity: script.zigbee2mqtt_rename
|
||||||
|
- type: divider
|
||||||
|
- entity: input_text.zigbee2mqtt_remove
|
||||||
|
- entity: input_boolean.zigbee2mqtt_force_remove
|
||||||
|
- entity: script.zigbee2mqtt_remove
|
168
packages/zigbee2mqtt.yaml
Normal file
168
packages/zigbee2mqtt.yaml
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
zigbee2mqtt:
|
||||||
|
# Input select for Zigbee2MQTT debug level
|
||||||
|
input_select:
|
||||||
|
zigbee2mqtt_log_level:
|
||||||
|
name: Zigbee2MQTT Log Level
|
||||||
|
options:
|
||||||
|
- debug
|
||||||
|
- info
|
||||||
|
- warn
|
||||||
|
- error
|
||||||
|
initial: warn
|
||||||
|
icon: mdi:format-list-bulleted
|
||||||
|
|
||||||
|
# Input number for joining time remaining (in minutes)
|
||||||
|
input_number:
|
||||||
|
zigbee2mqtt_join_minutes:
|
||||||
|
name: "Zigbee2MQTT join minutes"
|
||||||
|
initial: 2
|
||||||
|
min: 1
|
||||||
|
max: 5
|
||||||
|
step: 1
|
||||||
|
mode: slider
|
||||||
|
|
||||||
|
# Input text to input Zigbee2MQTT friendly_name for scripts
|
||||||
|
input_text:
|
||||||
|
zigbee2mqtt_old_name:
|
||||||
|
name: Zigbee2MQTT Old Name
|
||||||
|
initial: ""
|
||||||
|
zigbee2mqtt_new_name:
|
||||||
|
name: Zigbee2MQTT New Name
|
||||||
|
initial: ""
|
||||||
|
zigbee2mqtt_remove:
|
||||||
|
name: Zigbee2MQTT Remove
|
||||||
|
initial: ""
|
||||||
|
|
||||||
|
# Input boolean to set the force remove flag for devices
|
||||||
|
input_boolean:
|
||||||
|
zigbee2mqtt_force_remove:
|
||||||
|
name: Zigbee2MQTT Force Remove
|
||||||
|
initial: false
|
||||||
|
icon: mdi:alert-remove
|
||||||
|
|
||||||
|
# Scripts for renaming & removing devices
|
||||||
|
script:
|
||||||
|
zigbee2mqtt_rename:
|
||||||
|
alias: Zigbee2MQTT Rename
|
||||||
|
sequence:
|
||||||
|
service: mqtt.publish
|
||||||
|
data_template:
|
||||||
|
topic: zigbee2mqtt/bridge/request/device/rename
|
||||||
|
payload_template: >-
|
||||||
|
{
|
||||||
|
"from": "{{ states.input_text.zigbee2mqtt_old_name.state | string }}",
|
||||||
|
"to": "{{ states.input_text.zigbee2mqtt_new_name.state | string }}"
|
||||||
|
}
|
||||||
|
zigbee2mqtt_remove:
|
||||||
|
alias: Zigbee2MQTT Remove
|
||||||
|
sequence:
|
||||||
|
service: mqtt.publish
|
||||||
|
data_template:
|
||||||
|
topic: zigbee2mqtt/bridge/request/device/remove
|
||||||
|
payload_template: >-
|
||||||
|
{
|
||||||
|
"id": "{{ states.input_text.zigbee2mqtt_remove.state | string }}",
|
||||||
|
"force": {% if states.input_boolean.zigbee2mqtt_force_remove.state == "off" %}false{% else %}true{% endif %}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Timer for joining time remaining (254 sec)
|
||||||
|
timer:
|
||||||
|
zigbee_permit_join:
|
||||||
|
name: Time remaining
|
||||||
|
duration: 254
|
||||||
|
|
||||||
|
mqtt:
|
||||||
|
sensor:
|
||||||
|
# Sensor for monitoring the bridge state
|
||||||
|
- name: Zigbee2MQTT Bridge state
|
||||||
|
unique_id: zigbee2mqtt_bridge_state_sensor
|
||||||
|
state_topic: "zigbee2mqtt/bridge/state"
|
||||||
|
icon: mdi:router-wireless
|
||||||
|
# Sensor for Showing the Zigbee2MQTT Version
|
||||||
|
- name: Zigbee2MQTT Version
|
||||||
|
unique_id: zigbee2mqtt_version_sensor
|
||||||
|
state_topic: "zigbee2mqtt/bridge/info"
|
||||||
|
value_template: "{{ value_json.version }}"
|
||||||
|
icon: mdi:zigbee
|
||||||
|
# Sensor for Showing the Coordinator Version
|
||||||
|
- name: Zigbee2MQTT Coordinator Version
|
||||||
|
unique_id: zigbee2mqtt_coordinator_version_sensor
|
||||||
|
state_topic: "zigbee2mqtt/bridge/info"
|
||||||
|
value_template: "{{ value_json.coordinator.meta.revision }}"
|
||||||
|
icon: mdi:chip
|
||||||
|
- name: Zigbee2mqtt Networkmap
|
||||||
|
unique_id: zigbee2mqtt_networkmap_sensor
|
||||||
|
# if you change base_topic of Zigbee2mqtt, change state_topic accordingly
|
||||||
|
state_topic: zigbee2mqtt/bridge/networkmap/raw
|
||||||
|
value_template: >-
|
||||||
|
{{ now().strftime('%Y-%m-%d %H:%M:%S') }}
|
||||||
|
# again, if you change base_topic of Zigbee2mqtt, change json_attributes_topic accordingly
|
||||||
|
json_attributes_topic: zigbee2mqtt/bridge/networkmap/raw
|
||||||
|
|
||||||
|
# Switch for enabling joining
|
||||||
|
switch:
|
||||||
|
- name: "Zigbee2MQTT Main join"
|
||||||
|
unique_id: zigbee2mqtt_main_join_switch
|
||||||
|
state_topic: "zigbee2mqtt/bridge/info"
|
||||||
|
value_template: "{{ value_json.permit_join | lower }}"
|
||||||
|
command_topic: "zigbee2mqtt/bridge/request/permit_join"
|
||||||
|
payload_on: "true"
|
||||||
|
payload_off: "false"
|
||||||
|
|
||||||
|
automation:
|
||||||
|
# Automation for sending MQTT message on input select change
|
||||||
|
- alias: Zigbee2MQTT Log Level
|
||||||
|
initial_state: "on"
|
||||||
|
trigger:
|
||||||
|
platform: state
|
||||||
|
entity_id: input_select.zigbee2mqtt_log_level
|
||||||
|
action:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
payload_template: "{{ states('input_select.zigbee2mqtt_log_level') }}"
|
||||||
|
topic: zigbee2mqtt/bridge/request/config/log_level
|
||||||
|
# Automation to start timer when enable join is turned on
|
||||||
|
- id: zigbee_join_enabled
|
||||||
|
alias: Zigbee Join Enabled
|
||||||
|
trigger:
|
||||||
|
platform: state
|
||||||
|
entity_id: switch.zigbee2mqtt_main_join
|
||||||
|
to: "on"
|
||||||
|
action:
|
||||||
|
service: timer.start
|
||||||
|
entity_id: timer.zigbee_permit_join
|
||||||
|
data_template:
|
||||||
|
duration: "{{ '00:0%i:00' % (states('input_number.zigbee2mqtt_join_minutes') | int ) }}"
|
||||||
|
# Automation to stop timer when switch turned off and turn off switch when timer finished
|
||||||
|
- id: zigbee_join_disabled
|
||||||
|
alias: Zigbee Join Disabled
|
||||||
|
trigger:
|
||||||
|
- platform: event
|
||||||
|
event_type: timer.finished
|
||||||
|
event_data:
|
||||||
|
entity_id: timer.zigbee_permit_join
|
||||||
|
- platform: state
|
||||||
|
entity_id: switch.zigbee2mqtt_main_join
|
||||||
|
to: "off"
|
||||||
|
action:
|
||||||
|
- service: timer.cancel
|
||||||
|
data:
|
||||||
|
entity_id: timer.zigbee_permit_join
|
||||||
|
- service: switch.turn_off
|
||||||
|
entity_id: switch.zigbee2mqtt_main_join
|
||||||
|
- id: "zigbee2mqtt_create_notification_on_successful_interview"
|
||||||
|
alias: Zigbee Device Joined Notification
|
||||||
|
trigger:
|
||||||
|
platform: mqtt
|
||||||
|
topic: "zigbee2mqtt/bridge/event"
|
||||||
|
condition:
|
||||||
|
condition: template
|
||||||
|
value_template: '{{trigger.payload_json.type == "device_interview" and trigger.payload_json.data.status == "successful" and trigger.payload_json.data.supported}}'
|
||||||
|
action:
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data_template:
|
||||||
|
title: Device joined the Zigbee2MQTT network
|
||||||
|
message: "Name: {{trigger.payload_json.data.friendly_name}},
|
||||||
|
Vendor: {{trigger.payload_json.data.definition.vendor}},
|
||||||
|
Model: {{trigger.payload_json.data.definition.model}},
|
||||||
|
Description: {{trigger.payload_json.data.definition.description}}"
|
0
scenes.yaml
Normal file
0
scenes.yaml
Normal file
0
scripts.yaml
Normal file
0
scripts.yaml
Normal file
22
templates/bath.yaml
Normal file
22
templates/bath.yaml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
- sensor:
|
||||||
|
- name: bath_sensor_air_quality_rating
|
||||||
|
unit_of_measurement: ppb
|
||||||
|
state: "{{ states('sensor.bath_sensor_air_quality_voc') | float(0) }}"
|
||||||
|
icon: >
|
||||||
|
{% if states.sensor.bath_sensor_air_quality_voc.state is defined %}
|
||||||
|
{% if not is_state('sensor.bath_sensor_air_quality_voc', ['unknown', 'unavailable']) %}
|
||||||
|
{% if float(states('sensor.bath_sensor_air_quality_voc')) < 150 %}
|
||||||
|
mdi:star-circle
|
||||||
|
{% elif float(states('sensor.bath_sensor_air_quality_voc')) >= 150 and float(states('sensor.bath_sensor_air_quality_voc')) < 400 %}
|
||||||
|
mdi:star-circle-outline
|
||||||
|
{% elif float(states('sensor.bath_sensor_air_quality_voc')) >= 400 and float(states('sensor.bath_sensor_air_quality_voc')) < 1300 %}
|
||||||
|
mdi:check-circle-outline
|
||||||
|
{% elif float(states('sensor.bath_sensor_air_quality_voc')) >= 1300 and float(states('sensor.bath_sensor_air_quality_voc')) < 4000 %}
|
||||||
|
mdi:alert-circle-outline
|
||||||
|
{% elif float(states('sensor.bath_sensor_air_quality_voc')) >= 4000 %}
|
||||||
|
mdi:radioactive-circle-outline
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
mdi:circle-off-outline
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
12
templates/kitchen.yaml
Normal file
12
templates/kitchen.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
- binary_sensor:
|
||||||
|
- name: ki_fridge_door_sensor_contact_state
|
||||||
|
state: "{{ states('binary_sensor.ki_fridge_door_sensor_contact') }}"
|
||||||
|
device_class: door
|
||||||
|
icon: >
|
||||||
|
{% if states.binary_sensor.ki_fridge_door_sensor_contact.state is defined %}
|
||||||
|
{% if is_state('binary_sensor.ki_fridge_door_sensor_contact', 'off') %}
|
||||||
|
mdi:fridge-industrial
|
||||||
|
{% else %}
|
||||||
|
mdi:fridge-industrial-alert-outline
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
22
templates/office.yaml
Normal file
22
templates/office.yaml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
- sensor:
|
||||||
|
- name: office_sensor_air_quality_rating
|
||||||
|
unit_of_measurement: ppb
|
||||||
|
state: "{{ states('sensor.office_sensor_air_quality_voc') | float(0) }}"
|
||||||
|
icon: >
|
||||||
|
{% if states.sensor.office_sensor_air_quality_voc.state is defined %}
|
||||||
|
{% if not is_state('sensor.office_sensor_air_quality_voc', ['unknown', 'unavailable']) %}
|
||||||
|
{% if float(states('sensor.office_sensor_air_quality_voc')) < 150 %}
|
||||||
|
mdi:star-circle
|
||||||
|
{% elif float(states('sensor.office_sensor_air_quality_voc')) >= 150 and float(states('sensor.office_sensor_air_quality_voc')) < 400 %}
|
||||||
|
mdi:star-circle-outline
|
||||||
|
{% elif float(states('sensor.office_sensor_air_quality_voc')) >= 400 and float(states('sensor.office_sensor_air_quality_voc')) < 1300 %}
|
||||||
|
mdi:check-circle-outline
|
||||||
|
{% elif float(states('sensor.office_sensor_air_quality_voc')) >= 1300 and float(states('sensor.office_sensor_air_quality_voc')) < 4000 %}
|
||||||
|
mdi:alert-circle-outline
|
||||||
|
{% elif float(states('sensor.office_sensor_air_quality_voc')) >= 4000 %}
|
||||||
|
mdi:radioactive-circle-outline
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
mdi:circle-off-outline
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
53
templates/roborock.yaml
Normal file
53
templates/roborock.yaml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
- sensor:
|
||||||
|
- name: valetudo_rockrobo_main_filter_duration
|
||||||
|
state: >-
|
||||||
|
{% set minutes = states("sensor.valetudo_rockrobo_main_filter") | int(0) %}
|
||||||
|
{% set hours = ((minutes % 1440) / 60) | int %}
|
||||||
|
{% set days = (minutes / 1440) | int %}
|
||||||
|
{%- if minutes >= 60 -%}
|
||||||
|
{%- if days > 0 %}{{ days }}d{% endif -%}
|
||||||
|
{%- if hours > 0 -%}
|
||||||
|
{{ ' ' + hours | string if days > 0 else hours }}h
|
||||||
|
{%- endif -%}
|
||||||
|
{%- else -%}
|
||||||
|
{{ minutes }}min
|
||||||
|
{%- endif -%}
|
||||||
|
- name: valetudo_rockrobo_right_brush_duration
|
||||||
|
state: >-
|
||||||
|
{% set minutes = states("sensor.valetudo_rockrobo_right_brush") | int(0) %}
|
||||||
|
{% set hours = ((minutes % 1440) / 60) | int %}
|
||||||
|
{% set days = (minutes / 1440) | int %}
|
||||||
|
{%- if minutes >= 60 -%}
|
||||||
|
{%- if days > 0 %}{{ days }}d{% endif -%}
|
||||||
|
{%- if hours > 0 -%}
|
||||||
|
{{ ' ' + hours | string if days > 0 else hours }}h
|
||||||
|
{%- endif -%}
|
||||||
|
{%- else -%}
|
||||||
|
{{ minutes }}min
|
||||||
|
{%- endif -%}
|
||||||
|
- name: valetudo_rockrobo_main_brush_duration
|
||||||
|
state: >-
|
||||||
|
{% set minutes = states("sensor.valetudo_rockrobo_main_brush") | int(0) %}
|
||||||
|
{% set hours = ((minutes % 1440) / 60) | int %}
|
||||||
|
{% set days = (minutes / 1440) | int %}
|
||||||
|
{%- if minutes >= 60 -%}
|
||||||
|
{%- if days > 0 %}{{ days }}d{% endif -%}
|
||||||
|
{%- if hours > 0 -%}
|
||||||
|
{{ ' ' + hours | string if days > 0 else hours }}h
|
||||||
|
{%- endif -%}
|
||||||
|
{%- else -%}
|
||||||
|
{{ minutes }}min
|
||||||
|
{%- endif -%}
|
||||||
|
- name: valetudo_rockrobo_sensor_duration
|
||||||
|
state: >-
|
||||||
|
{% set minutes = states("sensor.valetudo_rockrobo_sensor_cleaning") | int(0) %}
|
||||||
|
{% set hours = ((minutes % 1440) / 60) | int %}
|
||||||
|
{% set days = (minutes / 1440) | int %}
|
||||||
|
{%- if minutes >= 60 -%}
|
||||||
|
{%- if days > 0 %}{{ days }}d{% endif -%}
|
||||||
|
{%- if hours > 0 -%}
|
||||||
|
{{ ' ' + hours | string if days > 0 else hours }}h
|
||||||
|
{%- endif -%}
|
||||||
|
{%- else -%}
|
||||||
|
{{ minutes }}min
|
||||||
|
{%- endif -%}
|
3
ui-lovelace.yaml
Normal file
3
ui-lovelace.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
title: Apartment
|
||||||
|
|
||||||
|
views: !include_dir_merge_list lovelace/views/
|
1
www/icons/robot.svg
Normal file
1
www/icons/robot.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 24 24"><path style="fill:#FFFFFF;" d="M12,2C14.65,2 17.19,3.06 19.07,4.93L17.65,6.35C16.15,4.85 14.12,4 12,4C9.88,4 7.84,4.84 6.35,6.35L4.93,4.93C6.81,3.06 9.35,2 12,2M3.66,6.5L5.11,7.94C4.39,9.17 4,10.57 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12C20,10.57 19.61,9.17 18.88,7.94L20.34,6.5C21.42,8.12 22,10.04 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12C2,10.04 2.58,8.12 3.66,6.5M12,6A6,6 0 0,1 18,12C18,13.59 17.37,15.12 16.24,16.24L14.83,14.83C14.08,15.58 13.06,16 12,16C10.94,16 9.92,15.58 9.17,14.83L7.76,16.24C6.63,15.12 6,13.59 6,12A6,6 0 0,1 12,6M12,8A1,1 0 0,0 11,9A1,1 0 0,0 12,10A1,1 0 0,0 13,9A1,1 0 0,0 12,8Z" /></svg>
|
After Width: | Height: | Size: 893 B |
172
www/plugins/auto-entities.js
Normal file
172
www/plugins/auto-entities.js
Normal file
File diff suppressed because one or more lines are too long
559
www/plugins/button-card.js
Normal file
559
www/plugins/button-card.js
Normal file
File diff suppressed because one or more lines are too long
90
www/plugins/card-tools.js
Normal file
90
www/plugins/card-tools.js
Normal file
File diff suppressed because one or more lines are too long
86
www/plugins/fold-entity-row.js
Normal file
86
www/plugins/fold-entity-row.js
Normal file
File diff suppressed because one or more lines are too long
1
www/plugins/multiple-entity-row.js
Normal file
1
www/plugins/multiple-entity-row.js
Normal file
File diff suppressed because one or more lines are too long
1
www/plugins/slider-entity-row.js
Normal file
1
www/plugins/slider-entity-row.js
Normal file
File diff suppressed because one or more lines are too long
4
www/plugins/vacuum-card.js
Normal file
4
www/plugins/vacuum-card.js
Normal file
File diff suppressed because one or more lines are too long
2
www/plugins/valetudo-map-card.js
Normal file
2
www/plugins/valetudo-map-card.js
Normal file
File diff suppressed because one or more lines are too long
189
www/plugins/vertical-stack-in-card.js
Normal file
189
www/plugins/vertical-stack-in-card.js
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
console.log(`%cvertical-stack-in-card\n%cVersion: ${'0.4.4'}`, 'color: #1976d2; font-weight: bold;', '');
|
||||||
|
|
||||||
|
class VerticalStackInCard extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
this._cardSize = {};
|
||||||
|
this._cardSize.promise = new Promise((resolve) => (this._cardSize.resolve = resolve));
|
||||||
|
|
||||||
|
if (!config || !config.cards || !Array.isArray(config.cards)) {
|
||||||
|
throw new Error('Card config incorrect');
|
||||||
|
}
|
||||||
|
this._config = config;
|
||||||
|
this._refCards = [];
|
||||||
|
this.renderCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
async renderCard() {
|
||||||
|
const config = this._config;
|
||||||
|
if (window.loadCardHelpers) {
|
||||||
|
this.helpers = await window.loadCardHelpers();
|
||||||
|
}
|
||||||
|
const promises = config.cards.map((config) => this.createCardElement(config));
|
||||||
|
this._refCards = await Promise.all(promises);
|
||||||
|
|
||||||
|
// Style cards
|
||||||
|
this._refCards.forEach((card) => {
|
||||||
|
if (card.updateComplete) {
|
||||||
|
card.updateComplete.then(() => this.styleCard(card));
|
||||||
|
} else {
|
||||||
|
this.styleCard(card);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the card
|
||||||
|
const card = document.createElement('ha-card');
|
||||||
|
const cardContent = document.createElement('div');
|
||||||
|
card.header = config.title;
|
||||||
|
card.style.overflow = 'hidden';
|
||||||
|
this._refCards.forEach((card) => cardContent.appendChild(card));
|
||||||
|
if (config.horizontal) {
|
||||||
|
cardContent.style.display = 'flex';
|
||||||
|
cardContent.childNodes.forEach((card) => {
|
||||||
|
card.style.flex = '1 1 0';
|
||||||
|
card.style.minWidth = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
card.appendChild(cardContent);
|
||||||
|
|
||||||
|
const shadowRoot = this.shadowRoot || this.attachShadow({mode: 'open'});
|
||||||
|
while (shadowRoot.hasChildNodes()) {
|
||||||
|
shadowRoot.removeChild(shadowRoot.lastChild);
|
||||||
|
}
|
||||||
|
shadowRoot.appendChild(card);
|
||||||
|
|
||||||
|
// Calculate card size
|
||||||
|
this._cardSize.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCardElement(cardConfig) {
|
||||||
|
const createError = (error, origConfig) => {
|
||||||
|
return createThing('hui-error-card', {
|
||||||
|
type: 'error',
|
||||||
|
error,
|
||||||
|
origConfig
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createThing = (tag, config) => {
|
||||||
|
if (this.helpers) {
|
||||||
|
if (config.type === 'divider') {
|
||||||
|
return this.helpers.createRowElement(config);
|
||||||
|
} else {
|
||||||
|
return this.helpers.createCardElement(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = document.createElement(tag);
|
||||||
|
try {
|
||||||
|
element.setConfig(config);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(tag, err);
|
||||||
|
return createError(err.message, config);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
let tag = cardConfig.type;
|
||||||
|
if (tag.startsWith('divider')) {
|
||||||
|
tag = `hui-divider-row`;
|
||||||
|
} else if (tag.startsWith('custom:')) {
|
||||||
|
tag = tag.substr('custom:'.length);
|
||||||
|
} else {
|
||||||
|
tag = `hui-${tag}-card`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = createThing(tag, cardConfig);
|
||||||
|
element.hass = this._hass;
|
||||||
|
element.addEventListener(
|
||||||
|
'll-rebuild',
|
||||||
|
(ev) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.createCardElement(cardConfig).then(() => {
|
||||||
|
this.renderCard();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
set hass(hass) {
|
||||||
|
this._hass = hass;
|
||||||
|
if (this._refCards) {
|
||||||
|
this._refCards.forEach((card) => {
|
||||||
|
card.hass = hass;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
styleCard(element) {
|
||||||
|
const config = this._config;
|
||||||
|
if (element.shadowRoot) {
|
||||||
|
if (element.shadowRoot.querySelector('ha-card')) {
|
||||||
|
let ele = element.shadowRoot.querySelector('ha-card');
|
||||||
|
ele.style.boxShadow = 'none';
|
||||||
|
ele.style.borderRadius = '0';
|
||||||
|
ele.style.border = "none";
|
||||||
|
if ('styles' in config) {
|
||||||
|
Object.entries(config.styles).forEach(([key, value]) => ele.style.setProperty(key, value));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let searchEles = element.shadowRoot.getElementById('root');
|
||||||
|
if (!searchEles) {
|
||||||
|
searchEles = element.shadowRoot.getElementById('card');
|
||||||
|
}
|
||||||
|
if (!searchEles) return;
|
||||||
|
searchEles = searchEles.childNodes;
|
||||||
|
for (let i = 0; i < searchEles.length; i++) {
|
||||||
|
if (searchEles[i].style) {
|
||||||
|
searchEles[i].style.margin = '0px';
|
||||||
|
}
|
||||||
|
this.styleCard(searchEles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof element.querySelector === 'function' && element.querySelector('ha-card')) {
|
||||||
|
let ele = element.querySelector('ha-card');
|
||||||
|
ele.style.boxShadow = 'none';
|
||||||
|
ele.style.borderRadius = '0';
|
||||||
|
ele.style.border = "none";
|
||||||
|
if ('styles' in config) {
|
||||||
|
Object.entries(config.styles).forEach(([key, value]) => ele.style.setProperty(key, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let searchEles = element.childNodes;
|
||||||
|
for (let i = 0; i < searchEles.length; i++) {
|
||||||
|
if (searchEles[i] && searchEles[i].style) {
|
||||||
|
searchEles[i].style.margin = '0px';
|
||||||
|
}
|
||||||
|
this.styleCard(searchEles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeCardSize(card) {
|
||||||
|
if (typeof card.getCardSize === 'function') {
|
||||||
|
return card.getCardSize();
|
||||||
|
}
|
||||||
|
return customElements
|
||||||
|
.whenDefined(card.localName)
|
||||||
|
.then(() => this._computeCardSize(card))
|
||||||
|
.catch(() => 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCardSize() {
|
||||||
|
await this._cardSize.promise;
|
||||||
|
const sizes = await Promise.all(this._refCards.map(this._computeCardSize));
|
||||||
|
return sizes.reduce((a, b) => a + b, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('vertical-stack-in-card', VerticalStackInCard);
|
||||||
|
|
||||||
|
window.customElements.get('vertical-stack-in-card').getConfigElement = function() {
|
||||||
|
return document.createElement('hui-stack-card-editor');
|
||||||
|
}
|
530
www/plugins/weather-card.js
Normal file
530
www/plugins/weather-card.js
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
const LitElement = customElements.get("hui-masonry-view") ? Object.getPrototypeOf(customElements.get("hui-masonry-view")) : Object.getPrototypeOf(customElements.get("hui-view"));
|
||||||
|
const html = LitElement.prototype.html;
|
||||||
|
const css = LitElement.prototype.css;
|
||||||
|
|
||||||
|
const weatherIconsDay = {
|
||||||
|
clear: "day",
|
||||||
|
"clear-night": "night",
|
||||||
|
cloudy: "cloudy",
|
||||||
|
fog: "cloudy",
|
||||||
|
hail: "rainy-7",
|
||||||
|
lightning: "thunder",
|
||||||
|
"lightning-rainy": "thunder",
|
||||||
|
partlycloudy: "cloudy-day-3",
|
||||||
|
pouring: "rainy-6",
|
||||||
|
rainy: "rainy-5",
|
||||||
|
snowy: "snowy-6",
|
||||||
|
"snowy-rainy": "rainy-7",
|
||||||
|
sunny: "day",
|
||||||
|
windy: "cloudy",
|
||||||
|
"windy-variant": "cloudy-day-3",
|
||||||
|
exceptional: "!!",
|
||||||
|
};
|
||||||
|
|
||||||
|
const weatherIconsNight = {
|
||||||
|
...weatherIconsDay,
|
||||||
|
clear: "night",
|
||||||
|
sunny: "night",
|
||||||
|
partlycloudy: "cloudy-night-3",
|
||||||
|
"windy-variant": "cloudy-night-3",
|
||||||
|
};
|
||||||
|
|
||||||
|
const windDirections = [
|
||||||
|
"N",
|
||||||
|
"NNE",
|
||||||
|
"NE",
|
||||||
|
"ENE",
|
||||||
|
"E",
|
||||||
|
"ESE",
|
||||||
|
"SE",
|
||||||
|
"SSE",
|
||||||
|
"S",
|
||||||
|
"SSW",
|
||||||
|
"SW",
|
||||||
|
"WSW",
|
||||||
|
"W",
|
||||||
|
"WNW",
|
||||||
|
"NW",
|
||||||
|
"NNW",
|
||||||
|
"N",
|
||||||
|
];
|
||||||
|
|
||||||
|
window.customCards = window.customCards || [];
|
||||||
|
window.customCards.push({
|
||||||
|
type: "weather-card",
|
||||||
|
name: "Weather Card",
|
||||||
|
description: "A custom weather card with animated icons.",
|
||||||
|
preview: true,
|
||||||
|
documentationURL: "https://github.com/bramkragten/weather-card",
|
||||||
|
});
|
||||||
|
|
||||||
|
const fireEvent = (node, type, detail, options) => {
|
||||||
|
options = options || {};
|
||||||
|
detail = detail === null || detail === undefined ? {} : detail;
|
||||||
|
const event = new Event(type, {
|
||||||
|
bubbles: options.bubbles === undefined ? true : options.bubbles,
|
||||||
|
cancelable: Boolean(options.cancelable),
|
||||||
|
composed: options.composed === undefined ? true : options.composed,
|
||||||
|
});
|
||||||
|
event.detail = detail;
|
||||||
|
node.dispatchEvent(event);
|
||||||
|
return event;
|
||||||
|
};
|
||||||
|
|
||||||
|
function hasConfigOrEntityChanged(element, changedProps) {
|
||||||
|
if (changedProps.has("_config")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldHass = changedProps.get("hass");
|
||||||
|
if (oldHass) {
|
||||||
|
return (
|
||||||
|
oldHass.states[element._config.entity] !==
|
||||||
|
element.hass.states[element._config.entity] ||
|
||||||
|
oldHass.states["sun.sun"] !== element.hass.states["sun.sun"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WeatherCard extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
_config: {},
|
||||||
|
hass: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getConfigElement() {
|
||||||
|
await import("./weather-card-editor.js");
|
||||||
|
return document.createElement("weather-card-editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
static getStubConfig(hass, unusedEntities, allEntities) {
|
||||||
|
let entity = unusedEntities.find((eid) => eid.split(".")[0] === "weather");
|
||||||
|
if (!entity) {
|
||||||
|
entity = allEntities.find((eid) => eid.split(".")[0] === "weather");
|
||||||
|
}
|
||||||
|
return { entity };
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
if (!config.entity) {
|
||||||
|
throw new Error("Please define a weather entity");
|
||||||
|
}
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldUpdate(changedProps) {
|
||||||
|
return hasConfigOrEntityChanged(this, changedProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this._config || !this.hass) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.numberElements = 0;
|
||||||
|
|
||||||
|
const stateObj = this.hass.states[this._config.entity];
|
||||||
|
|
||||||
|
if (!stateObj) {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
.not-found {
|
||||||
|
flex: 1;
|
||||||
|
background-color: yellow;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<ha-card>
|
||||||
|
<div class="not-found">
|
||||||
|
Entity not available: ${this._config.entity}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card @click="${this._handleClick}">
|
||||||
|
${this._config.current !== false ? this.renderCurrent(stateObj) : ""}
|
||||||
|
${this._config.details !== false ? this.renderDetails(stateObj) : ""}
|
||||||
|
${this._config.forecast !== false
|
||||||
|
? this.renderForecast(stateObj.attributes.forecast)
|
||||||
|
: ""}
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCurrent(stateObj) {
|
||||||
|
this.numberElements++;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="current ${this.numberElements > 1 ? "spacer" : ""}">
|
||||||
|
<span
|
||||||
|
class="icon bigger"
|
||||||
|
style="background: none, url('${this.getWeatherIcon(
|
||||||
|
stateObj.state.toLowerCase(),
|
||||||
|
this.hass.states["sun.sun"]
|
||||||
|
)}') no-repeat; background-size: contain;"
|
||||||
|
>${stateObj.state}
|
||||||
|
</span>
|
||||||
|
${this._config.name
|
||||||
|
? html` <span class="title"> ${this._config.name} </span> `
|
||||||
|
: ""}
|
||||||
|
<span class="temp"
|
||||||
|
>${this.getUnit("temperature") == "°F"
|
||||||
|
? Math.round(stateObj.attributes.temperature)
|
||||||
|
: stateObj.attributes.temperature}</span
|
||||||
|
>
|
||||||
|
<span class="tempc"> ${this.getUnit("temperature")}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDetails(stateObj) {
|
||||||
|
const sun = this.hass.states["sun.sun"];
|
||||||
|
let next_rising;
|
||||||
|
let next_setting;
|
||||||
|
|
||||||
|
if (sun) {
|
||||||
|
next_rising = new Date(sun.attributes.next_rising);
|
||||||
|
next_setting = new Date(sun.attributes.next_setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.numberElements++;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ul class="variations ${this.numberElements > 1 ? "spacer" : ""}">
|
||||||
|
<li>
|
||||||
|
<ha-icon icon="mdi:water-percent"></ha-icon>
|
||||||
|
${stateObj.attributes.humidity}<span class="unit"> % </span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ha-icon icon="mdi:weather-windy"></ha-icon> ${windDirections[
|
||||||
|
parseInt((stateObj.attributes.wind_bearing + 11.25) / 22.5)
|
||||||
|
]}
|
||||||
|
${stateObj.attributes.wind_speed}<span class="unit">
|
||||||
|
${this.getUnit("length")}/h
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ha-icon icon="mdi:gauge"></ha-icon>
|
||||||
|
${stateObj.attributes.pressure}
|
||||||
|
<span class="unit">
|
||||||
|
${this.getUnit("air_pressure")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ha-icon icon="mdi:weather-fog"></ha-icon> ${stateObj.attributes
|
||||||
|
.visibility}<span class="unit">
|
||||||
|
${this.getUnit("length")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
${next_rising
|
||||||
|
? html`
|
||||||
|
<li>
|
||||||
|
<ha-icon icon="mdi:weather-sunset-up"></ha-icon>
|
||||||
|
${next_rising.toLocaleTimeString()}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${next_setting
|
||||||
|
? html`
|
||||||
|
<li>
|
||||||
|
<ha-icon icon="mdi:weather-sunset-down"></ha-icon>
|
||||||
|
${next_setting.toLocaleTimeString()}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderForecast(forecast) {
|
||||||
|
if (!forecast || forecast.length === 0) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lang = this.hass.selectedLanguage || this.hass.language;
|
||||||
|
|
||||||
|
this.numberElements++;
|
||||||
|
return html`
|
||||||
|
<div class="forecast clear ${this.numberElements > 1 ? "spacer" : ""}">
|
||||||
|
${forecast
|
||||||
|
.slice(
|
||||||
|
0,
|
||||||
|
this._config.number_of_forecasts
|
||||||
|
? this._config.number_of_forecasts
|
||||||
|
: 5
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(daily) => html`
|
||||||
|
<div class="day">
|
||||||
|
<div class="dayname">
|
||||||
|
${this._config.hourly_forecast
|
||||||
|
? new Date(daily.datetime).toLocaleTimeString(lang, {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
})
|
||||||
|
: new Date(daily.datetime).toLocaleDateString(lang, {
|
||||||
|
weekday: "short",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="icon"
|
||||||
|
style="background: none, url('${this.getWeatherIcon(
|
||||||
|
daily.condition.toLowerCase()
|
||||||
|
)}') no-repeat; background-size: contain"
|
||||||
|
></i>
|
||||||
|
<div class="highTemp">
|
||||||
|
${daily.temperature}${this.getUnit("temperature")}
|
||||||
|
</div>
|
||||||
|
${daily.templow !== undefined
|
||||||
|
? html`
|
||||||
|
<div class="lowTemp">
|
||||||
|
${daily.templow}${this.getUnit("temperature")}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${!this._config.hide_precipitation &&
|
||||||
|
daily.precipitation !== undefined &&
|
||||||
|
daily.precipitation !== null
|
||||||
|
? html`
|
||||||
|
<div class="precipitation">
|
||||||
|
${Math.round(daily.precipitation*10)/10} ${this.getUnit("precipitation")}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${!this._config.hide_precipitation &&
|
||||||
|
daily.precipitation_probability !== undefined &&
|
||||||
|
daily.precipitation_probability !== null
|
||||||
|
? html`
|
||||||
|
<div class="precipitation_probability">
|
||||||
|
${Math.round(daily.precipitation_probability)} ${this.getUnit("precipitation_probability")}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWeatherIcon(condition, sun) {
|
||||||
|
return `${
|
||||||
|
this._config.icons
|
||||||
|
? this._config.icons
|
||||||
|
: "https://cdn.jsdelivr.net/gh/bramkragten/weather-card/dist/icons/"
|
||||||
|
}${
|
||||||
|
sun && sun.state == "below_horizon"
|
||||||
|
? weatherIconsNight[condition]
|
||||||
|
: weatherIconsDay[condition]
|
||||||
|
}.svg`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnit(measure) {
|
||||||
|
const lengthUnit = this.hass.config.unit_system.length;
|
||||||
|
switch (measure) {
|
||||||
|
case "air_pressure":
|
||||||
|
return lengthUnit === "km" ? "hPa" : "inHg";
|
||||||
|
case "length":
|
||||||
|
return lengthUnit;
|
||||||
|
case "precipitation":
|
||||||
|
return lengthUnit === "km" ? "mm" : "in";
|
||||||
|
case "precipitation_probability":
|
||||||
|
return "%";
|
||||||
|
default:
|
||||||
|
return this.hass.config.unit_system[measure] || "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleClick() {
|
||||||
|
fireEvent(this, "hass-more-info", { entityId: this._config.entity });
|
||||||
|
}
|
||||||
|
|
||||||
|
getCardSize() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
ha-card {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-top: 1.3em;
|
||||||
|
padding-bottom: 1.3em;
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: absolute;
|
||||||
|
left: 3em;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 3em;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp {
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 4em;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tempc {
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 1.5em;
|
||||||
|
vertical-align: super;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
margin-top: -14px;
|
||||||
|
margin-right: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 460px) {
|
||||||
|
.title {
|
||||||
|
font-size: 2.2em;
|
||||||
|
left: 4em;
|
||||||
|
}
|
||||||
|
.temp {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
.tempc {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.current {
|
||||||
|
padding: 1.2em 0;
|
||||||
|
margin-bottom: 3.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variations {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
list-style: none;
|
||||||
|
padding: 0 1em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variations ha-icon {
|
||||||
|
height: 22px;
|
||||||
|
margin-right: 5px;
|
||||||
|
color: var(--paper-item-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.variations li {
|
||||||
|
flex-basis: auto;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variations li:nth-child(2n) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variations li:nth-child(2n) ha-icon {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forecast {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
flex: 1;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
border-right: 0.1em solid #d9d9d9;
|
||||||
|
line-height: 2;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dayname {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forecast .day:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forecast .day:nth-last-child(1) {
|
||||||
|
border-right: none;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highTemp {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lowTemp {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.precipitation {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.bigger {
|
||||||
|
width: 10em;
|
||||||
|
height: 10em;
|
||||||
|
margin-top: -4em;
|
||||||
|
position: absolute;
|
||||||
|
left: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
margin-right: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
text-indent: -9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather {
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
text-align: left;
|
||||||
|
position: absolute;
|
||||||
|
top: -0.5em;
|
||||||
|
left: 6em;
|
||||||
|
word-wrap: break-word;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define("weather-card", WeatherCard);
|
Loading…
Reference in New Issue
Block a user