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