initial commit

This commit is contained in:
Robert Kaussow 2024-10-17 21:45:45 +02:00
commit 865a27fb6e
Signed by: xoxys
GPG Key ID: 4E692A2EAECC03C0
34 changed files with 2400 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.cloud
.storage
.HA_VERSION
.vscode
home-assistant.log*

67
automations.yaml Normal file
View 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
View 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
View 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

View 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)

View File

@ -0,0 +1,2 @@
"light.all":
friendly_name: Alle Lampen

13
customize/glob/hall.yaml Normal file
View 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

View 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

View 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

View 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
View File

View 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

View 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

View 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

View 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
View 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
View File

0
scripts.yaml Normal file
View File

22
templates/bath.yaml Normal file
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
title: Apartment
views: !include_dir_merge_list lovelace/views/

1
www/icons/robot.svg Normal file
View 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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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
View 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);