Home Assistant Git Exporter

This commit is contained in:
root
2024-05-31 09:39:52 +02:00
parent cd6fa93633
commit d5ccfbb540
1353 changed files with 43876 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"*.yaml": "yaml"
}
}

View File

@@ -0,0 +1,65 @@
---
#----------------------------------------
#
# jour de semaine
#----------------------------------------
#_ platform: workday
#country: FR
# workdays: [mon, tue, wed, thu, fri]
#----------------------------------------
#
# pi computer & esp & other hardware
#----------------------------------------
# - platform: ping
# host: 10.0.0.30
# name: pi0_bureau
# count: 2
# scan_interval: 300
# - platform: ping
# host: 10.0.0.31
# name: pi0_comble2
# count: 2
# scan_interval: 300
# - platform: ping
# host: 10.0.0.251
# name: WifiHome
# count: 2
# scan_interval: 300
# - platform: ping
# host: 10.0.0.100
# name: Linksys g
# count: 2
# scan_interval: 300
# - platform: ping
# host: 10.0.0.106
# name: RM pro
# count: 2
# scan_interval: 300
# - platform: ping
# host: 10.0.0.78
# name: ArduinoESP
# count: 2
# scan_interval: 300
# - platform: ping
# host: 10.0.0.237
# name: TV
# count: 2
# scan_interval: 60
# - platform: ping
# host: 10.0.0.208
# name: sonnete
# count: 2
# scan_interval: 1
# - platform: template
# sensors:
# your_sensor:
# friendly_name: fenetre
# value_template: "{{ is_state('binary_sensor.trigger_sensor', 'off') and (states.trigger_sensor.last_changed < (now() - timedelta(minutes=10))) }}"
- platform: meteoalarm
country: "france"
province: "Haute-Loire"
language: "fr-FR"

View File

@@ -0,0 +1,33 @@
######################################################################
## chaudiere
## digital inputs
# input_type: discrete_input
## FC=02 lire discrete inputs de 10001-10086 enlever 1 a register
######################################################################
- platform: modbus
scan_interval: 5
inputs:
- name: porte_chaudiere
hub: pimodbus
slave: 2
address: 0
input_type: discrete_input
device_class: door
######################################################################
## chaudiere
## digital outputs
# input_type: coil
## FC=01 lire coil inputs de 00001-00158 enlever 1 a register
######################################################################
- platform: modbus
scan_interval: 10
inputs:
- name: circuit chauffage pompe 1
hub: pimodbus
slave: 2
address: 0
input_type: coil
device_class: moving

View File

View File

@@ -0,0 +1,33 @@
# Example configuration.yaml entry
device_tracker:
- platform: ping
hosts:
ipad_denis_p: 10.0.0.150
ipad_gilles_p: 10.0.0.223
iphone_gilles_p: 10.0.0.103
iphone_denis_p: 10.0.0.141
binary_sensor:
- platform: ping
hosts:
d_pc_domotic: 10.0.0.4
d_vm_mqtt: 10.0.0.3
d_vm_node_red: 10.0.0.9
d_vm_docker: 10.0.0.8
d_pi_chauff: 10.0.0.7
d_pi0_comble: 10.0.0.6
# d_pi_solar: 10.0.0.14
# d_pi0_cave: 10.0.0.35
d_sonoff_ballon: 10.0.0.52
d_sonoff_ecl_bois: 10.0.0.60
d_sonoff_dressing: 10.0.0.62
d_nodemcu_volet_sal1: 10.0.0.74
d_nodemcu_volet_sal2: 10.0.0.75
#d_port_lenovo: 10.0.0.113
#h_sonnette: 10.0.0.208
pi0_bureau: 10.0.0.30
pi0_comble: 10.0.0.31
h_wifi_home: 10.0.0.251
h_wifi_linksys_g: 10.0.0.100
h_rm_pro: 10.0.0.112
h_tv: 10.0.0.237

View File

@@ -0,0 +1,58 @@
#https://github.com/rgc99/irrigation_unlimited
controllers:
zones:
- name: "Rosiers"
entity_id: "switch.rosier_1"
duration: "00:03:00"
- name: "Terrasse"
entity_id: "switch.terrasse_2"
duration: "00:02:00"
- name: "Vigne"
entity_id: "switch.vigne_3"
duration: "00:05:00"
sequences:
- name: "hiver"
duration: "00:02"
delay: "00:01"
schedules:
- time: "06:30"
weekday: [mon, tue, wed, thu, fri, sat, sun]
month: [dec, jan, feb]
zones:
- zone_id: 1
- zone_id: 2
- zone_id: 3
- name: "Eté"
duration: "00:02"
delay: "00:01"
schedules:
- time: "06:30"
weekday: [mon, tue, wed, thu, fri, sat, sun]
month: [jun, jul, aug]
zones:
- zone_id: 1
- zone_id: 2
- zone_id: 3
- name: "Printemp & Automne"
duration: "00:02"
delay: "00:01"
schedules:
- time: "06:30"
weekday: [mon, tue, wed, thu, fri, sat, sun]
month: [mar, apr, may, sep, oct, nov]
zones:
- zone_id: 3
- zone_id: 2
- zone_id: 1
- name: "Soirée"
duration: "00:02"
delay: "00:01"
schedules:
- time: "20:30"
weekday: [mon, tue, wed, thu, fri, sat, sun]
month: [jun, jul, aug]
zones:
- zone_id: 3
- zone_id: 2
- zone_id: 1

View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"*.yaml": "home-assistant"
}
}

View File

@@ -0,0 +1,8 @@
#salon:
# name: plafond_salon_z
# entities:
# - light.tz3000_qd7hej8u_ts0505b_9037cafe_level_light_color_on_off
# - light.tz3000_qd7hej8u_ts0505b_fc94bafe_level_light_color_on_off

View File

@@ -0,0 +1,70 @@
# #----------------------------------------
# #
# # RFXCOM with Node-red
# #
# #----------------------------------------
# - platform: mqtt
# name: "applique_salon"
# schema: json
# state_topic: "home/light/a_salon"
# command_topic: "home/light/a_salon/set"
# brightness: true
# rgb: false
# brightness_scale: 100
# unique_id: "AC/0x011E611E/1"
# # state_topic: "home/app_salon/light/status"
# # command_topic: "home/app_salon/light/switch"
# # brightness_state_topic: 'home/app_salon/light/brightness'
# # brightness_command_topic: 'home/app_salon/light/brightness/set'
# # qos: 0
# # payload_on: "On"
# # payload_off: "Off"
# # optimistic: false
# - platform: mqtt
# name: "applique_cuisine"
# schema: json
# state_topic: "home/light/a_cuisine"
# command_topic: "home/light/a_cuisine/set"
# brightness: true
# rgb: false
# brightness_scale: 100
# unique_id: "AC/0x011E611E/2"
# # state_topic: "home/app_salon/light/status"
# # command_topic: "home/app_salon/light/switch"
# # brightness_state_topic: 'home/app_salon/light/brightness'
# # brightness_command_topic: 'home/app_salon/light/brightness/set'
# # qos: 0
# # payload_on: "On"
# # payload_off: "Off"
# # optimistic: false
# - platform: mqtt
# name: "plafond_cuisine"
# schema: json
# state_topic: "home/light/p_cuisine"
# command_topic: "home/light/p_cuisine/set"
# brightness: true
# rgb: false
# brightness_scale: 100
# unique_id: "AC/0x011E611E/4"
# # state_topic: "home/app_salon/light/status"
# # command_topic: "home/app_salon/light/switch"
# # brightness_state_topic: 'home/app_salon/light/brightness'
# # brightness_command_topic: 'home/app_salon/light/brightness/set'
# # qos: 0
# # payload_on: "On"
# # payload_off: "Off"
# # optimistic: false

View File

@@ -0,0 +1,417 @@
- platform: mqtt
name: "LED"
command_topic: "cmnd/20/Output/14"
state_topic: "stat/20/Output/14"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 1"
command_topic: "cmnd/20/Output/1"
state_topic: "stat/20/Output/1"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 2"
command_topic: "cmnd/20/Output/2"
state_topic: "stat/20/Output/2"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 3"
command_topic: "cmnd/20/Output/3"
state_topic: "stat/20/Output/3"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 4"
command_topic: "cmnd/20/Output/4"
state_topic: "stat/20/Output/4"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 5"
command_topic: "cmnd/20/Output/5"
state_topic: "stat/20/Output/5"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 6"
command_topic: "cmnd/20/Output/6"
state_topic: "stat/20/Output/6"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 7"
command_topic: "cmnd/20/Output/7"
state_topic: "stat/20/Output/7"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 8"
command_topic: "cmnd/20/Output/8"
state_topic: "stat/20/Output/8"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 9"
command_topic: "cmnd/20/Output/9"
state_topic: "stat/20/Output/9"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 10"
command_topic: "cmnd/20/Output/10"
state_topic: "stat/20/Output/10"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 11"
command_topic: "cmnd/20/Output/11"
state_topic: "stat/20/Output/11"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 12"
command_topic: "cmnd/20/Output/12"
state_topic: "stat/20/Output/12"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay_13"
command_topic: "cmnd/20/Output/13"
state_topic: "stat/20/Output/13"
payload_on: "0"
payload_off: "1"
- platform: mqtt
name: "relay 14"
command_topic: "cmnd/20/Output/14"
state_topic: "stat/20/Output/14"
payload_on: "0"
payload_off: "1"
#----------------------------------------
#
# tasmota
#
#----------------------------------------
- platform: mqtt
name: "Comble"
state_topic: "stat/comble/RESULT"
value_template: "{{ value_json.POWER }}"
command_topic: "cmnd/comble/Power1"
availability_topic: "tele/comble/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "Cave"
state_topic: "stat/cave/RESULT"
value_template: "{{ value_json.POWER1 }}"
command_topic: "cmnd/cave/Power1"
availability_topic: "tele/cave/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "Cave2"
state_topic: "stat/cave/RESULT"
value_template: "{{ value_json.POWER4 }}"
command_topic: "cmnd/cave/Power4"
availability_topic: "tele/cave/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "VMC1"
state_topic: "stat/sonoffVMC/RESULT"
value_template: "{{ value_json.POWER1 }}"
command_topic: "cmnd/sonoffVMC/Power1"
availability_topic: "tele/sonoffVMC/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "VMC2"
state_topic: "stat/sonoffVMC/RESULT"
value_template: "{{ value_json.POWER2 }}"
command_topic: "cmnd/sonoffVMC/Power2"
availability_topic: "tele/sonoffVMC/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "Eclairage bois"
state_topic: "stat/garage/RESULT"
value_template: "{{ value_json.POWER }}"
command_topic: "cmnd/garage/Power"
availability_topic: "tele/garage/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "Couloir"
state_topic: "stat/couloir/RESULT"
value_template: "{{ value_json.POWER }}"
command_topic: "cmnd/couloir/Power"
availability_topic: "tele/couloir/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "Escalier"
state_topic: "stat/escalier/RESULT"
value_template: "{{ value_json.POWER }}"
command_topic: "cmnd/escalier/POWER"
payload_on: "ON"
payload_off: "OFF"
availability_topic: "tele/escalier/LWT"
payload_available: "Online"
payload_not_available: "Offline"
qos: 1
retain: false
- platform: mqtt
name: "Dressing"
state_topic: "stat/dressing/RESULT"
value_template: "{{ value_json.POWER }}"
command_topic: "cmnd/dressing/Power"
availability_topic: "tele/dressing/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "bureau1"
state_topic: "stat/bureau1/RESULT"
value_template: "{{ value_json.POWER }}"
command_topic: "cmnd/bureau1/Power1"
availability_topic: "tele/bureau1/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "bureau2"
state_topic: "stat/bureau2/RESULT"
value_template: "{{ value_json.POWER }}"
command_topic: "cmnd/bureau2/Power1"
availability_topic: "tele/bureau2/LWT"
qos: 0
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "lampe_bureau"
state_topic: "stat/desklamp/POWER"
command_topic: "cmnd/desklamp/POWER"
availability_topic: "tele/desklamp/LWT"
brightness_state_topic: "stat/desklamp/RESULT"
brightness_command_topic: "cmnd/desklamp/Dimmer"
brightness_scale: 100
brightness_value_template: "{{ value_json.Dimmer }}"
qos: 1
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
#----------------------------------------
#
# dimmer TUYA
#
#----------------------------------------
- platform: mqtt
name: "plafond_cuisine2"
state_topic: "stat/plafond_cuisine/POWER"
command_topic: "cmnd/plafond_cuisine/POWER"
availability_topic: "tele/plafond_cuisine/LWT"
brightness_state_topic: "stat/plafond_cuisine/RESULT"
brightness_command_topic: "cmnd/plafond_cuisine/Dimmer"
brightness_scale: 100
brightness_value_template: "{{ value_json.Dimmer }}"
qos: 1
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
- platform: mqtt
name: "plafond_salon2"
state_topic: "stat/plafond_salon/POWER"
command_topic: "cmnd/plafond_salon/POWER"
availability_topic: "tele/plafond_salon/LWT"
brightness_state_topic: "stat/plafond_salon/RESULT"
brightness_command_topic: "cmnd/plafond_salon/Dimmer"
brightness_scale: 100
brightness_value_template: "{{ value_json.Dimmer }}"
qos: 1
payload_on: "ON"
payload_off: "OFF"
payload_available: "Online"
payload_not_available: "Offline"
retain: false
#----------------------------------------
#
# H801 tasmota RGB
#
#----------------------------------------
- platform: mqtt
name: "RGB"
state_topic: "stat/sonoffrgb/POWER"
command_topic: "cmnd/sonoffrgb/POWER"
#brightness_state_topic: "008565AA/rgb/brightness/status"
#brightness_command_topic: "008565AA/rgb/brightness/set"
rgb_state_topic: "state/sonoffrgb/color"
rgb_command_topic: "cmnd/sonoffrgb/color"
state_value_template: "{{ value_json.state }}"
brightness_value_template: "{{ value_json.brightness }}"
rgb_value_template: "{{ value_json.rgb | join(',') }}"
qos: 0
payload_on: "ON"
payload_off: "OFF"
optimistic: false
#brightness: true
#rgb: true
#----------------------------------------
#
# RFXCOM with Node-red
#
#----------------------------------------
- platform: mqtt
name: "applique_salon"
schema: json
state_topic: "home/light/a_salon"
command_topic: "home/light/a_salon/set"
brightness: true
rgb: false
brightness_scale: 100
unique_id: "AC/0x011E611E/1"
# state_topic: "home/app_salon/light/status"
# command_topic: "home/app_salon/light/switch"
# brightness_state_topic: 'home/app_salon/light/brightness'
# brightness_command_topic: 'home/app_salon/light/brightness/set'
# qos: 0
# payload_on: "On"
# payload_off: "Off"
# optimistic: false
- platform: mqtt
name: "applique_cuisine"
schema: json
state_topic: "home/light/a_cuisine"
command_topic: "home/light/a_cuisine/set"
brightness: true
rgb: false
brightness_scale: 100
unique_id: "AC/0x011E611E/2"
# state_topic: "home/app_salon/light/status"
# command_topic: "home/app_salon/light/switch"
# brightness_state_topic: 'home/app_salon/light/brightness'
# brightness_command_topic: 'home/app_salon/light/brightness/set'
# qos: 0
# payload_on: "On"
# payload_off: "Off"
# optimistic: false
- platform: mqtt
name: "plafond_salon"
schema: json
state_topic: "home/light/p_salon"
command_topic: "home/light/p_salon/set"
brightness: true
rgb: false
brightness_scale: 100
unique_id: "AC/0x011E611E/3"
# state_topic: "home/app_salon/light/status"
# command_topic: "home/app_salon/light/switch"
# brightness_state_topic: 'home/app_salon/light/brightness'
# brightness_command_topic: 'home/app_salon/light/brightness/set'
# qos: 0
# payload_on: "On"
# payload_off: "Off"
# optimistic: false
- platform: mqtt
name: "plafond_cuisine"
schema: json
state_topic: "home/light/p_cuisine"
command_topic: "home/light/p_cuisine/set"
brightness: true
rgb: false
brightness_scale: 100
unique_id: "AC/0x011E611E/4"
# state_topic: "home/app_salon/light/status"
# command_topic: "home/app_salon/light/switch"
# brightness_state_topic: 'home/app_salon/light/brightness'
# brightness_command_topic: 'home/app_salon/light/brightness/set'
# qos: 0
# payload_on: "On"
# payload_off: "Off"
# optimistic: false
- platform: mqtt
name: "chambre"
schema: json
state_topic: "home/light/chambre"
command_topic: "home/light/chambre/set"
brightness: true
rgb: false
brightness_scale: 100
unique_id: "AC/0x011E611E/5"
# state_topic: "home/app_salon/light/status"
# command_topic: "home/app_salon/light/switch"
# brightness_state_topic: 'home/app_salon/light/brightness'
# brightness_command_topic: 'home/app_salon/light/brightness/set'
# qos: 0
# payload_on: "On"
# payload_off: "Off"
# optimistic: false

View File

@@ -0,0 +1,9 @@
yeelight:
devices:
10.0.0.107:
name: plafondSalon2
transition: 1000
use_music_mode: true
save_on_change: true
model: color4

View File

@@ -0,0 +1,64 @@
- platform: mqtt
name: "bureau etage"
state_topic: "yeelight/light/bureau/status"
command_topic: "yeelight/light/bureau/status/set"
brightness_state_topic: "yeelight/light/bureau/bright"
brightness_command_topic: "yeelight/light/bureau/bright/set"
brightness_scale: 100
color_temp_state_topic: "yeelight/light/bureau/ct"
color_temp_command_topic: "yeelight/light/bureau/ct/set"
qos: 0
payload_on: "on"
payload_off: "off"
optimistic: false
- platform: mqtt
name: "salon 2"
state_topic: "yeelight/light/salon2/status"
command_topic: "yeelight/light/salon2/status/set"
brightness_state_topic: "yeelight/light/salon2/bright"
brightness_command_topic: "yeelight/light/salon2/bright/set"
brightness_scale: 100
color_temp_state_topic: "yeelight/light/salon2/ct"
color_temp_command_topic: "yeelight/light/salon2/ct/set"
min_mireds: 1700
max_mireds: 6500
rgb_state_topic: "yeelight/light/salon2/rgb"
rgb_command_topic: "yeelight/light/salon2/rgb/set"
qos: 0
payload_on: "on"
payload_off: "off"
optimistic: false
- platform: mqtt
name: "salon 1"
state_topic: "yeelight/light/salon1/status"
command_topic: "yeelight/light/salon1/status/set"
brightness_state_topic: "yeelight/light/salon1/bright"
brightness_command_topic: "yeelight/light/salon1/bright/set"
brightness_scale: 100
color_temp_state_topic: "yeelight/light/salon1/ct"
color_temp_command_topic: "yeelight/light/salon1/ct/set"
min_mireds: 1700
max_mireds: 6500
rgb_state_topic: "yeelight/light/salon1/rgb"
rgb_command_topic: "yeelight/light/salon1/rgb/set"
qos: 0
payload_on: "on"
payload_off: "off"
optimistic: false
- platform: mqtt
name: "chambre1"
state_topic: "yeelight/light/chambre1/status"
command_topic: "yeelight/light/chambre1/status/set"
brightness_state_topic: "yeelight/light/chambre1/bright"
brightness_command_topic: "yeelight/light/chambre1/bright/set"
brightness_scale: 100
color_temp_state_topic: "yeelight/light/chambre1/ct"
color_temp_command_topic: "yeelight/light/chambre1/ct/set"
min_mireds: 1700
max_mireds: 6500
rgb_state_topic: "yeelight/light/chambre1/rgb"
rgb_command_topic: "yeelight/light/chambre1/rgb/set"
qos: 0
payload_on: "on"
payload_off: "off"
optimistic: false

View File

@@ -0,0 +1,324 @@
# Digital Outputs
# Function: Read Coil Status (FC=01)
# Address Range: 00001-00158
# Digital Inputs
# Function: Read Input Status (FC=02)
# Address Range: 10001-10086
# Actual Values
# Function: Read Input Registers(FC=04)
# Address Range: 30001-30272
# input_type : input
# Parameters
# Function: Read Holding Registers(FC=03)
# Address Range: 40001-41094
# input_type: holding
#- name: froling_S3
# type: tcp
# host: 10.0.0.12
# port: 502
# maybe need 1 second delay after connect for device to get ready
#delay: 1
# sensors:
# # SYSTEM STATUS
# - name: Modbus PE1 System Status Enum
# unique_id: modbus_pe1_system_status_enum
# slave: 2
# input_type: input
# address: 4000
# scan_interval: 30
# device_class: enum
# - name: Modbus PE1 Furnace Status Enum
# unique_id: modbus_pe1_furnace_status_enum
# slave: 2
# input_type: input
# address: 4001
# scan_interval: 30
# device_class: enum
# # - name: Modbus PE1 Heating Mode Enum
# # unique_id: modbus_pe1_heating_mode_enum
# # slave: 2
# # input_type: holding
# # address: 8046
# # scan_interval: 30
# # device_class: enum
# # TEMPERATURES
# - name: Modbus PE1 Hotwater Temperature Top
# unique_id: modbus_pe1_hotwater_temperature_top
# slave: 2
# input_type: input
# # 31631 - 30001 = 1630
# address: 1630
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Furnace Temperature
# unique_id: modbus_pe1_furnace_temperature
# slave: 2
# input_type: input
# address: 0
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Calculated Heater Target Temperature
# unique_id: modbus_pe1_calculated_heater_target_temperature
# slave: 2
# input_type: input
# address: 27
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Return Flow Temperature
# unique_id: modbus_pe1_return_flow_temperature
# slave: 2
# input_type: input
# address: 9
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Calculated Target Return Flow Temperature
# unique_id: modbus_pe1_calculated_target_return_flow_temperature
# slave: 2
# input_type: input
# address: 66
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Target Flow Temperature At Circulation Line
# unique_id: modbus_pe1_return_flow_temperature_at_circulation_line
# slave: 2
# input_type: input
# address: 711
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Outside Temperature
# unique_id: modbus_pe1_outside_temperature
# slave: 2
# input_type: input
# address: 1000
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Flow Temperature Actual
# unique_id: modbus_pe1_flow_temperature_actual
# slave: 2
# input_type: input
# address: 1030
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Flow Temperature Target
# unique_id: modbus_pe1_flow_temperature_target
# slave: 2
# input_type: input
# address: 1031
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Room Temperature
# unique_id: modbus_pe1_room_temperature
# slave: 2
# input_type: input
# address: 1032
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Buffer Temperature Top
# unique_id: modbus_pe1_buffer_temperature_top
# slave: 2
# data_type: float16
# input_type: input
# address: 30121
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# - name: Modbus PE1 Buffer Temperature Bottom
# unique_id: modbus_pe1_buffer_temperature_bottom
# slave: 2
# input_type: input
# data_type: float16
# address: 30119
# scan_interval: 30
# scale: 0.5
# precision: 0
# device_class: temperature
# state_class: measurement
# unit_of_measurement: °C
# # CONSUMPTION
# - name: Modbus PE1 Total Pellet Consumption
# unique_id: modbus_pe1_total_pellet_consumption
# slave: 2
# input_type: input
# address: 83
# scan_interval: 3600
# scale: 0.1
# precision: 1
# device_class: weight
# state_class: total_increasing
# unit_of_measurement: mg
# - name: Modbus PE1 Pellet Consumption KG
# unique_id: modbus_pe1_pellet_consumption_kg
# slave: 2
# input_type: input
# address: 81
# scan_interval: 3600
# scale: 1
# precision: 0
# device_class: weight
# state_class: total
# unit_of_measurement: kg
# - name: Modbus PE1 Pellet Fill Level
# unique_id: modbus_pe1_pellet_fill_level
# slave: 2
# input_type: input
# address: 21
# scan_interval: 3600
# scale: 0.005
# precision: 1
# device_class: battery
# state_class: measurement
# unit_of_measurement: '%'
# # PUMP CONTROLS
# - name: Modbus PE1 Return Flow Pump Control
# unique_id: modbus_pe1_return_flow_pump_control
# slave: 2
# input_type: input
# address: 36
# scan_interval: 30
# scale: 1
# precision: 0
# device_class: power_factor
# state_class: measurement
# unit_of_measurement: '%'
# - name: Modbus PE1 Water Boiler Pump Control
# unique_id: modbus_pe1_water_boiler_pump_control
# slave: 2
# input_type: input
# address: 1632
# scan_interval: 30
# scale: 1
# precision: 0
# device_class: power_factor
# state_class: measurement
# unit_of_measurement: '%'
# - name: Modbus PE1 Buffer Pump Control
# unique_id: modbus_pe1_buffer_pump_control
# slave: 2
# input_type: input
# address: 2003
# scan_interval: 30
# scale: 1
# precision: 0
# device_class: power_factor
# state_class: measurement
# unit_of_measurement: '%'
# # BUFFER CHARGE
# - name: Modbus PE1 Buffer Charge
# unique_id: modbus_pe1_buffer_charge
# slave: 2
# input_type: input
# address: 2006
# scan_interval: 30
# scale: 1
# precision: 0
# device_class: battery
# state_class: measurement
# unit_of_measurement: '%'
# # MISC
# - name: Modbus PE1 Operating Hours
# unique_id: modbus_pe1_operating_hours
# slave: 2
# input_type: input
# address: 20
# scan_interval: 3600
# scale: 1
# precision: 0
# device_class: duration
# state_class: total_increasing
# unit_of_measurement: 'h'
# - name: Modbus PE1 Hours Since Last Maintenance
# unique_id: modbus_pe1_hours_since_last_maintenance
# slave: 2
# input_type: input
# address: 55
# scan_interval: 3600
# scale: 1
# precision: 0
# device_class: duration
# state_class: total
# unit_of_measurement: 'h'
# - name: Modbus PE1 Hours Of Pellets Operation
# unique_id: modbus_pe1_hours_of_pellets_operation
# slave: 2
# input_type: input
# address: 62
# scan_interval: 3600
# scale: 1
# precision: 0
# device_class: duration
# state_class: total_increasing
# unit_of_measurement: 'h'
# - name: Modbus PE1 Hours Of Heating
# unique_id: modbus_pe1_hours_of_heating
# slave: 2
# input_type: input
# address: 63
# scan_interval: 3600
# scale: 1
# precision: 0
# device_class: duration
# state_class: total_increasing
# unit_of_measurement: 'h'
# - name: Modbus PE1 Hours Until Ash Removal
# unique_id: modbus_pe1_hours_until_ash_removal
# slave: 2
# input_type: input
# address: 86
# scan_interval: 3600
# scale: 1
# precision: 0
# device_class: duration
# state_class: measurement
# unit_of_measurement: 'h'

View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"*.yaml": "home-assistant"
}
}

View File

@@ -0,0 +1,36 @@
---
- unique_id: "door_swith_04"
name: "porte bureau"
state_topic: "switch/porte/bureau"
# command_topic: "switch/porte/bureau"
device_class: door
payload_on: "1"
payload_off: "0"
#- unique_id: "door_swith_02"
# name: "porte garage"
# state_topic: "arduino/garage/p_garage"
# command_topic: "switch/porte/bureau"
# device_class: door
# payload_on: "OPEN"
# payload_off: "CLOSED"
#- unique_id: "door_swith_03"
# name: "porte cave"
# state_topic: "switch/porte/cave"
# command_topic: "switch/porte/bureau"
# device_class: door
# payload_on: "1"
# payload_off: "0"
#----------------------------------------
#
# Bouton poussoir
#
#----------------------------------------
#- unique_id: switch_chambre_01
# name: "Chambre1"
# state_topic: "switch/chambre"
# command_topic: "switch/porte/bureau"
# device_class: light
# payload_on: "1"
# payload_off: "0"

View File

@@ -0,0 +1,19 @@
######################################################################
## chaudiere
## digital inputs
# input_type: discrete_input
## FC=02 lire discrete inputs de 10001-10086 enlever 1 a register
######################################################################
- unique_id: "porte_chaudiere"
state_topic: "chaudiere/porte"
#device_class: "opening"
name: "porte chaudiere"
payload_on: "1"
payload_off: "0"
device_class: door
#state_topic: "switch/porte/chaudiere"
# command_topic: "switch/porte/bureau"

View File

@@ -0,0 +1,62 @@
#----------------------------------------
#
# RFXCOM with Node-red
#
#----------------------------------------
- unique_id: "AC/0x011E611E/1"
name: "applique_salon"
schema: json
state_topic: "home/light/a_salon"
command_topic: "home/light/a_salon/set"
brightness: true
rgb: false
brightness_scale: 100
# state_topic: "home/app_salon/light/status"
# command_topic: "home/app_salon/light/switch"
# brightness_state_topic: 'home/app_salon/light/brightness'
# brightness_command_topic: 'home/app_salon/light/brightness/set'
# qos: 0
# payload_on: "On"
# payload_off: "Off"
# optimistic: false
- unique_id: "AC/0x011E611E/2"
name: "applique_cuisine"
schema: json
state_topic: "home/light/a_cuisine"
command_topic: "home/light/a_cuisine/set"
brightness: true
rgb: false
brightness_scale: 100
# state_topic: "home/app_salon/light/status"
# command_topic: "home/app_salon/light/switch"
# brightness_state_topic: 'home/app_salon/light/brightness'
# brightness_command_topic: 'home/app_salon/light/brightness/set'
# qos: 0
# payload_on: "On"
# payload_off: "Off"
# optimistic: false
- unique_id: "AC/0x011E611E/4"
name: "plafond_cuisine"
schema: json
state_topic: "home/light/p_cuisine"
command_topic: "home/light/p_cuisine/set"
brightness: true
rgb: false
brightness_scale: 100
# state_topic: "home/app_salon/light/status"
# command_topic: "home/app_salon/light/switch"
# brightness_state_topic: 'home/app_salon/light/brightness'
# brightness_command_topic: 'home/app_salon/light/brightness/set'
# qos: 0
# payload_on: "On"
# payload_off: "Off"
# optimistic: false

View File

@@ -0,0 +1,10 @@
sensor:
!include_dir_merge_list sensor/
switch:
!include_dir_merge_list switch/
light:
!include_dir_merge_list light/
binary_sensor:
!include_dir_merge_list binary_sensor/
number:
!include_dir_merge_list number/

View File

@@ -0,0 +1,8 @@
# - unique_id: mqtt_servo1
# name: mqtt_servo
# state_topic: arduinomegatest/servo/status
# command_topic: arduinomegatest/servo/cmnd
# min: 0
# max: 100
# step: 1
# icon: mdi:engine

View File

@@ -0,0 +1,66 @@
#----------------------------------------
#
# lux
#
#----------------------------------------
#- unique_id: luminosite_salon_01
# state_topic: "sensor/lux"
# name: "Luminosité"
# device_class: illuminance
# unit_of_measurement: "lx"
# value_template: "{{ value | round(1) }}"
#- unique_id: courant_t_div_01
# state_topic: "arduino/garage/intensite"
# name: "Intensité t_divisionnaire"
# unit_of_measurement: "A"
# device_class: current
# value_template: "{{ value | round(1) }}"
#- unique_id: power_t_div_01
# state_topic: "arduino/garage/puissance"
# name: "Puissance t_divisionnaire"
# device_class: power
# unit_of_measurement: "W"
# value_template: "{{ value | round(1) }}"
#----------------------------------------
#
# chauffage reglage PID
#
#----------------------------------------
- unique_id: "chauff_pid_payload_01"
state_topic: "sensor/chauff/pid/payload"
name: "payload"
- unique_id: "chauff_pid_pv_01"
state_topic: "sensor/chauff/pid/pv"
name: "pv"
value_template: "{{ value | round(3) }}"
- unique_id: "chauff_pid_setpoint_01"
state_topic: "sensor/chauff/pid/setpoint"
name: "setpoint"
- unique_id: "chauff_pid_proportional_01"
state_topic: "sensor/chauff/pid/proportional"
name: "P"
value_template: "{{ value | round(3) }}"
- unique_id: "chauff_pid_integral_01"
state_topic: "sensor/chauff/pid/integral"
name: "I"
value_template: "{{ value | round(3) }}"
- unique_id: "chauff_pid_pderivative_01"
state_topic: "sensor/chauff/pid/derivative"
name: "D"
value_template: "{{ value | round(3) }}"
- unique_id: tempe_pc_domotic
state_topic: "domotic/ISAadapter/Package id 0"
name: "Temp PC Domotic"
device_class: temperature
unit_of_measurement: "°C"
value_template: "{{ value | round(1) }}"

View File

@@ -0,0 +1,33 @@
#arduino mega avec pzem
- unique_id: "tab_domo_voltage"
state_topic: "comble/pzem/voltage"
name: "voltage_tableau_domotique"
device_class: voltage
unit_of_measurement: "V"
state_class: measurement
value_template: "{{ value | round(12) }}"
- unique_id: "tab_domo_current"
state_topic: "comble/pzem/courant"
name: "courant_tableau_domotique"
device_class: current
unit_of_measurement: "A"
state_class: measurement
value_template: "{{ value | round(2) }}"
- unique_id: "tab_domo_power"
state_topic: "comble/pzem/power"
name: "Power_tableau_domotique"
icon: mdi:solar-power
device_class: power
unit_of_measurement: "W"
state_class: measurement
value_template: "{{ value | round(1) }}"
- unique_id: "tab_domo_energy_total"
state_topic: "comble/pzem/energy"
name: "tableau_domotique_Energy"
device_class: energy
state_class: total_increasing
unit_of_measurement: "Wh"
value_template: "{{ value | round(2) }}"

View File

@@ -0,0 +1,97 @@
######################################################################
## chaudiere
## actual value - input register
# register_type: input
## FC=04 de 30001-30272 enlever 1 a register
######################################################################
- unique_id: temp_chaud_01
state_topic: "chaudiere/t_chaud"
unit_of_measurement: "°C"
device_class: temperature
name: "T° chaudiere"
- unique_id: temp_fumee_01
state_topic: "chaudiere/t_fumee"
name: "T° fumée"
device_class: temperature
unit_of_measurement: "°C"
- unique_id: temp_board_01
state_topic: "chaudiere/t_board"
name: "T° board"
device_class: temperature
unit_of_measurement: "°C"
- unique_id: temp_ext_N_01
state_topic: "chaudiere/t_ext"
name: "T°exterieur nord"
device_class: temperature
unit_of_measurement: "°C"
- unique_id: venti_chaud_01
state_topic: "chaudiere/vit_venti"
name: "Ventilation"
unit_of_measurement: "U/min"
- unique_id: heure_chauff_01
state_topic: "chaudiere/h_chauffage"
name: "Heure de chauffage"
unit_of_measurement: "h"
- unique_id: heure_serv_01
state_topic: "chaudiere/h_service"
name: "Heure de service"
unit_of_measurement: "h"
- unique_id: temp_dep_chauff_01
state_topic: "chaudiere/t_dep_chauff"
name: "T° départ"
device_class: temperature
unit_of_measurement: "°C"
- unique_id: charge_tampon_01
state_topic: "chaudiere/charge_tampon"
name: "Charge du tampon"
- unique_id: tampon_haut_01
state_topic: "chaudiere/tampon_haut"
name: "tampon haut"
device_class: temperature
unit_of_measurement: "°C"
- unique_id: tampon_bas_01
state_topic: "chaudiere/tampon_bas"
name: "tampon bas"
device_class: temperature
unit_of_measurement: "°C"
- unique_id: temp_cons_fumee_01
state_topic: "chaudiere/cons_t_fumee"
name: "Consigne T° fumée"
device_class: temperature
unit_of_measurement: "°C"
- unique_id: etat_chaud_01
state_topic: "chaudiere/etat_chaudiere"
name: "Etat chaudiere"
- unique_id: O2_resi_01
state_topic: "chaudiere/O2_res"
name: "O2_residuel"
######################################################################
## chaudiere
## parameter - holding registers
# register_type: holding
## FC=03 de 40001-41094 enlever 1 a register
######################################################################

View File

@@ -0,0 +1,40 @@
# pc portable lenovo battery
- unique_id: portable_lenovo_battery
name: portable_lenovo_battery
device_class: battery
unit_of_measurement: "%"
# payload_on: low
# payload_off: normal
qos: 1
#availability_topic: home/downstairs/living-room/front-windows/availability
#payload_available: online
#payload_not_available: offline
state_topic: computer/lenovo
value_template: "{{ value_json.percent }}"
- unique_id: portable_elitebook_corei5
name: portable_elitebook_corei5
device_class: battery
unit_of_measurement: "%"
# payload_on: low
# payload_off: normal
qos: 1
#availability_topic: home/downstairs/living-room/front-windows/availability
#payload_available: online
#payload_not_available: offline
state_topic: computer/corei5
value_template: "{{ value_json.percent }}"
- unique_id: portable_elitebook_centrino
name: portable_elitebook_centrino
device_class: battery
unit_of_measurement: "%"
# payload_on: low
# payload_off: normal
qos: 1
#availability_topic: home/downstairs/living-room/front-windows/availability
#payload_available: online
#payload_not_available: offline
state_topic: computer/centrino
value_template: "{{ value_json.percent }}"

View File

@@ -0,0 +1,244 @@
# # modbus sensors for EPEver
# - unique_id: "array_voltage"
# ## reg 3100
# state_topic: "tracer/sensor/tracer_1_pv_array_voltage"
# name: "solar_PV_Voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(12) }}"
# - unique_id: "array_current"
# ## reg 3101
# state_topic: "tracer/sensor/tracer_1_pv_array_current"
# name: "solar_PV_Current"
# device_class: current
# unit_of_measurement: 'A'
# state_class: measurement
# value_template: "{{ value | round(2) }}"
# - unique_id: "array_power"
# ## reg 3102 3103
# state_topic: "tracer/sensor/tracer_1_pv_array_power"
# name: "solar_PV_Power"
# icon: mdi:solar-power
# device_class: power
# unit_of_measurement: 'W'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - unique_id: "battery_voltage"
# ## reg 3104
# state_topic: "tracer/sensor/tracer_1_battery_voltage"
# name: "solar_Battery_Voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - unique_id: "battery_charging_current"
# ##reg 3105
# state_topic: "tracer/sensor/tracer_1_battery_charging_current"
# name: "Battery_charging_Current"
# device_class: current
# unit_of_measurement: 'A'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - unique_id: "max_battery_voltage_today_01"
# #reg 3302
# state_topic: "tracer/sensor/tracer_1_max_battery_voltage_today"
# name: "solar_Battery_Max_Voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - unique_id: "min_battery_voltage_today_01"
# #reg 3303
# state_topic: "tracer/sensor/tracer_1_min_battery_voltage_today"
# name: "solar_Battery_Min_Voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - unique_id: "net_battery_current"
# ##reg 331B-331C
# state_topic: "tracer/sensor/tracer_1_net_battery_current"
# name: "net_Battery_Current"
# device_class: current
# unit_of_measurement: 'A'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - unique_id: "battery_power"
# state_topic: "tracer/registers/Charging equipment output power/value"
# name: "solar_Battery_Power"
# device_class: power
# unit_of_measurement: 'W'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - unique_id: "battery_temperature"
# #reg 3110
# state_topic: "tracer/sensor/tracer_1_battery_temperature"
# name: "solar_Battery_Temperature"
# icon: mdi:thermometer
# device_class: temperature
# state_class: measurement
# unit_of_measurement: '°C'
# value_template: "{{ value | round(1) }}"
# - unique_id: "battery_SOC"
# #reg 311A
# state_topic: "tracer/sensor/tracer_1_battery_soc"
# name: "solar_Battery_SOC"
# unit_of_measurement: '%'
# value_template: "{{ value | round(1) }}"
# - unique_id: "device_temperature"
# #reg 3111
# state_topic: "tracer/sensor/tracer_1_charger_temperature"
# name: "solar_device_Temperature"
# device_class: temperature
# state_class: measurement
# unit_of_measurement: '°C'
# value_template: "{{ value | round(1) }}"
# - unique_id: "solar_PV_status"
# ## reg 3201
# state_topic: "tracer/sensor/tracer_1_equipment_status"
# name: "solar_PV_Status"
# # device_class: energy
# #unit_of_measurement: 'kW'
# value_template: "{{ value }}"
# - unique_id: "battery_status"
# ## reg 3200
# state_topic: "tracer/sensor/tracer_1_battery_status"
# name: "solar_battery_Status"
# value_template: "{{ value }}"
# - unique_id: "generated_energy_today"
# ## reg 330C-330D
# state_topic: "tracer/sensor/tracer_1_generated_energy_today"
# name: "generated_Today_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - unique_id: "generated_energy_total"
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_total_generated_energy"
# name: "generated_total_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - unique_id: "generated_energy_month"
# ## reg 330E-330F
# state_topic: "tracer/sensor/tracer_1_generated_energy_this_month"
# name: "generated_month_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - unique_id: "generated_energy_year"
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_generated_energy_this_year"
# name: "generated_year_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - unique_id: "load power"
# ##reg 310E-310F
# state_topic: "tracer/sensor/tracer_1_load_power"
# name: "DC_Load_Power"
# device_class: power
# unit_of_measurement: 'W'
# state_class: measurement
# value_template: "{{ value | round(2) }}"
# - unique_id: "load voltage"
# ## reg 310C
# state_topic: "tracer/sensor/tracer_1_load_voltage"
# name: "DC_Load_Voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(2) }}"
# - unique_id: "load current"
# ## reg 310D
# state_topic: "tracer/sensor/tracer_1_load_current"
# name: "DC_Load_Courant"
# device_class: current
# unit_of_measurement: 'A'
# state_class: measurement
# value_template: "{{ value | round(2) }}"
# - unique_id: "tracer_1_day_night"
# ## reg 200C
# state_topic: "tracer/sensor/tracer_1_day/night"
# name: "solar_Day_Night"
# #device_class: energy
# #unit_of_measurement: 'kW'
# value_template: "{{ value }}"
# - unique_id: "energy_today"
# ## reg 330C-330D
# state_topic: "tracer/sensor/tracer_1_consumed_energy_today"
# name: "consumed_Today_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - unique_id: "consumed_energy_total"
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_total_consumed_energy"
# name: "consumed_Total_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - unique_id: "consumed_energy_month"
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_consumed_energy_this_month"
# name: "consumed_Month_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - unique_id: "consumed_energy_year"
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_consumed_energy_this_year"
# name: "consumed_Year_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - unique_id: "charging mode"
# ## reg 3201
# state_topic: "tracer/sensor/tracer_1_charging_mode"
# name: "charging mode"
# # device_class: energy
# #unit_of_measurement: 'kW'
# value_template: "{{ value | round(1) }}"
# - unique_id: "servo_pos1"
# ## reg 3201
# state_topic: "arduinomegatest/servo/status"
# name: "servo_pos"
# # device_class: energy
# #unit_of_measurement: 'kW'
# # value_template: "{{ value }}"

View File

@@ -0,0 +1,49 @@
# - unique_id: ecuR_courant
# state_topic: "stat/ecu_r/current"
# name: "courant ecuR"
# device_class: current
# unit_of_measurement: "A"
# value_template: "{{ value | round(1) }}"
# - unique_id: ecuR_tension
# state_topic: "stat/ecu_r/volt"
# name: "tension ecuR"
# device_class: voltage
# unit_of_measurement: "V"
# value_template: "{{ value | round(1) }}"
# - unique_id: ecuR_puissance
# state_topic: "stat/ecu_r/power"
# name: "puissance ecuR"
# device_class: power
# unit_of_measurement: "W"
# value_template: "{{ value | round(1) }}"
# - unique_id: ecuR_frequence
# state_topic: "stat/ecu_r/frequency"
# name: "frequence ecuR"
# device_class: frequency
# unit_of_measurement: "Hz"
# value_template: "{{ value | round(1) }}"
# - unique_id: ecuR_cos_phi
# state_topic: "stat/ecu_r/cos_phi"
# name: "cos Phi ecuR"
# device_class: power_factor
# unit_of_measurement: "%"
# value_template: "{{ value | round(1) }}"
# - unique_id: ecuR_temperature
# state_topic: "stat/ecu_r/temperature"
# name: "temperature ecuR"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
# - unique_id: ecuR_energy_today
# state_topic: "stat/ecu_r/energy_today"
# name: "energy_today ecuR"
# device_class: energy
# unit_of_measurement: "Wh"
# value_template: "{{ value | round(1) }}"
# state_class: total_increasing

View File

@@ -0,0 +1,62 @@
---
#----------------------------------------
#
# temperature
#
#----------------------------------------
- unique_id: temp_bureau_01
state_topic: "temp/bureau"
name: "T Bureau"
device_class: temperature
unit_of_measurement: "°C"
value_template: "{{ value | round(1) }}"
#- unique_id: temp_cave_01
# state_topic: "temp/cave"
# name: "T Cave"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
#- unique_id: temp_garage_01
# state_topic: "temp/garage"
# name: "T Garage"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
- unique_id: temp_chambre_01
state_topic: "temp/chambre1"
name: "T Chambre1"
device_class: temperature
unit_of_measurement: "°C"
value_template: "{{ value | round(1) }}"
- unique_id: temp_chambre_02
state_topic: "temp/chambre2"
name: "T Chambre2"
unit_of_measurement: "°C"
device_class: temperature
value_template: "{{ value | round(1) }}"
- unique_id: temp_comble_01
state_topic: "temp/comble"
name: "T Comble"
device_class: temperature
unit_of_measurement: "°C"
value_template: "{{ value | round(1) }}"
- unique_id: hum_sdb_01
state_topic: "hum/sdb"
name: "H% salle de bain"
device_class: humidity
unit_of_measurement: "%"
value_template: "{{ value | round(1) }}"
- unique_id: temp_sdb_01
state_topic: "temp/sdb"
name: "T salle de bain"
device_class: temperature
unit_of_measurement: "°C"
value_template: "{{ value | round(1) }}"

View File

@@ -0,0 +1,17 @@
---
- unique_id: bedroom_switch
name: "Bedroom Switch"
state_topic: "home/bedroom/switch1"
command_topic: "home/bedroom/switch1/set"
availability:
- topic: "home/bedroom/switch1/available"
payload_not_available: "offline"
payload_available: "online"
payload_on: "ON"
payload_off: "OFF"
state_on: "ON"
state_off: "OFF"
optimistic: false
qos: 0
retain: true

View File

@@ -0,0 +1,28 @@
#----------------------------------------
#
# on off konica
#
#----------------------------------------
- unique_id: switch_konica_01
name: "switch_konica"
command_topic: "cmnd/konica/power"
state_topic: "stat/konica/power"
payload_on: "on"
payload_off: "off"
#----------------------------------------
#
# relay chaudiere
#
#----------------------------------------
- unique_id: relay_chaudiere_01
name: "relay_chauffage"
command_topic: "cmnd/relay/chaudiere"
state_topic: "stat/relay/chaudiere"
payload_on: "1"
payload_off: "0"

View File

@@ -0,0 +1,647 @@
#openhasp:
plate1:
objects:
- obj: "p0b2"
properties:
"text": '{{ states("sensor.ecowitt_temp")| float | round(1) }}°C'
- obj: "p0b1" # temperature label on all pages
properties:
"text": '{{ states("sensor.ecowitt_tempin")| float | round(1)}}°C'
#page 1 sensor:
- obj: "p1b11" # temperature label on all pages
properties:
"text": '{{ states("sensor.temp_fumee")| float | round(1) }}°C'
- obj: "p1b13" # temperature label on all pages
properties:
"text": '{{ states("sensor.temp_depart_chauffage")| float | round(1) }}°C'
#page 1 light:
- obj: "p1b1" # light-switch toggle button
properties:
"val": '{{ 1 if states("light.lumieres_couloir") == "on" else 0 }}'
"text": '{{ "\uE6E8" if is_state("light.lumieres_couloir", "on") else "\uE335" | e }}'
event:
"up":
- service: homeassistant.toggle
entity_id: "light.lumieres_couloir"
- obj: "p1b2" # light-switch toggle button
properties:
"val": '{{ 1 if states("light.applique_salon") == "on" else 0 }}'
"text": '{{ "\uE6E8" if is_state("light.applique_salon", "on") else "\uE335" | e }}'
event:
"up":
- service: homeassistant.toggle
entity_id: "light.applique_salon"
- obj: "p1b3" # light-switch toggle button
properties:
"val": '{{ 1 if states("light.applique_cuisine") == "on" else 0 }}'
"text": '{{ "\uE6E8" if is_state("light.applique_cuisine", "on") else "\uE335" | e }}'
event:
"up":
- service: homeassistant.toggle
entity_id: "light.applique_cuisine"
- obj: "p1b4" # light-switch toggle button
properties:
"val": '{{ 1 if states("light.lumieres_escaliers") == "on" else 0 }}'
"text": '{{ "\uE6E8" if is_state("light.lumieres_escaliers", "on") else "\uE335" | e }}'
event:
"up":
- service: homeassistant.toggle
entity_id: "light.lumieres_escaliers"
- obj: "p1b7" # light-switch toggle button
properties:
"val": '{{ 1 if states("light.eclairage_bois") == "on" else 0 }}'
"text": '{{ "\uE6E8" if is_state("light.eclairage_bois", "on") else "\uE335" | e }}'
event:
"up":
- service: homeassistant.toggle
entity_id: "light.eclairage_bois"
- obj: "p1b8" # light-switch toggle button
properties:
"val": '{{ 1 if states("light.yeelight_color4_0x15771126") == "on" else 0 }}'
"text": '{{ "\uE6E8" if is_state("light.yeelight_color4_0x15771126", "on") else "\uE335" | e }}'
event:
"up":
- service: homeassistant.toggle
entity_id: "light.yeelight_color4_0x15771126"
- obj: "p1b9" # light-switch toggle button
properties:
"val": '{{ 1 if states("switch.kc868_a8_d758d0_d758d0_lumiere_comble_kc") == "on" else 0 }}'
"text": '{{ "\uE6E8" if is_state("switch.kc868_a8_d758d0_d758d0_lumiere_comble_kc", "on") else "\uE335" | e }}'
event:
"up":
- service: homeassistant.toggle
entity_id: "switch.kc868_a8_d758d0_d758d0_lumiere_comble_kc"
#page 2 light
- obj: "p2b21" # Light brightness
properties:
"val": "{{ state_attr('light.applique_salon', 'brightness') if state_attr('light.applique_salon', 'brightness') != None else 0 }}"
event:
"changed":
- service: light.turn_on
data:
entity_id: light.applique_salon
brightness: "{{ val }}"
"up":
- service: light.turn_on
data:
entity_id: light.applique_salon
brightness: "{{ val }}"
- obj: "p2b23" # Light brightness
properties:
"val": "{{ state_attr('light.lumieres_plafond', 'brightness') if state_attr('light.lumieres_plafond', 'brightness') != None else 0 }}"
event:
"changed":
- service: light.turn_on
data:
entity_id: light.lumieres_plafond
brightness: "{{ val }}"
"up":
- service: light.turn_on
data:
entity_id: light.lumieres_plafond
brightness: "{{ val }}"
- obj: "p2b25" # light-switch toggle button
properties:
"val": '{{ 1 if states("light.eclairage_bois") == "on" else 0 }}'
"text": '{{ "\uE6E8" if is_state("light.eclairage_bois", "on") else "\uE335" | e }}'
event:
"up":
- service: homeassistant.toggle
entity_id: "light.eclairage_bois"
#thermostat
- obj: "p3b20" # arc slider
properties:
"val": >
{% if state_attr('climate.salon','temperature') is not none %}
{{ state_attr('climate.salon','temperature') | int * 10 }}
{%- endif %}
"min": >
{% if state_attr('climate.salon','min_temp') is not none %}
{{ state_attr('climate.salon','min_temp') | int * 10 }}
{%- endif %}
"max": >
{% if state_attr('climate.salon','max_temp') is not none %}
{{ state_attr('climate.salon','max_temp') | int * 10 }}
{%- endif %}
"opacity": "{{ 60 if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 255 }}"
"click": "{{ 'false' if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 'true' }}"
"line_color1": >
{% if is_state('climate.salon', 'cool') %}
{{ "#346beb" }}
{%-elif is_state('climate.salon', 'heat_cool') %}
{{ "#34bdeb" }}
{%-elif is_state('climate.salon', 'heat') %}
{{ "#eb3434" }}
{%-elif is_state('climate.salon', 'dry') %}
{{ "#ebeb34" }}
{%-elif is_state('climate.salon', 'fan_only') %}
{{ "#34eb77" }}
{%-else %}
{{ "#9f96b0" }}
{% endif %}
event:
"changed":
- service: climate.set_temperature
target:
entity_id: climate.salon
data:
temperature: "{{ val | int / 10 }}"
"up":
- service: climate.set_temperature
target:
entity_id: climate.salon
data:
temperature: "{{ val | int / 10 }}"
- obj: "p3b21" # gauge current temp
properties:
"val": >
{% if not (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) %}
{{ states('climate.salon') | float (default=0) * 10 }}
{%- endif %}
"min": >
{% if state_attr('climate.salon','min_temp') is not none %}
{{ state_attr('climate.salon','min_temp') | int * 10 }}
{%- endif %}
"max": >
{% if state_attr('climate.salon','max_temp') is not none %}
{{ state_attr('climate.salon','max_temp') | int * 10 }}
{%- endif %}
"critical_value": >
{% if state_attr('climate.salon','max_temp') is not none %}
{{ state_attr('climate.salon','max_temp') | int * 10 + 1 }}
{%- endif %}
"label_count": >
{% if state_attr('climate.salon','max_temp') is not none %}
{{ state_attr('climate.salon','max_temp') | int - state_attr('climate.salon','min_temp') | int + 1 }}
{%- endif %}
"line_count": >
{% if state_attr('climate.salon','max_temp') is not none %}
{{ (state_attr('climate.salon','max_temp') | int - state_attr('climate.salon','min_temp') | int) * 2 + 1 }}
{%- endif %}
"opacity": "{{ 60 if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 255 }}"
- obj: "p3b23" # label current temp (and +/- with short/long touch)
properties:
"text": >
{% if (is_state('sensor.ecowitt_tempin','unavailable') or is_state('sensor.ecowitt_tempin','unknown')) %}
{{ "--.-" }}
{%-else %}
{{ states('sensor.ecowitt_tempin') | round(1,default=0) }}
{%- endif %}
"click": "{{ 'false' if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 'true' }}"
"opacity": "{{ 60 if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 255 }}"
event:
"up":
- service: climate.set_temperature
target:
entity_id: climate.salon
data:
temperature: "{{ state_attr('climate.salon','temperature') + state_attr('climate.salon','target_temp_step') | float(default=1)}}"
"long":
- service: climate.set_temperature
target:
entity_id: climate.salon
data:
temperature: "{{ state_attr('climate.salon','temperature') - state_attr('climate.salon','target_temp_step') | float(default=1)}}"
- obj: "p3b25" # label target temp
properties:
"text": >
{% if state_attr('climate.salon','temperature') is not none %}
{{ state_attr('climate.salon','temperature') }}
{%- endif %}
"opacity": "{{ 60 if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 255 }}"
- obj: "p3b41" # on/off switch
properties:
"val": "{{ 0 if (is_state('climate.salon', 'off') or is_state('climate.salon', 'unavailable')) else 1 }}"
"click": "{{ 'false' if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 'true' }}"
"opacity": "{{ 60 if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 255 }}"
event:
"down":
- service_template: >
{% if val == 0 -%}
climate.turn_on
{% else -%}
climate.turn_off
{% endif -%}
entity_id: "climate.salon"
- obj: "p3b30" # tab dots
event:
"changed":
- service: openhasp.command
target:
entity_id: openhasp.your_plate
data:
keyword: p3b26.text
parameters: >
{% if val == 0 %}
{{ "#000000 \u2022# #909090 \u2022# #909090 \u2022#" | e }}
{%-elif val == 1 %}
{{ "#909090 \u2022# #000000 \u2022# #909090 \u2022#" | e }}
{%-elif val == 2 %}
{{ "#909090 \u2022# #909090 \u2022# #000000 \u2022#" | e }}
{% endif %}
- obj: "p3b42" # dropdown with fan_modes
properties:
"options": >
{% if state_attr('climate.salon','fan_modes') is not none %}{%for mode in state_attr('climate.salon','fan_modes')%}
{%- if mode == 'auto' -%}
Automatic{{"\n"|e}}
{%- elif mode == 'low' -%}
Low{{"\n"|e}}
{%- elif mode == 'medium' -%}
Medium{{"\n"|e}}
{%- elif mode == 'high' -%}
High{{"\n"|e}}
{%- elif mode == 'turbo' -%}
Turbo{{"\n"|e}}
{%- endif -%}
{%-if not loop.last%}{%-endif%}{%-endfor%}{% endif %}
"click": "{{ 'false' if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 'true' }}"
"val": >
{% if not (is_state('climate.salon','unavailable')) %}{%for mode in state_attr('climate.salon','fan_modes')%}
{{loop.index -1 if mode == state_attr('climate.salon','fan_mode') }}
{%-endfor%}{% endif %}
event:
"changed":
- service: climate.set_fan_mode
target:
entity_id: climate.salon
data:
fan_mode: >
{% if text == "Automatic" -%}
auto
{% elif text == 'Low' -%}
low
{% elif text == 'Medium' -%}
medium
{% elif text == 'High' -%}
high
{% elif text == 'Turbo' -%}
turbo
{% endif -%}
- obj: "p3b43" # dropdown with hvac_modes
properties:
"options": >
{% if state_attr('climate.salon','hvac_modes') is not none %}{%for mode in state_attr('climate.salon','hvac_modes')%}
{%- if mode == 'off' -%}
Off{{"\n"|e}}
{%- elif mode == 'heat' -%}
Heating{{"\n"|e}}
{%- elif mode == 'cool' -%}
Cooling{{"\n"|e}}
{%- elif mode == 'heat_cool' -%}
Heat/Cool{{"\n"|e}}
{%- elif mode == 'dry' -%}
Drying{{"\n"|e}}
{%- elif mode == 'fan_only' -%}
Fan only{{"\n"|e}}
{%- else -%}
On{{"\n"|e}}
{%- endif -%}
{%-if not loop.last%}{%-endif%}{%-endfor%}{% endif %}
"click": "{{ 'false' if (is_state('climate.salon','unavailable') or is_state('climate.salon','unknown')) else 'true' }}"
"val": >
{% if not (is_state('climate.salon','unavailable')) %}{%for mode in state_attr('climate.salon','hvac_modes')%}
{{loop.index -1 if mode == states('climate.salon') }}
{%-endfor%}{% endif %}
event:
"changed":
- service: climate.set_hvac_mode
target:
entity_id: climate.salon
data:
hvac_mode: >
{% if text == "Off" -%}
off
{% elif text == 'Heating' -%}
heat
{% elif text == 'Cooling' -%}
cool
{% elif text == 'Heat/Cool' -%}
heat_cool
{% elif text == 'Drying' -%}
dry
{% elif text == 'Fan only' -%}
fan_only
{% endif -%}
# solar page4
- obj: "p4b41" # light-switch toggle button
properties:
"val": '{{ 1 if states("switch.relay_inverter_1") == "on" else 0 }}'
"text": '{{ "\uE6E8" if is_state("switch.relay_inverter_1", "on") else "\uE335" | e }}'
event:
"up":
- service: homeassistant.toggle
entity_id: "switch.relay_inverter_1"
- obj: "p4b43" # temperature label on all pages
properties:
"text": '{{ states("sensor.pv_power_3") }}W'
- obj: "p4b45" # temperature label on all pages
properties:
"text": '{{ states("sensor.tacsolar_power") }}W'
- obj: "p4b47" # temperature label on all pages
properties:
"text": '{{ states("sensor.power_compteur") }}W'
- obj: "p4b49" # temperature label on all pages
properties:
"text": '{{ states("sensor.battery_state_of_charge_2") }}%'
# weather section
- obj: "p5b14" # Icon
properties:
"src": "{{ 'L:/w-128-' + states('weather.openweathermap') + '.png' if not is_state('weather.openweathermap','unavailable') }}"
- obj: "p5b15" # Current date (adjust format to your needs)
properties:
"text": >
{%- if not is_state('weather.openweathermap','unavailable') %}
{%- set day = (states.weather.openweathermap.last_changed).strftime('%w') %}
{%- set days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] %}
{{- days[ day | int -1 ] }} {{ (states.weather.openweathermap.last_changed).strftime('%m. %d. ') }}
{% endif -%}
- obj: "p5b16" # Current temp (you can use your own outdoor temp sensor if you have one)
properties:
"text": "{{ state_attr('weather.openweathermap','temperature') |string + '°C' if not is_state('weather.openweathermap','unavailable') }}" # or "{{ states('sensor.your_own_temp_sensor') if not is_state('sensor.your_own_temp_sensor','unavailable') else '--' }}°C"
- obj: "p5b17" # Current weather condition
properties:
"text": >
{% if is_state('weather.openweathermap','clear-night') -%}
Clear night
{% elif is_state('weather.openweathermap','cloudy') -%}
Cloudy
{% elif is_state('weather.openweathermap','fog') -%}
Fog
{% elif is_state('weather.openweathermap','hail') -%}
Hail
{% elif is_state('weather.openweathermap','lightning') -%}
Lightning
{% elif is_state('weather.openweathermap','lightning-rainy') -%}
Thunderstorms
{% elif is_state('weather.openweathermap','partlycloudy') -%}
Partly cloudy
{% elif is_state('weather.openweathermap','pouring') -%}
Pouring rain
{% elif is_state('weather.openweathermap','rainy') -%}
Rainy
{% elif is_state('weather.openweathermap','snowy') -%}
Snowy
{% elif is_state('weather.openweathermap','snowy-rainy') -%}
Snowy-rainy
{% elif is_state('weather.openweathermap','sunny') -%}
Sunny
{% elif is_state('weather.openweathermap','windy') -%}
Windy
{% elif is_state('weather.openweathermap','windy-variant') -%}
Windy
{% elif is_state('weather.openweathermap','exceptional') -%}
Exceptional
{% elif is_state('weather.openweathermap','unavailable') -%}
(not available)
{% else -%}
{{ states('weather.openweathermap') }}
{% endif -%}
- obj: "p5b10" # tab dots - MAKE SURE YOU UPDATE THIS ONE!!
event:
"changed":
- service: openhasp.command
target:
entity_id: openhasp.your_plate
data:
keyword: p5b19.text
parameters: >
{% if val == 0 %}
{{ "#000000 \u2022# #909090 \u2022#" | e }}
{%-elif val == 1 %}
{{ "#909090 \u2022# #000000 \u2022#" | e }}
{% endif %}
- obj: "p5b21" # Forecast time +1h
properties:
"text": >
{%- if not is_state('weather.openweathermap','unavailable') %}
{%- set update = states('sensor.date') %}
{%- set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}
{%- set event = as_timestamp(strptime(state_attr('weather.openweathermap','forecast')[1]['datetime'], '%Y-%m-%dT%H:%M:%S%z', default='2020-01-00T00:00:00+00:00')) %}
{%- set delta = ((event - midnight) // 86400) | int %}
{%- if delta == 0 %}
Today
{%- elif delta == 1 %}
Tomorrow
{%- endif %}
{{- event | timestamp_custom(" %-I %p") }}
{%- endif %}
- obj: "p5b22" # Forecast temp +1h
properties:
"text": "{{ state_attr('weather.openweathermap','forecast')[1]['temperature'] if not is_state('weather.openweathermap','unavailable') else '-' }}"
- obj: "p5b23" # Forecast condition +1h
properties:
"src": >
{%- if not is_state('weather.openweathermap','unavailable') %}
L:/w-32-{{ state_attr('weather.openweathermap','forecast')[1]['condition'] }}.png
{%- endif %}
- obj: "p5b31" # Forecast time +2h (using Dawn/Morn etc instead of Today/Tomorrow)
properties:
"text": >
{%- if not is_state('weather.openweathermap','unavailable') %}
{%- set hour = as_timestamp(strptime(state_attr('weather.openweathermap','forecast')[3]['datetime'], '%Y-%m-%dT%H:%M:%S%z', default='2020-01-00T00:00:00+00:00')) | timestamp_custom("%-H") | int %}
{%- if 4 <= hour < 6 %}
Dawning
{%- elif 6 <= hour < 9 %}
Morning
{%- elif 9 <= hour < 12 %}
Forenoon
{%- elif 12 <= hour < 18 %}
Afternoon
{%- elif 18 <= hour < 23 %}
Evening
{%- elif 23 <= hour or hour < 4 %}
Night
{%- endif %}
{{- " " + hour |string + " o'clock" }}
{%- endif %}
- obj: "p5b32" # Forecast temp +2h
properties:
"text": "{{ state_attr('weather.openweathermap','forecast')[3]['temperature'] if not is_state('weather.openweathermap','unavailable') else '-' }}"
- obj: "p5b33" # Forecast condition +2h
properties:
"src": >
{%- if not is_state('weather.openweathermap','unavailable') %}
L:/w-32-{{ state_attr('weather.openweathermap','forecast')[3]['condition'] }}.png
{%- endif %}
- obj: "p5b41" # Forecast time +4h
properties:
"text": >
{%- if not is_state('weather.openweathermap','unavailable') %}
{%- set update = states('sensor.date') %}
{%- set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}
{%- set event = as_timestamp(strptime(state_attr('weather.openweathermap','forecast')[6]['datetime'], '%Y-%m-%dT%H:%M:%S%z', default='2020-01-00T00:00:00+00:00')) %}
{%- set delta = ((event - midnight) // 86400) | int %}
{%- if delta == 0 %}
Today
{%- elif delta == 1 %}
Tomorrow
{%- endif %}
{{- event | timestamp_custom(" %-I %p") }}
{%- endif %}
- obj: "p5b42" # Forecast temp +4h
properties:
"text": "{{ state_attr('weather.openweathermap','forecast')[6]['temperature'] if not is_state('weather.openweathermap','unavailable') else '-' }}"
- obj: "p5b43" # Forecast condition +4h
properties:
"src": >
{%- if not is_state('weather.openweathermap','unavailable') %}
L:/w-32-{{ state_attr('weather.openweathermap','forecast')[6]['condition'] }}.png
{%- endif %}
- obj: "p5b51" # Forecast time +8h
properties:
"text": >
{%- if not is_state('weather.openweathermap','unavailable') %}
{%- set update = states('sensor.date') %}
{%- set midnight = now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp() %}
{%- set event = as_timestamp(strptime(state_attr('weather.openweathermap','forecast')[12]['datetime'], '%Y-%m-%dT%H:%M:%S%z', default='2020-01-00T00:00:00+00:00')) %}
{%- set delta = ((event - midnight) // 86400) | int %}
{%- if delta == 0 %}
Today
{%- elif delta == 1 %}
Tomorrow
{%- endif %}
{{- event | timestamp_custom(" %-I %p") }}
{%- endif %}
- obj: "p5b52" # Forecast temp +8h
properties:
"text": "{{ state_attr('weather.openweathermap','forecast')[12]['temperature'] if not is_state('weather.openweathermap','unavailable') else '-' }}"
- obj: "p5b53" # Forecast condition +8h
properties:
"src": >
{%- if not is_state('weather.openweathermap','unavailable') %}
L:/w-32-{{ state_attr('weather.openweathermap','forecast')[12]['condition'] }}.png
{%- endif %}
- obj: "p5b61" # Forecast date +1d
properties:
"text": >
{%- if not is_state('weather.bessamorel','unavailable') %}
{%- set now = as_timestamp(strptime(state_attr('weather.bessamorel','forecast')[0]['datetime'], '%Y-%m-%dT%H:%M:%S%z', default='2020-01-00T00:00:00+00:00')) %}
{%- set day = now | timestamp_custom("%w") %}
{%- set days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] %}
{{ days[ day | int -1 ] }}{{- now | timestamp_custom(" %d") }}
{%- endif %}
- obj: "p5b62" # Forecast temp min +1d
properties:
"text": "{{ state_attr('weather.bessamorel','forecast')[0]['templow'] if not is_state('weather.bessamorel','unavailable') else '-' }}"
- obj: "p5b63" # Forecast temp max +1d
properties:
"text": "{{ state_attr('weather.bessamorel','forecast')[0]['temperature'] if not is_state('weather.bessamorel','unavailable') else '-' }}"
- obj: "p5b64" # Forecast condition +1d
properties:
"src": >
{%- if not is_state('weather.bessamorel','unavailable') %}
L:/w-32-{{ state_attr('weather.bessamorel','forecast')[0]['condition'] }}.png
{%- endif %}
- obj: "p5b71" # Forecast date +2d
properties:
"text": >
{%- if not is_state('weather.bessamorel','unavailable') %}
{%- set now = as_timestamp(strptime(state_attr('weather.bessamorel','forecast')[1]['datetime'], '%Y-%m-%dT%H:%M:%S%z', default='2020-01-00T00:00:00+00:00')) %}
{%- set day = now | timestamp_custom("%w") %}
{%- set days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] %}
{{ days[ day | int -1 ] }}{{- now | timestamp_custom(" %d") }}
{%- endif %}
- obj: "p5b72" # Forecast temp min +2d
properties:
"text": "{{ state_attr('weather.bessamorel','forecast')[1]['templow'] if not is_state('weather.bessamorel','unavailable') else '-' }}"
- obj: "p5b73" # Forecast temp max +2d
properties:
"text": "{{ state_attr('weather.bessamorel','forecast')[1]['temperature'] if not is_state('weather.bessamorel','unavailable') else '-' }}"
- obj: "p5b74" # Forecast condition +2d
properties:
"src": >
{%- if not is_state('weather.bessamorel','unavailable') %}
L:/w-32-{{ state_attr('weather.bessamorel','forecast')[1]['condition'] }}.png
{%- endif %}
- obj: "p5b81" # Forecast date +3d
properties:
"text": >
{%- if not is_state('weather.bessamorel','unavailable') %}
{%- set now = as_timestamp(strptime(state_attr('weather.bessamorel','forecast')[2]['datetime'], '%Y-%m-%dT%H:%M:%S%z', default='2020-01-00T00:00:00+00:00')) %}
{%- set day = now | timestamp_custom("%w") %}
{%- set days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] %}
{{ days[ day | int -1 ] }}{{- now | timestamp_custom(" %d") }}
{%- endif %}
- obj: "p5b82" # Forecast temp min +3d
properties:
"text": "{{ state_attr('weather.bessamorel','forecast')[2]['templow'] if not is_state('weather.bessamorel','unavailable') else '-' }}"
- obj: "p5b83" # Forecast temp max +3d
properties:
"text": "{{ state_attr('weather.bessamorel','forecast')[2]['temperature'] if not is_state('weather.bessamorel','unavailable') else '-' }}"
- obj: "p5b84" # Forecast condition +3d
properties:
"src": >
{%- if not is_state('weather.bessamorel','unavailable') %}
L:/w-32-{{ state_attr('weather.bessamorel','forecast')[2]['condition'] }}.png
{%- endif %}
- obj: "p5b91" # Forecast date +4d
properties:
"text": >
{%- if not is_state('weather.bessamorel','unavailable') %}
{%- set now = as_timestamp(strptime(state_attr('weather.bessamorel','forecast')[3]['datetime'], '%Y-%m-%dT%H:%M:%S%z', default='2020-01-00T00:00:00+00:00')) %}
{%- set day = now | timestamp_custom("%w") %}
{%- set days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] %}
{{ days[ day | int -1 ] }}{{- now | timestamp_custom(" %d") }}
{%- endif %}
- obj: "p5b92" # Forecast temp min +4d
properties:
"text": "{{ state_attr('weather.bessamorel','forecast')[3]['templow'] if not is_state('weather.bessamorel','unavailable') else '-' }}"
- obj: "p5b93" # Forecast temp max +4d
properties:
"text": "{{ state_attr('weather.bessamorel','forecast')[3]['temperature'] if not is_state('weather.bessamorel','unavailable') else '-' }}"
- obj: "p5b94" # Forecast condition +4d
properties:
"src": >
{%- if not is_state('weather.bessamorel','unavailable') %}
L:/w-32-{{ state_attr('weather.bessamorel','forecast')[3]['condition'] }}.png
{%- endif %}

View File

@@ -0,0 +1,42 @@
db_url: mysql://homeassistant:homeassistant@core-mariadb/homeassistant?charset=utf8mb4
purge_keep_days: 30
auto_purge: true
include:
domains:
- climate
- binary_sensor
- input_boolean
- input_datetime
- input_number
- input_select
- sensor
- switch
- person
- device_tracker
- light
exclude:
domains:
- camera
- zone
- automation
- sun
- weather
- cover
- group
- script
- pool_pump
entity_globs:
- sensor.clock*
- sensor.date*
- sensor.glances*
- sensor.load_*m
- sensor.time*
- sensor.uptime*
- device_tracker.nmap_tracker*
entities:
- camera.front_door
- sensor.memory_free
- sensor.memory_use
- sensor.memory_use_percent
- sensor.processor_use
- weather.openweathermap

View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"*.yaml": "home-assistant"
}
}

View File

@@ -0,0 +1,43 @@
# ## pc portable lenovo battery
# - platform: mqtt
# unique_id: portable_lenovo_battery
# name: portable_lenovo_battery
# device_class: battery
# unit_of_measurement: "%"
# # payload_on: low
# # payload_off: normal
# qos: 1
# #availability_topic: home/downstairs/living-room/front-windows/availability
# #payload_available: online
# #payload_not_available: offline
# state_topic: computer/lenovo
# value_template: "{{ value_json.percent }}"
# - platform: mqtt
# unique_id: portable_elitebook_corei5
# name: portable_elitebook_corei5
# device_class: battery
# unit_of_measurement: "%"
# # payload_on: low
# # payload_off: normal
# qos: 1
# #availability_topic: home/downstairs/living-room/front-windows/availability
# #payload_available: online
# #payload_not_available: offline
# state_topic: computer/corei5
# value_template: "{{ value_json.percent }}"
# - platform: mqtt
# unique_id: portable_elitebook_centrino
# name: portable_elitebook_centrino
# device_class: battery
# unit_of_measurement: "%"
# # payload_on: low
# # payload_off: normal
# qos: 1
# #availability_topic: home/downstairs/living-room/front-windows/availability
# #payload_available: online
# #payload_not_available: offline
# state_topic: computer/centrino
# value_template: "{{ value_json.percent }}"

View File

@@ -0,0 +1,27 @@
- platform: mqtt_room
device_id: "iphone X"
name: "iphone X espresence"
state_topic: "espresense/devices/iphone_x"
timeout: 10
away_timeout: 60
- platform: mqtt_room
device_id: "xiaomi"
name: "xiaomi espresence"
state_topic: "espresense/devices/xiaomi"
timeout: 10
away_timeout: 60
- platform: mqtt_room
device_id: "ipad"
name: "ipad espresence"
state_topic: "espresense/devices/ipad"
timeout: 10
away_timeout: 60
- platform: mqtt_room
device_id: "known:ed317301e564"
name: "clef espresence"
state_topic: "espresense/devices/known:ed317301e564"
timeout: 10
away_timeout: 60

View File

@@ -0,0 +1,198 @@
######################################################################
## chaudiere
## actual value - input register
# register_type: input
## FC=04 de 30001-30272 enlever 1 a register
######################################################################
- platform: modbus
scan_interval: 10
registers:
- name: T° chaudiere
hub: pimodbus
register: 0
register_type: input
device_class: temperature
unit_of_measurement: °C
slave: 2
count: 1
scale: 0.5
offset: 0
precision: 0
- platform: modbus
scan_interval: 10
registers:
- name: T° fumée
hub: pimodbus
register: 1
register_type: input
device_class: temperature
unit_of_measurement: °C
slave: 2
count: 1
scale: 1
offset: 0
precision: 0
- platform: modbus
scan_interval: 60
registers:
- name: T° board
hub: pimodbus
register: 2
register_type: input
device_class: temperature
unit_of_measurement: °C
slave: 2
count: 1
scale: 0.5
offset: 0
precision: 0
- platform: modbus
scan_interval: 60
registers:
- name: T°exterieur nord
device_class: temperature
hub: pimodbus
register: 4
register_type: input
unit_of_measurement: °C
slave: 2
count: 1
scale: 0.5
offset: 0
precision: 0
- platform: modbus
scan_interval: 60
registers:
- name: Ventilation
hub: pimodbus
register: 7
register_type: input
unit_of_measurement: U/min
slave: 2
count: 1
scale: 1
offset: 0
precision: 0
- platform: modbus
scan_interval: 60
registers:
- name: Heure de chauffage
hub: pimodbus
register: 221
register_type: input
unit_of_measurement: h
slave: 2
count: 1
scale: 1
offset: 0
precision: 0
- platform: modbus
scan_interval: 60
registers:
- name: Heure de service
hub: pimodbus
register: 98
register_type: input
unit_of_measurement: h
slave: 2
count: 1
scale: 1
offset: 0
precision: 0
- platform: modbus
scan_interval: 10
registers:
- name: T° départ
hub: pimodbus
register: 21
register_type: input
device_class: temperature
unit_of_measurement: °C
slave: 2
count: 1
scale: 0.5
offset: 0
precision: 0
- platform: modbus
scan_interval: 10
registers:
- name: Charge du tampon
hub: pimodbus
register: 225
register_type: input
slave: 2
count: 1
scale: 1
offset: 0
precision: 0
- platform: modbus
scan_interval: 10
registers:
- name: tampon haut
hub: pimodbus
register: 118
register_type: input
device_class: temperature
unit_of_measurement: °C
slave: 2
count: 1
scale: 0.5
offset: 0
precision: 0
- platform: modbus
scan_interval: 10
registers:
- name: tampon bas
hub: pimodbus
register: 120
register_type: input
device_class: temperature
unit_of_measurement: °C
slave: 2
count: 1
scale: 0.5
offset: 0
precision: 0
- platform: modbus
scan_interval: 60
registers:
- name: Consigne T° fumée
device_class: temperature
hub: pimodbus
register: 19
register_type: input
unit_of_measurement: °C
slave: 2
count: 1
scale: 1
offset: 0
precision: 0
- platform: modbus
scan_interval: 60
registers:
- name: Etat chaudiere
hub: pimodbus
register: 4001
register_type: input
slave: 2
count: 1
scale: 1
- platform: modbus
scan_interval: 60
registers:
- name: O2_residuel
hub: pimodbus
register: 3
register_type: input
slave: 2
count: 1
scale: 0.1
offset: 0
precision: 0
######################################################################
## chaudiere
## parameter - holding registers
# register_type: holding
## FC=03 de 40001-41094 enlever 1 a register
######################################################################

View File

@@ -0,0 +1,101 @@
# ######################################################################
# ## chaudiere
# ## actual value - input register
# # register_type: input
# ## FC=04 de 30001-30272 enlever 1 a register
# ######################################################################
# - platform: mqtt
# state_topic: "chaudiere/t_chaud"
# unit_of_measurement: "°C"
# device_class: temperature
# name: "T° chaudiere"
# unique_id: temp_chaud_01
# - platform: mqtt
# state_topic: "chaudiere/t_fumee"
# name: "T° fumée"
# device_class: temperature
# unit_of_measurement: "°C"
# unique_id: temp_fumee_01
# - platform: mqtt
# state_topic: "chaudiere/t_board"
# name: "T° board"
# device_class: temperature
# unit_of_measurement: "°C"
# unique_id: temp_board_01
# - platform: mqtt
# state_topic: "chaudiere/t_ext"
# name: "T°exterieur nord"
# device_class: temperature
# unit_of_measurement: "°C"
# unique_id: temp_ext_N_01
# - platform: mqtt
# state_topic: "chaudiere/vit_venti"
# name: "Ventilation"
# unit_of_measurement: "U/min"
# unique_id: venti_chaud_01
# - platform: mqtt
# state_topic: "chaudiere/h_chauffage"
# name: "Heure de chauffage"
# unit_of_measurement: "h"
# unique_id: heure_chauff_01
# - platform: mqtt
# state_topic: "chaudiere/h_service"
# name: "Heure de service"
# unit_of_measurement: "h"
# unique_id: heure_serv_01
# - platform: mqtt
# state_topic: "chaudiere/t_dep_chauff"
# name: "T° départ"
# device_class: temperature
# unit_of_measurement: "°C"
# unique_id: temp_dep_chauff_01
# - platform: mqtt
# state_topic: "chaudiere/charge_tampon"
# name: "Charge du tampon"
# unique_id: charge_tampon_01
# - platform: mqtt
# state_topic: "chaudiere/tampon_haut"
# name: "tampon haut"
# device_class: temperature
# unit_of_measurement: "°C"
# unique_id: tampon_haut_01
# - platform: mqtt
# state_topic: "chaudiere/tampon_bas"
# name: "tampon bas"
# device_class: temperature
# unit_of_measurement: "°C"
# unique_id: tampon_bas_01
# - platform: mqtt
# state_topic: "chaudiere/cons_t_fumee"
# name: "Consigne T° fumée"
# device_class: temperature
# unit_of_measurement: "°C"
# unique_id: temp_cons_fumee_01
# - platform: mqtt
# state_topic: "chaudiere/etat_chaudiere"
# name: "Etat chaudiere"
# unique_id: etat_chaud_01
# - platform: mqtt
# state_topic: "chaudiere/O2_res"
# name: "O2_residuel"
# unique_id: O2_resi_01
# ######################################################################
# ## chaudiere
# ## parameter - holding registers
# # register_type: holding
# ## FC=03 de 40001-41094 enlever 1 a register
# ######################################################################

View File

@@ -0,0 +1,4 @@
- platform: mqtt
state_topic: 'home/sensor/1456/temp'
name: 'chaudiere'
unit_of_measurement: '°C'

View File

@@ -0,0 +1,15 @@
- platform: integration
source: sensor.puissance_compteur
name: total_energy_elec
unit_prefix: k
unit: kWh
unit_time: h
round: 2
- platform: integration
source: sensor.solar_pv_power
name: total_energy_solar
unit_prefix: k
unit: kWh
unit_time: h
round: 2

View File

View File

@@ -0,0 +1,162 @@
#- platform: uptime
#- platform: moon
#- platform: season
- platform: time_date
display_options:
- "time"
- "date"
# #----------------------------------------
# #
# # lux
# #
# #----------------------------------------
# - platform: mqtt
# state_topic: "sensor/lux"
# name: "Luminosité"
# device_class: illuminance
# unit_of_measurement: "lx"
# value_template: "{{ value | round(1) }}"
# unique_id: luminosite_salon_01
# - platform: mqtt
# state_topic: "arduino/garage/intensite"
# name: "Intensité t_divisionnaire"
# unit_of_measurement: "A"
# device_class: current
# value_template: "{{ value | round(1) }}"
# unique_id: courant_t_div_01
# - platform: mqtt
# state_topic: "arduino/garage/puissance"
# name: "Puissance t_divisionnaire"
# device_class: power
# unit_of_measurement: "W"
# value_template: "{{ value | round(1) }}"
# unique_id: power_t_div_01
# #----------------------------------------
# #
# # chauffage reglage PID
# #
# #----------------------------------------
# - platform: mqtt
# state_topic: "sensor/chauff/pid/payload"
# name: "payload"
# - platform: mqtt
# state_topic: "sensor/chauff/pid/pv"
# name: "pv"
# value_template: "{{ value | round(3) }}"
# - platform: mqtt
# state_topic: "sensor/chauff/pid/setpoint"
# name: "setpoint"
# - platform: mqtt
# state_topic: "sensor/chauff/pid/proportional"
# name: "P"
# value_template: "{{ value | round(3) }}"
# - platform: mqtt
# state_topic: "sensor/chauff/pid/integral"
# name: "I"
# value_template: "{{ value | round(3) }}"
# - platform: mqtt
# state_topic: "sensor/chauff/pid/derivative"
# name: "D"
# value_template: "{{ value | round(3) }}"
#----------------------------------------
#
# stat divers: lumiere , detection
#
#----------------------------------------
# - platform: history_stats
# name: Lampes couloir
# entity_id: light.couloir
# state: "on"
# type: time
# start: "{{ now().replace(hour=0, minute=0, second=0) }}"
# end: "{{ now() }}"
# - platform: history_stats
# name: Lampes escalier
# entity_id: light.escalier
# state: "on"
# type: time
# start: "{{ now().replace(hour=0, minute=0, second=0) }}"
# end: "{{ now() }}"
# - platform: history_stats
# name: Lampes cuisine
# entity_id: light.plafond_cuisine
# state: "on"
# type: time
# start: "{{ now().replace(hour=0, minute=0, second=0) }}"
# end: "{{ now() }}"
# - platform: history_stats
# name: Ouverture portes garages
# entity_id: binary_sensor.porte_garage
# state: "on"
# type: count
# start: "{{ now().replace(hour=0, minute=0, second=0) }}"
# end: "{{ now() }}"
# - platform: history_stats
# name: Ouverture portes escalier
# entity_id: binary_sensor.porte_escalier
# state: "on"
# type: count
# start: "{{ now().replace(hour=0, minute=0, second=0) }}"
# end: "{{ now() }}"
#----------------------------------------
#
# calcul humidité bien-etre
#sensor/tension/compteur
#----------------------------------------
- platform: mold_indicator
indoor_temp_sensor: sensor.ecowitt_tempin
indoor_humidity_sensor: sensor.ecowitt_humidityin
outdoor_temp_sensor: sensor.ecowitt_temp
calibration_factor: 2.0
#----------------------------------------
#
# adresse IP internet
#
#----------------------------------------
#- platform: dnsip
# name: ip_WAN
#- platform: seventeentrack
# username: gil.soulier@gmail.com
# password: Miss*17track
# show_archived: false
# show_delivered: true
- platform: sql
db_url: mysql://homeassistant:homeassistant@core-mariadb/homeassistant?charset=utf8
scan_interval: 60
queries:
- name: "MariaDB DataBase size"
query: 'SELECT table_schema "database", Round(Sum(data_length + index_length) / 1024, 1) "value" FROM information_schema.tables WHERE table_schema="homeassistant" GROUP BY table_schema;'
column: "value"
unit_of_measurement: MB
- platform: sql
db_url: mysql://homeassistant:homeassistant@core-mariadb/homeassistant?charset=utf8mb4
scan_interval: 3600
queries:
- name: db_size
query: 'SELECT table_schema "database", Round(Sum(data_length + index_length) / 1048576, 2) "value" FROM information_schema.tables WHERE #table_schema="homeassistant" GROUP BY table_schema;'
column: "value"
unit_of_measurement: MB
device_class: data_size
# - platform: mqtt
# state_topic: "domotic/ISAadapter/Package id 0"
# name: "Temp PC Domotic"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
# unique_id: tempe_pc_domotic

View File

@@ -0,0 +1,274 @@
# # modbus sensors for EPEver
# - platform: mqtt
# ## reg 3100
# state_topic: "tracer/sensor/tracer_1_pv_array_voltage"
# unique_id: "array_voltage"
# name: "solar_PV_Voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(12) }}"
# - platform: mqtt
# ## reg 3101
# state_topic: "tracer/sensor/tracer_1_pv_array_current"
# unique_id: "array_current"
# name: "solar_PV_Current"
# device_class: current
# unit_of_measurement: 'A'
# state_class: measurement
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ## reg 3102 3103
# state_topic: "tracer/sensor/tracer_1_pv_array_power"
# unique_id: "array_power"
# name: "solar_PV_Power"
# icon: mdi:solar-power
# device_class: power
# unit_of_measurement: 'W'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# ## reg 3104
# state_topic: "tracer/sensor/tracer_1_battery_voltage"
# unique_id: "battery_voltage"
# name: "solar_Battery_Voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# ##reg 3105
# state_topic: "tracer/sensor/tracer_1_battery_charging_current"
# name: "Battery_charging_Current"
# unique_id: "battery_charging_current"
# device_class: current
# unit_of_measurement: 'A'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# #reg 3302
# state_topic: "tracer/sensor/tracer_1_max_battery_voltage_today"
# name: "solar_Battery_Max_Voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# #reg 3303
# state_topic: "tracer/sensor/tracer_1_min_battery_voltage_today"
# name: "solar_Battery_Min_Voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# ##reg 331B-331C
# state_topic: "tracer/sensor/tracer_1_net_battery_current"
# name: "net_Battery_Current"
# unique_id: "net_battery_current"
# device_class: current
# unit_of_measurement: 'A'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# state_topic: "tracer/registers/Charging equipment output power/value"
# unique_id: "battery_power"
# name: "solar_Battery_Power"
# device_class: power
# unit_of_measurement: 'W'
# state_class: measurement
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# #reg 3110
# state_topic: "tracer/sensor/tracer_1_battery_temperature"
# name: "solar_Battery_Temperature"
# unique_id: "battery_temperature"
# icon: mdi:thermometer
# device_class: temperature
# state_class: measurement
# unit_of_measurement: '°C'
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# #reg 311A
# state_topic: "tracer/sensor/tracer_1_battery_soc"
# name: "solar_Battery_SOC"
# unique_id: "battery_SOC"
# unit_of_measurement: '%'
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# #reg 3111
# state_topic: "tracer/sensor/tracer_1_charger_temperature"
# unique_id: "device_temperature"
# name: "solar_device_Temperature"
# device_class: temperature
# state_class: measurement
# unit_of_measurement: '°C'
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# ## reg 3201
# state_topic: "tracer/sensor/tracer_1_equipment_status"
# name: "solar_PV_Status"
# unique_id: "solar_PV_status"
# # device_class: energy
# #unit_of_measurement: 'kW'
# value_template: "{{ value }}"
# - platform: mqtt
# ## reg 3200
# state_topic: "tracer/sensor/tracer_1_battery_status"
# name: "solar_battery_Status"
# unique_id: "battery_status"
# value_template: "{{ value }}"
# - platform: mqtt
# ## reg 330C-330D
# state_topic: "tracer/sensor/tracer_1_generated_energy_today"
# name: "generated_Today_Energy"
# unique_id: "generated_energy_today"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_total_generated_energy"
# unique_id: "generated_energy_total"
# name: "generated_total_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ## reg 330E-330F
# state_topic: "tracer/sensor/tracer_1_generated_energy_this_month"
# name: "generated_month_Energy"
# unique_id: "generated_energy_month"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_generated_energy_this_year"
# unique_id: "generated_energy_year"
# name: "generated_year_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ##reg 310E-310F
# state_topic: "tracer/sensor/tracer_1_load_power"
# unique_id: "load power"
# name: "DC_Load_Power"
# device_class: power
# unit_of_measurement: 'W'
# state_class: measurement
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ## reg 310C
# state_topic: "tracer/sensor/tracer_1_load_voltage"
# name: "DC_Load_Voltage"
# unique_id: "load voltage"
# device_class: voltage
# unit_of_measurement: 'V'
# state_class: measurement
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ## reg 310D
# state_topic: "tracer/sensor/tracer_1_load_current"
# name: "DC_Load_Courant"
# unique_id: "load current"
# device_class: current
# unit_of_measurement: 'A'
# state_class: measurement
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ## reg 200C
# state_topic: "tracer/sensor/tracer_1_day/night"
# name: "solar_Day_Night"
# #device_class: energy
# #unit_of_measurement: 'kW'
# value_template: "{{ value }}"
# - platform: mqtt
# ## reg 330C-330D
# state_topic: "tracer/sensor/tracer_1_consumed_energy_today"
# name: "consumed_Today_Energy"
# unique_id: "energy_today"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_total_consumed_energy"
# unique_id: "consumed_energy_total"
# name: "consumed_Total_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_consumed_energy_this_month"
# unique_id: "consumed_energy_month"
# name: "consumed_Month_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ##reg 330A-330B
# state_topic: "tracer/sensor/tracer_1_consumed_energy_this_year"
# unique_id: "consumed_energy_year"
# name: "consumed_Year_Energy"
# device_class: energy
# state_class: total_increasing
# unit_of_measurement: 'kWh'
# value_template: "{{ value | round(2) }}"
# - platform: mqtt
# ## reg 3201
# state_topic: "tracer/sensor/tracer_1_charging_mode"
# name: "charging mode"
# unique_id: "charging mode"
# # device_class: energy
# #unit_of_measurement: 'kW'
# value_template: "{{ value | round(1) }}"
# - platform: mqtt
# ## reg 3201
# state_topic: "arduinomegatest/servo/status"
# name: "servo_pos"
# unique_id: "servo_pos1"
# # device_class: energy
# #unit_of_measurement: 'kW'
# # value_template: "{{ value }}"

View File

@@ -0,0 +1,63 @@
# ecu-r apsystem
#- platform: apsystems
# authId: 2c9f95c781d7527d0181e27daf25252a
# systemId: 2c9f95c781d7527d0181dedde94c1f7c
# ecuId: 216200068509
# sunset: off
# - platform: mqtt
# state_topic: "stat/ecu_r/current"
# name: "courant ecuR"
# device_class: current
# unit_of_measurement: "A"
# value_template: "{{ value | round(1) }}"
# unique_id: ecuR_courant
# - platform: mqtt
# state_topic: "stat/ecu_r/volt"
# name: "tension ecuR"
# device_class: voltage
# unit_of_measurement: "V"
# value_template: "{{ value | round(1) }}"
# unique_id: ecuR_tension
# - platform: mqtt
# state_topic: "stat/ecu_r/power"
# name: "puissance ecuR"
# device_class: power
# unit_of_measurement: "W"
# value_template: "{{ value | round(1) }}"
# unique_id: ecuR_puissance
# - platform: mqtt
# state_topic: "stat/ecu_r/frequency"
# name: "frequence ecuR"
# device_class: frequency
# unit_of_measurement: "Hz"
# value_template: "{{ value | round(1) }}"
# unique_id: ecuR_frequence
# - platform: mqtt
# state_topic: "stat/ecu_r/cos_phi"
# name: "cos Phi ecuR"
# device_class: power_factor
# unit_of_measurement: "%"
# value_template: "{{ value | round(1) }}"
# unique_id: ecuR_cos_phi
# - platform: mqtt
# state_topic: "stat/ecu_r/temperature"
# name: "temperature ecuR"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
# unique_id: ecuR_temperature
# - platform: mqtt
# state_topic: "stat/ecu_r/energy_today"
# name: "energy_today ecuR"
# device_class: energy
# unit_of_measurement: "Wh"
# value_template: "{{ value | round(1) }}"
# unique_id: ecuR_energy_today
# state_class: total_increasing
#- platform: integration
# source: sensor.puissance_compteur
# name: total_energy_elec
# unit_time: h
# round: 2

View File

@@ -0,0 +1,278 @@
# modbus sensors for EPEver
- platform: modbus
scan_interval: 30
registers:
- name: EPEver_Battery_Voltage #0x00331A
hub: hub1
unit_of_measurement: V
slave: 01
register: 13082
register_type: input
scale: 0.01
precision: 2
- name: Epever_Battery Current #0x00331B & 1C
hub: hub1
unit_of_measurement: A
slave: 1
register: 13083
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Epever_Battery Temperature #0x003110
hub: hub1
unit_of_measurement: °C
slave: 1
register: 12560
register_type: input
scale: 0.01
precision: 2
- name: EPEver_Battery_SOC
hub: hub1
unit_of_measurement: '%'
slave: 01
register: 12570
register_type: input
- name: Epever_Battery Status #0x003200
hub: hub1
slave: 1
register: 12800
register_type: input
scale: 1
precision: 0
#######################################################################
#D15: 1-Wrong identification for rated voltage
#D8: Battery inner resistance, abnormal 1, normal 0
#D7-D4: 00H Normal, 01H Over, Temp.(Higher than the warning settings) 02H Low Temp.(Lower than the warning settings),
#D3-D0: 00H Normal ,01H Over Voltage. , 02H Under Voltage, 03H Over discharge, 04H Fault
#Status analysis
#Array status:address 3201 bits D15-D10
#Charging status:address 3201 bits D3-D2
#Battery status: address 3200 bits D7-D0
#Load status: address 3201 bits D9-D7,
#Device status: address 3200 bit D15 address 3202 bits D13-D8,D6-D4 address 3201 bits D6 address 2000
############################################################################################
- name: Epever_Device Temperature #0x003111
hub: hub1
unit_of_measurement: °C
slave: 1
register: 12561
register_type: input
scale: 0.01
precision: 2
- name: Solar Charging Equipment Status #0x003201
hub: hub1
slave: 1
register: 12801
register_type: input
scale: 1
precision: 0
#######################################################################
#D15-D14: Input voltage status. 00H normal, 01H No input power connected, 02H Higher input voltage , 03H Input voltage error.
#D13: Charging MOSFET is short circuit.
#D12: Charging or Anti-reverse MOSFET is open circuit.
#D11: Anti-reverse MOSFET is short circuit.
#D10: Input is over current.
#D9: The load is over current.
#D8: The load is short circuit.
#D7: Load MOSFET is short circuit.
#D6:Disequilibrium in three circuits.A17
#D4: PV input is short circuit.
#D3-D2: Charging status. 00H No charging,01H Float,02H Boost, 03H Equalization.
#D1: 0 Normal, 1 Fault.
#D0: 1 Running, 0 Standby.
#Status analysis
#Array status:address 3201 bits D15-D10
#Charging status:address 3201 bits D3-D2
#Battery status: address 3200 bits D7-D0
#Load status: address 3201 bits D9-D7,
#Device status: address 3200 bit D15 address 3202 bits D13-D8,D6-D4 address 3201 bits D6 address 2000
############################################################################################
- name: Solar Consumed Energy Today #0x003304 and 0x003305
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13060
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: EPEver_Load_Current # 310D
hub: hub1
unit_of_measurement: A
slave: 01
register: 12557
register_type: input
scale: 0.01
precision: 2
- name: EPEver_Load_voltage #310C
hub: hub1
unit_of_measurement: V
slave: 1
register: 12556
register_type: input
scale: 0.01
- name: EPEver_Load_Power #0x00310E and 0x00310F
hub: hub1
unit_of_measurement: W
slave: 1
register: 12558
register_type: input
scale: 0.01
count: 2
reverse_order: true
- name: EPEver_Load_Status #0x003202
hub: hub1
slave: 1
register: 12802
register_type: input
scale: 1
precision: 0
- name: EPEver_Solar_voltage #3100
hub: hub1
unit_of_measurement: V
slave: 1
register: 12544
register_type: input
scale: 0.01
- name: EPEver_Solar_Current # 3101
hub: hub1
unit_of_measurement: A
slave: 01
register: 12545
register_type: input
scale: 0.01
precision: 2
- name: EPEver_Solar_Power #0x003102 and 0x003103
hub: hub1
unit_of_measurement: W
slave: 1
register: 12546
register_type: input
scale: 0.01
count: 2
reverse_order: true
# modbus sensors for EPEver
- platform: modbus
scan_interval: 600
registers:
- name: Epever_Maximum Battery Voltage Today #0x003302
hub: hub1
unit_of_measurement: V
slave: 1
register: 13058
register_type: input
scale: 0.01
precision: 2
- name: Epever_Minimum Battery Voltage Today #0x003303
hub: hub1
unit_of_measurement: V
slave: 1
register: 13059
register_type: input
scale: 0.01
precision: 2
- name: Epever Consumed Energy Today #0x003304 and 0x003305
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13060
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Epever Consumed Energy This Month #0x003306 and 07
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13062
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Epever Consumed Energy This Year #0x003308 & 09
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13064
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Total Consumed Energy #0x00330A & 0B
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13066
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Generated Energy This Month #0x00330E & 0F
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13070
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Generated Energy This Year #0x003310 & 11
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13072
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Total Generated Energy #0x003312 & 13
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13074
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Consumed Energy Today #0x003304 and 0x003305
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13060
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true

View File

@@ -0,0 +1,278 @@
# modbus sensors for EPEver
- platform: modbus
scan_interval: 30
registers:
- name: EPEver_Battery_Voltage #0x00331A
hub: hub1
unit_of_measurement: V
slave: 01
register: 13082
register_type: input
scale: 0.01
precision: 2
- name: Epever_Battery Current #0x00331B & 1C
hub: hub1
unit_of_measurement: A
slave: 1
register: 13083
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Epever_Battery Temperature #0x003110
hub: hub1
unit_of_measurement: °C
slave: 1
register: 12560
register_type: input
scale: 0.01
precision: 2
- name: EPEver_Battery_SOC
hub: hub1
unit_of_measurement: '%'
slave: 01
register: 12570
register_type: input
- name: Epever_Battery Status #0x003200
hub: hub1
slave: 1
register: 12800
register_type: input
scale: 1
precision: 0
#######################################################################
#D15: 1-Wrong identification for rated voltage
#D8: Battery inner resistance, abnormal 1, normal 0
#D7-D4: 00H Normal, 01H Over, Temp.(Higher than the warning settings) 02H Low Temp.(Lower than the warning settings),
#D3-D0: 00H Normal ,01H Over Voltage. , 02H Under Voltage, 03H Over discharge, 04H Fault
#Status analysis
#Array status:address 3201 bits D15-D10
#Charging status:address 3201 bits D3-D2
#Battery status: address 3200 bits D7-D0
#Load status: address 3201 bits D9-D7,
#Device status: address 3200 bit D15 address 3202 bits D13-D8,D6-D4 address 3201 bits D6 address 2000
############################################################################################
- name: Epever_Device Temperature #0x003111
hub: hub1
unit_of_measurement: °C
slave: 1
register: 12561
register_type: input
scale: 0.01
precision: 2
- name: Solar Charging Equipment Status #0x003201
hub: hub1
slave: 1
register: 12801
register_type: input
scale: 1
precision: 0
#######################################################################
#D15-D14: Input voltage status. 00H normal, 01H No input power connected, 02H Higher input voltage , 03H Input voltage error.
#D13: Charging MOSFET is short circuit.
#D12: Charging or Anti-reverse MOSFET is open circuit.
#D11: Anti-reverse MOSFET is short circuit.
#D10: Input is over current.
#D9: The load is over current.
#D8: The load is short circuit.
#D7: Load MOSFET is short circuit.
#D6:Disequilibrium in three circuits.A17
#D4: PV input is short circuit.
#D3-D2: Charging status. 00H No charging,01H Float,02H Boost, 03H Equalization.
#D1: 0 Normal, 1 Fault.
#D0: 1 Running, 0 Standby.
#Status analysis
#Array status:address 3201 bits D15-D10
#Charging status:address 3201 bits D3-D2
#Battery status: address 3200 bits D7-D0
#Load status: address 3201 bits D9-D7,
#Device status: address 3200 bit D15 address 3202 bits D13-D8,D6-D4 address 3201 bits D6 address 2000
############################################################################################
- name: Solar Consumed Energy Today #0x003304 and 0x003305
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13060
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: EPEver_Load_Current # 310D
hub: hub1
unit_of_measurement: A
slave: 01
register: 12557
register_type: input
scale: 0.01
precision: 2
- name: EPEver_Load_voltage #310C
hub: hub1
unit_of_measurement: V
slave: 1
register: 12556
register_type: input
scale: 0.01
- name: EPEver_Load_Power #0x00310E and 0x00310F
hub: hub1
unit_of_measurement: W
slave: 1
register: 12558
register_type: input
scale: 0.01
count: 2
reverse_order: true
- name: EPEver_Load_Status #0x003202
hub: hub1
slave: 1
register: 12802
register_type: input
scale: 1
precision: 0
- name: EPEver_Solar_voltage #3100
hub: hub1
unit_of_measurement: V
slave: 1
register: 12544
register_type: input
scale: 0.01
- name: EPEver_Solar_Current # 3101
hub: hub1
unit_of_measurement: A
slave: 01
register: 12545
register_type: input
scale: 0.01
precision: 2
- name: EPEver_Solar_Power #0x003102 and 0x003103
hub: hub1
unit_of_measurement: W
slave: 1
register: 12546
register_type: input
scale: 0.01
count: 2
reverse_order: true
# modbus sensors for EPEver
- platform: modbus
scan_interval: 600
registers:
- name: Epever_Maximum Battery Voltage Today #0x003302
hub: hub1
unit_of_measurement: V
slave: 1
register: 13058
register_type: input
scale: 0.01
precision: 2
- name: Epever_Minimum Battery Voltage Today #0x003303
hub: hub1
unit_of_measurement: V
slave: 1
register: 13059
register_type: input
scale: 0.01
precision: 2
- name: Epever Consumed Energy Today #0x003304 and 0x003305
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13060
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Epever Consumed Energy This Month #0x003306 and 07
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13062
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Epever Consumed Energy This Year #0x003308 & 09
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13064
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Total Consumed Energy #0x00330A & 0B
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13066
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Generated Energy This Month #0x00330E & 0F
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13070
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Generated Energy This Year #0x003310 & 11
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13072
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Total Generated Energy #0x003312 & 13
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13074
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true
- name: Solar Consumed Energy Today #0x003304 and 0x003305
hub: hub1
unit_of_measurement: KWh
slave: 1
register: 13060
register_type: input
scale: 0.01
precision: 2
count: 2
reverse_order: true

View File

@@ -0,0 +1,36 @@
- platform: average
name: "moy-current-cpt"
duration:
hours: 1
entities:
- sensor.tac2100_compteur_courant
- platform: average
name: "moy-couranta-cpt2"
duration:
hours: 1
entities:
- sensor.pj1203_zb_compteur_current_a
- platform: average
name: "moy-courantb-cpt2"
duration:
hours: 1
entities:
- sensor.pj1203_zb_compteur_current_b
- platform: average
name: "moy-power-cpt"
duration:
hours: 1
entities:
- sensor.tac2100_compteur_puissance_active
- platform: average
name: "moy-powera-cpt2"
duration:
hours: 1
entities:
- sensor.pj1203_zb_compteur_power_a
- platform: average
name: "moy-powerb-cpt2"
duration:
hours: 1
entities:
- sensor.pj1203_zb_compteur_power_b

View File

@@ -0,0 +1,10 @@
- platform: systemmonitor
resources:
- type: processor_use
# - type: processor_temperature
- type: memory_free
- type: disk_use_percent
- type: disk_use
- type: disk_free
- type: load_5m

View File

@@ -0,0 +1,78 @@
# ---
# #----------------------------------------
# #
# # temperature
# #
# #----------------------------------------
# - platform: mqtt
# state_topic: "temp/bureau"
# name: "T Bureau"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
# unique_id: temp_bureau_01
# - platform: mqtt
# state_topic: "temp/cave"
# name: "T Cave"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
# unique_id: temp_cave_01
# - platform: mqtt
# state_topic: "temp/garage"
# name: "T Garage"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
# unique_id: temp_garage_01
# - platform: mqtt
# state_topic: "temp/chambre1"
# name: "T Chambre1"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
# unique_id: temp_chambre_01
# - platform: mqtt
# state_topic: "temp/chambre2"
# name: "T Chambre2"
# unit_of_measurement: "°C"
# device_class: temperature
# value_template: "{{ value | round(1) }}"
# unique_id: temp_chambre_02
# - platform: mqtt
# state_topic: "temp/comble"
# name: "T Comble"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
# unique_id: temp_comble_01
# - platform: mqtt
# state_topic: "hum/sdb"
# name: "H% salle de bain"
# device_class: humidity
# unit_of_measurement: "%"
# value_template: "{{ value | round(1) }}"
# unique_id: hum_sdb_01
# - platform: mqtt
# state_topic: "temp/sdb"
# name: "T salle de bain"
# device_class: temperature
# unit_of_measurement: "°C"
# value_template: "{{ value | round(1) }}"
# unique_id: temp_sdb_01
#----------------------------------------
#
# temperature
#
#----------------------------------------
# - platform: min_max
# entity_ids:
# - sensor.temp
# name: "min_exterieur"
# type: min
# - platform: min_max
# entity_ids:
# - sensor.temp
# name: "max_exterieur"
# type: max

View File

@@ -0,0 +1,12 @@
- platform: template
sensors:
unraid_array_status:
friendly_name: UnRAID Array Status
value_template: >
{{state_attr("binary_sensor.tower_server", "arrayStatus")}}
unraid_array_space:
friendly_name: UnRAID Array Space
value_template: >
{% set state = state_attr("switch.tower_server", "diskSpace") %}
{{ Offline if state == None else state | regex_findall_index(".*\((\d+.?\d+) %\)") | float }}
unit_of_measurement: '%'

View File

@@ -0,0 +1,13 @@
turn_off_m83lenovo_pc: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' root@10.0.0.178 sudo shutdown -h now"
turn_off_pc_aorus: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' gilles@10.0.0.128 sudo shutdown -h now"
turn_off_unraid_pc: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' root@10.0.0.240 sudo shutdown -h now"
turn_off_lenovom91_bureau_pc: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' gilles@10.0.0.113 sudo shutdown -h now"
turn_off_hp3300_pc: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' root@10.0.1.208 sudo shutdown -h now"
turn_off_atermitter_pc: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' root@10.0.1.73 sudo shutdown -h now"
turn_off_hp_debnas_pc: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' gilles@10.0.0.16 sudo shutdown -h now"
screen_off_pc_yoga: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' gilles@10.0.0.24 busctl --user set-property org.gnome.Mutter.DisplayConfig /org/gnome/Mutter/DisplayConfig org.gnome.Mutter.DisplayConfig PowerSaveMode i 1"
screen_on_pc_yoga: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' gilles@10.0.0.24 busctl --user set-property org.gnome.Mutter.DisplayConfig /org/gnome/Mutter/DisplayConfig org.gnome.Mutter.DisplayConfig PowerSaveMode i 0"
turn_off_pc_yoga: "ssh -i /config/ssh_keys/id_rsa_homeassistant -o 'StrictHostKeyChecking=no' gilles@10.0.0.24 sudo shutdown -h now"

View File

@@ -0,0 +1,17 @@
# http://10.0.0.2:8123/hacs/repository/643579135
algorithm:
initial_temp: 1000
min_temp: 0.1
cooling_factor: 0.95
max_iteration_number: 1000
devices:
- name: "prise_ecran"
entity_id: "switch.prise_ecran"
power_max: 400
check_usable_template: "{{%if states('sensor.energy_pj1203_energy_flow_b') == 'producing' %}}"
duration_min: 6
duration_stop_min: 3
action_mode: "service_call"
service_activation: switch/turn_on"
deactivation_service: "switch/turn_off"

View File

@@ -0,0 +1,50 @@
- platform: command_line
switches:
ipx800_out1:
command_on: 'curl http://10.0.0.9/preset.htm?led1=1 >/dev/null'
command_off: 'curl http://10.0.0.9/preset.htm?led1=0 >/dev/null'
command_state: 'curl http://10.0.0.9/status.xml'
value_template: '{% set status = value | regex_findall_index("<led0>(.*)</led0>") %} {% if status == "1" %} true {%- endif -%}'
friendly_name: 'IPX800 Out 1'
ipx800_out2:
command_on: 'curl http://10.0.0.9/preset.htm?led2=1 >/dev/null'
command_off: 'curl http://10.0.0.9/preset.htm?led2=0 >/dev/null'
command_state: 'curl http://10.0.0.9/status.xml'
value_template: '{% set status = value | regex_findall_index("<led1>(.*)</led1>") %} {% if status == "1" %} true {%- endif -%}'
friendly_name: 'IPX800 Out 2'
ipx800_3:
command_on: 'curl http://10.0.0.9/preset.htm?led3=1 >/dev/null'
command_off: 'curl http://10.0.0.9/preset.htm?led3=0 >/dev/null'
command_state: 'curl http://10.0.0.9/status.xml'
value_template: '{% set status = value | regex_findall_index("<led2>(.*)</led2>") %} {% if status == "1" %} true {%- endif -%}'
friendly_name: 'IPX800 Out 3'
ipx800_out4:
command_on: 'curl http://10.0.0.9/preset.htm?led4=1 >/dev/null'
command_off: 'curl http://10.0.0.9/preset.htm?led4=0 >/dev/null'
command_state: 'curl http://10.0.0.9/status.xml'
value_template: '{% set status = value | regex_findall_index("<led3>(.*)</led3>") %} {% if status == "1" %} true {%- endif -%}'
friendly_name: 'IPX800 Out 4'
ipx800_out5:
command_on: 'curl http://10.0.0.9/preset.htm?led5=1 >/dev/null'
command_off: 'curl http://10.0.0.9/preset.htm?led5=0 >/dev/null'
command_state: 'curl http://10.0.0.9/status.xml'
value_template: '{% set status = value | regex_findall_index("<led4>(.*)</led4>") %} {% if status == "1" %} true {%- endif -%}'
friendly_name: 'IPX800 Out 5'
ipx800_out6:
command_on: 'curl http://10.0.0.9/preset.htm?led6=1 >/dev/null'
command_off: 'curl http://10.0.0.9/preset.htm?led6=0 >/dev/null'
command_state: 'curl http://10.0.0.9/status.xml'
value_template: '{% set status = value | regex_findall_index("<led5>(.*)</led5>") %} {% if status == "1" %} true {%- endif -%}'
friendly_name: 'IPX800 Out 6'
ipx800_out7:
command_on: 'curl http://10.0.0.9/preset.htm?led7=1 >/dev/null'
command_off: 'curl http://10.0.0.9/preset.htm?led7=0 >/dev/null'
command_state: 'curl http://10.0.0.9/status.xml'
value_template: '{% set status = value | regex_findall_index("<led6>(.*)</led6>") %} {% if status == "1" %} true {%- endif -%}'
friendly_name: 'IPX800 Out 7'
ipx800_out8:
command_on: 'curl http://10.0.0.9/preset.htm?led8=1 >/dev/null'
command_off: 'curl http://10.0.0.9/preset.htm?led8=0 >/dev/null'
command_state: 'curl http://10.0.0.9/status.xml'
value_template: '{% set status = value | regex_findall_index("<led7>(.*)</led7>") %} {% if status == "1" %} true {%- endif -%}'
friendly_name: 'IPX800 Out 8'

View File

@@ -0,0 +1,9 @@
- platform: rest
name: u20_V0_power
resource: "http://10.0.0.194:7125/machine/device_power/device?device=Printer"
body_on: '{"action": "on"}'
body_off: '{"action": "off"}'
headers:
Content-Type: 'application/json'
is_on_template: >-
{{ 'result' in value_json and (value_json.result.values() | list | first == "on") }}

View File

@@ -0,0 +1,85 @@
- platform: broadlink
#host: 10.0.0.106
mac: 78:0F:77:FD:46:6C
timeout: 60
#type: rm2_pro_plus
switches:
# TV
tv_onoff:
friendly_name: "TV"
command_on: 'JgBYAAABIJISExETETcSEhISEhQQFBETETcROBESEjcRNhM1EjcTNRMTERISNxEUERMSExE2EjYSNhM2EhIROBE3ETcREhITEgAFGwABH0oSAAwzAAEfShEADQU='
command_off: 'JgBYAAABHpISEhMSETYTEhATEhQREhMSEDgRNxISEjcRNxE1EjcSNhM1EhISNhEUERETEhI1EjcSEhE4ExARNxI3ETYTERMSEQAFGAABIEgTAAwpAAEfSRIADQU='
tv_tv:
friendly_name: "Mode_Télé"
command_on: 'JgBYAAABKJQSExEUEjcTEhITExISExITEjcSOBEUETgSOBE5ETgTNxEUEhMSEhMSEjgROBI4ETkSNxM3ETkTNhITERQSExETEgAFJwABJ0sSAAxXAAEmSxMADQU='
command_off: 'JgBYAAABKJQSExEUEjcTEhITExISExITEjcSOBEUETgSOBE5ETgTNxEUEhMSEhMSEjgROBI4ETkSNxM3ETkTNhITERQSExETEgAFJwABJ0sSAAxXAAEmSxMADQU='
tv_source:
friendly_name: "Source"
command_on: 'JgBYAAABJ5UTERITEjgRFRATExITEhMSETgTNxEUEjcTNxM3ETgSOBI4EjcTEhM3ERQSEhITEhMSExEUEjcUERI4ETgTNxE4EwAFJgABJ0sSAAxWAAEnSxIADQU='
command_off: 'JgBYAAABJ5UTERITEjgRFRATExITEhMSETgTNxEUEjcTNxM3ETgSOBI4EjcTEhM3ERQSEhITEhMSExEUEjcUERI4ETgTNxE4EwAFJgABJ0sSAAxWAAEnSxIADQU='
tv_vol_up:
friendly_name: "Vol+"
command_on: 'JgBYAAABKZMRFBEVETcSExEVEBUQFBMSETgSOBITEjcRORE4EjkRNhMUETgRFBITERQQFBIUEBQRORITETgSOBE4EjgRORI3EwAFJgABJ0sRAAxXAAEnSxEADQU='
command_off: 'JgBYAAABKZMRFBEVETcSExEVEBUQFBMSETgSOBITEjcRORE4EjkRNhMUETgRFBITERQQFBIUEBQRORITETgSOBE4EjgRORI3EwAFJgABJ0sRAAxXAAEnSxEADQU='
tv_vol_down:
friendly_name: "Vol-"
command_on: 'JgBYAAABKJQTEhISEjgRFBEUEhITEhIUEDkSNxMSEjgSNxM3EjgSNxI4ETkRFBMREhMRFRETERQRExITEzcSNxM3EjgSNxM3EgAFJwABJkwRAAxXAAEoShMADQU='
command_off: 'JgBYAAABKJQTEhISEjgRFBEUEhITEhIUEDkSNxMSEjgSNxM3EjgSNxI4ETkRFBMREhMRFRETERQRExITEzcSNxM3EjgSNxM3EgAFJwABJkwRAAxXAAEoShMADQU='
tv_prog_up:
friendly_name: "Prog+"
command_on: 'JgBYAAABJ5QTEhITETkSExETEhMSExMSEjcTNxEUEjcUNhI4EjcSOBITERQSEhITExISExEUERQROBI4EjcSOBE5ETgTNxE5EgAFJgABJ0sRAAxYAAEmTBEADQU='
command_off: 'JgBYAAABJ5QTEhITETkSExETEhMSExMSEjcTNxEUEjcUNhI4EjcSOBITERQSEhITExISExEUERQROBI4EjcSOBE5ETgTNxE5EgAFJgABJ0sRAAxYAAEmTBEADQU='
tv_prog_down:
friendly_name: "Prog-"
command_on: 'JgBQAAABJ5QTEhMSETkRExITEhMTEhEUETgTNxITETgTNxI4EjcTNxI4ERMTEhMTERMRFBITERMTEhI4ETgTNxI4EjcTNxI4EQAFJwABJ0sSAA0FAAAAAAAAAAA='
command_off: 'JgBQAAABJ5QTEhMSETkRExITEhMTEhEUETgTNxITETgTNxI4EjcTNxI4ERMTEhMTERMRFBITERMTEhI4ETgTNxI4EjcTNxI4EQAFJwABJ0sSAA0FAAAAAAAAAAA='
tv_mute:
friendly_name: "Mute"
command_on: 'JgBYAAABKJQSExEUETgSExITEhMRFBITEDkSNxIUETgROBM3EjgROBI4EhMRFBA5EhMRFBEUERQRExM3ETkSExI3ETkSNxI4EgAFJgABJ0sTAAxWAAEoShIADQU='
command_off: 'JgBYAAABKJQSExEUETgSExITEhMRFBITEDkSNxIUETgROBM3EjgROBI4EhMRFBA5EhMRFBEUERQRExM3ETkSExI3ETkSNxI4EgAFJgABJ0sTAAxWAAEoShIADQU='
tv_1:
friendly_name: "1"
command_on: 'JgBgAAABJ5URExMTETgRFBEUERQRExIUEDkSNxYPEjgROBI4ETkROBM3EhQRExETETgTExEUERQRFBE4EjcTOBETEjgSNxM3EQAFJwABKEoSAAxXAAEnSxIADFYAASdKEwANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5URExMTETgRFBEUERQRExIUEDkSNxYPEjgROBI4ETkROBM3EhQRExETETgTExEUERQRFBE4EjcTOBETEjgSNxM3EQAFJwABKEoSAAxXAAEnSxIADFYAASdKEwANBQAAAAAAAAAA'
tv_2:
friendly_name: "2"
command_on: 'JgBQAAABJ5USExETETkRFBEUERQQFRAVEDkROBEVEDkROBE5ETgSOBIUEDkRExIUEDkSExETEhQQOREUEjcSOBEUEjcRORI4EQAFKAABJUwRAA0FAAAAAAAAAAA='
command_off: 'JgBQAAABJ5USExETETkRFBEUERQQFRAVEDkROBEVEDkROBE5ETgSOBIUEDkRExIUEDkSExETEhQQOREUEjcSOBEUEjcRORI4EQAFKAABJUwRAA0FAAAAAAAAAAA='
tv_3:
friendly_name: "3"
command_on: 'JgBgAAABJ5QSExEVEjYSFBETEhMSExEUETgSOBITEjcSOBI3EzcRORI3EjgRFRAVETcSFBETERUQExMSEzcRORETEjgRORI3EgAFJgABKEoSAAxXAAEnSxEADFcAASdLEQANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5QSExEVEjYSFBETEhMSExEUETgSOBITEjcSOBI3EzcRORI3EjgRFRAVETcSFBETERUQExMSEzcRORETEjgRORI3EgAFJgABKEoSAAxXAAEnSxEADFcAASdLEQANBQAAAAAAAAAA'
tv_4:
friendly_name: "4"
command_on: 'JgBYAAABJ5QUEhETETkRFBAUExMQFQ8WETcSOBEUETgTNxI4ETgSOBEVEBQROBEUETkSExETExIRORE4ExMROBEUETgTNxE4EgAFJgABKEoTAAxWAAEnTBEADQU='
command_off: 'JgBYAAABJ5QUEhETETkRFBAUExMQFQ8WETcSOBEUETgTNxI4ETgSOBEVEBQROBEUETkSExETExIRORE4ExMROBEUETgTNxE4EgAFJgABKEoTAAxWAAEnTBEADQU='
tv_5:
friendly_name: "5"
command_on: 'JgBQAAABKJQRFRAVDzkSFBEUERQQFBEUETkQOREUEjcRORA5EjgRORI4ERMROREUETkQFRAVEBMSFBE4EhQQOREUETgSNxI4EwAFKAABJksRAA0FAAAAAAAAAAA='
command_off: 'JgBQAAABKJQRFRAVDzkSFBEUERQQFBEUETkQOREUEjcRORA5EjgRORI4ERMROREUETkQFRAVEBMSFBE4EhQQOREUETgSNxI4EwAFKAABJksRAA0FAAAAAAAAAAA='
tv_6:
friendly_name: "6"
command_on: 'JgBgAAABJpUTEhITETkSEhMTEhIRFBITEjcTNxEVEDgSOBI4ETgSOBEUEjcSOBEUETgSExEUERUQOBITEhMSOBEUETgRORE4EgAFJgABKEoSAAxWAAEnSxIADFYAASdLEQANBQAAAAAAAAAA'
command_off: 'JgBgAAABJpUTEhITETkSEhMTEhIRFBITEjcTNxEVEDgSOBI4ETgSOBEUEjcSOBEUETgSExEUERUQOBITEhMSOBEUETgRORE4EgAFJgABKEoSAAxWAAEnSxIADFYAASdLEQANBQAAAAAAAAAA'
tv_7:
friendly_name: "7"
command_on: 'JgBgAAABJ5USEhITEjkQFBEUEhITEhIUEDkROBIUEDgSORE4ETgTNxE5ETgSOBIUEDgSExITERUQFBETEhQROBIUEDgSOBE5EQAFJwABJ0sSAAxXAAEnSxEADFcAASdLEQANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5USEhITEjkQFBEUEhITEhIUEDkROBIUEDgSORE4ETgTNxE5ETgSOBIUEDgSExITERUQFBETEhQROBIUEDgSOBE5EQAFJwABJ0sSAAxXAAEnSxEADFcAASdLEQANBQAAAAAAAAAA'
tv_8:
friendly_name: "8"
command_on: 'JgBgAAABKJQRFBEUETgSExIUEBQRFBETEjgSOBITETgSOBE4EjgSOBITERMTExE4ETgSFBETERQRORM2EjkTERMSEjcRORI3EwAFJgABJ0sSAAxXAAEoShEADFgAASZMEgANBQAAAAAAAAAA'
command_off: 'JgBgAAABKJQRFBEUETgSExIUEBQRFBETEjgSOBITETgSOBE4EjgSOBITERMTExE4ETgSFBETERQRORM2EjkTERMSEjcRORI3EwAFJgABJ0sSAAxXAAEoShEADFgAASZMEgANBQAAAAAAAAAA'
tv_9:
friendly_name: "9"
command_on: 'JgBgAAABJ5URFBEUETgSExITERUQFBETEjgRORMREjgRORI3EjgROBI4ERQRFRE3EzcTExAUEhISFBE4ETkRFBITETgSNxM3EgAFJwABKEkTAAxWAAEmTBIADFYAASdLEgANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5URFBEUETgSExITERUQFBETEjgRORMREjgRORI3EjgROBI4ERQRFRE3EzcTExAUEhISFBE4ETkRFBITETgSNxM3EgAFJwABKEkTAAxWAAEmTBIADFYAASdLEgANBQAAAAAAAAAA'
tv_0:
friendly_name: "0"
command_on: 'JgBgAAABJ5USExITETgSFBETERQRFBETEjkQOBMSEjgSNxI4EjgSNxMSEhMRFBEUEjcTEhEVERMROBI4ETgSOBEUETgTNxI4EQAFJwABJkwRAAxXAAEnSxEADFcAAShKEQANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5USExITETgSFBETERQRFBETEjkQOBMSEjgSNxI4EjgSNxMSEhMRFBEUEjcTEhEVERMROBI4ETgSOBEUETgTNxI4EQAFJwABJkwRAAxXAAEnSxEADFcAAShKEQANBQAAAAAAAAAA'
# VideoProjecteur
vp_onoff:
friendly_name: "VideoProjecteur"
command_on: 'JgBYAAABKJQVEBMTEhITNxMSExMREhMSEjgSOBITFBESOBITEhITEhI4FDYTNxMTERMSExITEDoUERMRExISOBQ2EzcTNxMSEgAF0gABKEoTAAxsAAEoShMADQU='
command_off: 'JgDKAJCREjUUERI2EjYRExETEDcSEhETETcRExETETcRNxETETcQFBE2ETcSNhE2ExERNxI2EjYSEhETERMRExE3EhIRExE2ETcSNhETEhIRExETEhISEhISExESNhE2EjYSNhI2EK6QkRE2EhISNhI2EhISEhI2ERMRExI2ERMRExA3EjYRExI2EhIRNxE2EjYSNhISEjYRNhI2ERMSEhISEhIRNxETERMSNhE3EDcSEhETERMSEhISERMSEhETEzUSNhI1EjYSNhEADQUAAAAAAAAAAAAAAAAAAA=='

View File

@@ -0,0 +1,91 @@
- platform: broadlink
#host: 10.0.0.106
mac: 78:0F:77:FD:46:6C
#timeout: 60
#type: rm2_pro_plus
switches:
# TV
tv_onoff:
friendly_name: "TV"
command_on: 'JgBYAAABIJISExETETcSEhISEhQQFBETETcROBESEjcRNhM1EjcTNRMTERISNxEUERMSExE2EjYSNhM2EhIROBE3ETcREhITEgAFGwABH0oSAAwzAAEfShEADQU='
command_off: 'JgBYAAABHpISEhMSETYTEhATEhQREhMSEDgRNxISEjcRNxE1EjcSNhM1EhISNhEUERETEhI1EjcSEhE4ExARNxI3ETYTERMSEQAFGAABIEgTAAwpAAEfSRIADQU='
tv_tv:
friendly_name: "Mode_Télé"
command_on: 'JgBYAAABKJQSExEUEjcTEhITExISExITEjcSOBEUETgSOBE5ETgTNxEUEhMSEhMSEjgROBI4ETkSNxM3ETkTNhITERQSExETEgAFJwABJ0sSAAxXAAEmSxMADQU='
command_off: 'JgBYAAABKJQSExEUEjcTEhITExISExITEjcSOBEUETgSOBE5ETgTNxEUEhMSEhMSEjgROBI4ETkSNxM3ETkTNhITERQSExETEgAFJwABJ0sSAAxXAAEmSxMADQU='
tv_source:
friendly_name: "Source"
command_on: 'JgBYAAABJ5UTERITEjgRFRATExITEhMSETgTNxEUEjcTNxM3ETgSOBI4EjcTEhM3ERQSEhITEhMSExEUEjcUERI4ETgTNxE4EwAFJgABJ0sSAAxWAAEnSxIADQU='
command_off: 'JgBYAAABJ5UTERITEjgRFRATExITEhMSETgTNxEUEjcTNxM3ETgSOBI4EjcTEhM3ERQSEhITEhMSExEUEjcUERI4ETgTNxE4EwAFJgABJ0sSAAxWAAEnSxIADQU='
tv_vol_up:
friendly_name: "Vol+"
command_on: 'JgBYAAABKZMRFBEVETcSExEVEBUQFBMSETgSOBITEjcRORE4EjkRNhMUETgRFBITERQQFBIUEBQRORITETgSOBE4EjgRORI3EwAFJgABJ0sRAAxXAAEnSxEADQU='
command_off: 'JgBYAAABKZMRFBEVETcSExEVEBUQFBMSETgSOBITEjcRORE4EjkRNhMUETgRFBITERQQFBIUEBQRORITETgSOBE4EjgRORI3EwAFJgABJ0sRAAxXAAEnSxEADQU='
tv_vol_down:
friendly_name: "Vol-"
command_on: 'JgBYAAABKJQTEhISEjgRFBEUEhITEhIUEDkSNxMSEjgSNxM3EjgSNxI4ETkRFBMREhMRFRETERQRExITEzcSNxM3EjgSNxM3EgAFJwABJkwRAAxXAAEoShMADQU='
command_off: 'JgBYAAABKJQTEhISEjgRFBEUEhITEhIUEDkSNxMSEjgSNxM3EjgSNxI4ETkRFBMREhMRFRETERQRExITEzcSNxM3EjgSNxM3EgAFJwABJkwRAAxXAAEoShMADQU='
tv_prog_up:
friendly_name: "Prog+"
command_on: 'JgBYAAABJ5QTEhITETkSExETEhMSExMSEjcTNxEUEjcUNhI4EjcSOBITERQSEhITExISExEUERQROBI4EjcSOBE5ETgTNxE5EgAFJgABJ0sRAAxYAAEmTBEADQU='
command_off: 'JgBYAAABJ5QTEhITETkSExETEhMSExMSEjcTNxEUEjcUNhI4EjcSOBITERQSEhITExISExEUERQROBI4EjcSOBE5ETgTNxE5EgAFJgABJ0sRAAxYAAEmTBEADQU='
tv_prog_down:
friendly_name: "Prog-"
command_on: 'JgBQAAABJ5QTEhMSETkRExITEhMTEhEUETgTNxITETgTNxI4EjcTNxI4ERMTEhMTERMRFBITERMTEhI4ETgTNxI4EjcTNxI4EQAFJwABJ0sSAA0FAAAAAAAAAAA='
command_off: 'JgBQAAABJ5QTEhMSETkRExITEhMTEhEUETgTNxITETgTNxI4EjcTNxI4ERMTEhMTERMRFBITERMTEhI4ETgTNxI4EjcTNxI4EQAFJwABJ0sSAA0FAAAAAAAAAAA='
tv_mute:
friendly_name: "Mute"
command_on: 'JgBYAAABKJQSExEUETgSExITEhMRFBITEDkSNxIUETgROBM3EjgROBI4EhMRFBA5EhMRFBEUERQRExM3ETkSExI3ETkSNxI4EgAFJgABJ0sTAAxWAAEoShIADQU='
command_off: 'JgBYAAABKJQSExEUETgSExITEhMRFBITEDkSNxIUETgROBM3EjgROBI4EhMRFBA5EhMRFBEUERQRExM3ETkSExI3ETkSNxI4EgAFJgABJ0sTAAxWAAEoShIADQU='
tv_1:
friendly_name: "1"
command_on: 'JgBgAAABJ5URExMTETgRFBEUERQRExIUEDkSNxYPEjgROBI4ETkROBM3EhQRExETETgTExEUERQRFBE4EjcTOBETEjgSNxM3EQAFJwABKEoSAAxXAAEnSxIADFYAASdKEwANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5URExMTETgRFBEUERQRExIUEDkSNxYPEjgROBI4ETkROBM3EhQRExETETgTExEUERQRFBE4EjcTOBETEjgSNxM3EQAFJwABKEoSAAxXAAEnSxIADFYAASdKEwANBQAAAAAAAAAA'
tv_2:
friendly_name: "2"
command_on: 'JgBQAAABJ5USExETETkRFBEUERQQFRAVEDkROBEVEDkROBE5ETgSOBIUEDkRExIUEDkSExETEhQQOREUEjcSOBEUEjcRORI4EQAFKAABJUwRAA0FAAAAAAAAAAA='
command_off: 'JgBQAAABJ5USExETETkRFBEUERQQFRAVEDkROBEVEDkROBE5ETgSOBIUEDkRExIUEDkSExETEhQQOREUEjcSOBEUEjcRORI4EQAFKAABJUwRAA0FAAAAAAAAAAA='
tv_3:
friendly_name: "3"
command_on: 'JgBgAAABJ5QSExEVEjYSFBETEhMSExEUETgSOBITEjcSOBI3EzcRORI3EjgRFRAVETcSFBETERUQExMSEzcRORETEjgRORI3EgAFJgABKEoSAAxXAAEnSxEADFcAASdLEQANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5QSExEVEjYSFBETEhMSExEUETgSOBITEjcSOBI3EzcRORI3EjgRFRAVETcSFBETERUQExMSEzcRORETEjgRORI3EgAFJgABKEoSAAxXAAEnSxEADFcAASdLEQANBQAAAAAAAAAA'
tv_4:
friendly_name: "4"
command_on: 'JgBYAAABJ5QUEhETETkRFBAUExMQFQ8WETcSOBEUETgTNxI4ETgSOBEVEBQROBEUETkSExETExIRORE4ExMROBEUETgTNxE4EgAFJgABKEoTAAxWAAEnTBEADQU='
command_off: 'JgBYAAABJ5QUEhETETkRFBAUExMQFQ8WETcSOBEUETgTNxI4ETgSOBEVEBQROBEUETkSExETExIRORE4ExMROBEUETgTNxE4EgAFJgABKEoTAAxWAAEnTBEADQU='
tv_5:
friendly_name: "5"
command_on: 'JgBQAAABKJQRFRAVDzkSFBEUERQQFBEUETkQOREUEjcRORA5EjgRORI4ERMROREUETkQFRAVEBMSFBE4EhQQOREUETgSNxI4EwAFKAABJksRAA0FAAAAAAAAAAA='
command_off: 'JgBQAAABKJQRFRAVDzkSFBEUERQQFBEUETkQOREUEjcRORA5EjgRORI4ERMROREUETkQFRAVEBMSFBE4EhQQOREUETgSNxI4EwAFKAABJksRAA0FAAAAAAAAAAA='
tv_6:
friendly_name: "6"
command_on: 'JgBgAAABJpUTEhITETkSEhMTEhIRFBITEjcTNxEVEDgSOBI4ETgSOBEUEjcSOBEUETgSExEUERUQOBITEhMSOBEUETgRORE4EgAFJgABKEoSAAxWAAEnSxIADFYAASdLEQANBQAAAAAAAAAA'
command_off: 'JgBgAAABJpUTEhITETkSEhMTEhIRFBITEjcTNxEVEDgSOBI4ETgSOBEUEjcSOBEUETgSExEUERUQOBITEhMSOBEUETgRORE4EgAFJgABKEoSAAxWAAEnSxIADFYAASdLEQANBQAAAAAAAAAA'
tv_7:
friendly_name: "7"
command_on: 'JgBgAAABJ5USEhITEjkQFBEUEhITEhIUEDkROBIUEDgSORE4ETgTNxE5ETgSOBIUEDgSExITERUQFBETEhQROBIUEDgSOBE5EQAFJwABJ0sSAAxXAAEnSxEADFcAASdLEQANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5USEhITEjkQFBEUEhITEhIUEDkROBIUEDgSORE4ETgTNxE5ETgSOBIUEDgSExITERUQFBETEhQROBIUEDgSOBE5EQAFJwABJ0sSAAxXAAEnSxEADFcAASdLEQANBQAAAAAAAAAA'
tv_8:
friendly_name: "8"
command_on: 'JgBgAAABKJQRFBEUETgSExIUEBQRFBETEjgSOBITETgSOBE4EjgSOBITERMTExE4ETgSFBETERQRORM2EjkTERMSEjcRORI3EwAFJgABJ0sSAAxXAAEoShEADFgAASZMEgANBQAAAAAAAAAA'
command_off: 'JgBgAAABKJQRFBEUETgSExIUEBQRFBETEjgSOBITETgSOBE4EjgSOBITERMTExE4ETgSFBETERQRORM2EjkTERMSEjcRORI3EwAFJgABJ0sSAAxXAAEoShEADFgAASZMEgANBQAAAAAAAAAA'
tv_9:
friendly_name: "9"
command_on: 'JgBgAAABJ5URFBEUETgSExITERUQFBETEjgRORMREjgRORI3EjgROBI4ERQRFRE3EzcTExAUEhISFBE4ETkRFBITETgSNxM3EgAFJwABKEkTAAxWAAEmTBIADFYAASdLEgANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5URFBEUETgSExITERUQFBETEjgRORMREjgRORI3EjgROBI4ERQRFRE3EzcTExAUEhISFBE4ETkRFBITETgSNxM3EgAFJwABKEkTAAxWAAEmTBIADFYAASdLEgANBQAAAAAAAAAA'
tv_0:
friendly_name: "0"
command_on: 'JgBgAAABJ5USExITETgSFBETERQRFBETEjkQOBMSEjgSNxI4EjgSNxMSEhMRFBEUEjcTEhEVERMROBI4ETgSOBEUETgTNxI4EQAFJwABJkwRAAxXAAEnSxEADFcAAShKEQANBQAAAAAAAAAA'
command_off: 'JgBgAAABJ5USExITETgSFBETERQRFBETEjkQOBMSEjgSNxI4EjgSNxMSEhMRFBEUEjcTEhEVERMROBI4ETgSOBEUETgTNxI4EQAFJwABJkwRAAxXAAEnSxEADFcAAShKEQANBQAAAAAAAAAA'
# VideoProjecteur
vp_onoff:
friendly_name: "VideoProjecteur"
command_on: 'JgBYAAABKJQVEBMTEhITNxMSExMREhMSEjgSOBITFBESOBITEhITEhI4FDYTNxMTERMSExITEDoUERMRExISOBQ2EzcTNxMSEgAF0gABKEoTAAxsAAEoShMADQU='
command_off: 'JgDKAJCREjUUERI2EjYRExETEDcSEhETETcRExETETcRNxETETcQFBE2ETcSNhE2ExERNxI2EjYSEhETERMRExE3EhIRExE2ETcSNhETEhIRExETEhISEhISExESNhE2EjYSNhI2EK6QkRE2EhISNhI2EhISEhI2ERMRExI2ERMRExA3EjYRExI2EhIRNxE2EjYSNhISEjYRNhI2ERMSEhISEhIRNxETERMSNhE3EDcSEhETERMSEhISERMSEhETEzUSNhI1EjYSNhEADQUAAAAAAAAAAAAAAAAAAA=='
amibot_clean:
friendly_name: "Nettoyage"
command_on: 'JgBIAAABJpMSExITEhMRFBETEhMSNxMTETgRFBE4EhMSOBEUETgSExITERQROBEUERMSExI4ERQROBI4EhMROBE4EjgSExI3EgANBQ=='
amibot_base:
friendly_name: "Base"
command_on: 'JgBIAAABJpMTEhITEhMRFBEUERMROBIUETgRFRA4ERQTNxITEjcRFBE4FBEUERITETgSExITERQRExM3ETgSOBEUETgROBI5EQANBQ=='

View File

@@ -0,0 +1,16 @@
- platform: broadlink
#host: 10.0.0.106
mac: 78:0F:77:FD:46:6C
timeout: 60
#type: rm2_pro_plus
switches:
# amibot
- name: Amibot_Clean
friendly_name: "Nettoyage"
command_on: 'JgBIAAABJpMSExITEhMRFBETEhMSNxMTETgRFBE4EhMSOBEUETgSExITERQROBEUERMSExI4ERQROBI4EhMROBE4EjgSExI3EgANBQ=='
- name: Amibot_Base
friendly_name: "Base"
command_on: 'JgBIAAABJpMTEhITEhMRFBEUERMROBIUETgRFRA4ERQTNxITEjcRFBE4FBEUERITETgSExITERQRExM3ETgSOBEUETgROBI5EQANBQ=='

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,84 @@
# pc TOWER NAS
- platform: wake_on_lan
name: "NAS Unraid Tower"
mac: d4:be:d9:93:5e:dd
host: 10.0.0.240
turn_off:
service: shell_command.turn_off_unraid_pc
# pc proxmoxM83
- platform: wake_on_lan
name: "pve-lenovoM83"
mac: 00:23:24:79:3e:e0
host: 10.0.0.178
turn_off:
service: shell_command.turn_off_m83lenovo_pc
# pc HP3300_debian3D
- platform: wake_on_lan
name: "pc_HP3300"
mac: 3C:D9:2B:74:C1:47
host: 10.0.1.208
turn_off:
service: shell_command.turn_off_hp3300_pc
# pc Lenovo M91 Bureau
- platform: wake_on_lan
name: "pc_lenovoM91_bureau"
mac: 44:37:e6:6b:53:86
host: 10.0.0.113
turn_off:
service: shell_command.turn_off_lenovom91_bureau_pc
# pc HPdc7900_NAS
- platform: wake_on_lan
name: "hpdc7900_NAS16"
mac: 00:25:b3:12:45:b9
host: 10.0.0.16
turn_off:
service: shell_command.turn_off_hp_debnas_pc
# pc atermitter
- platform: wake_on_lan
name: "pc_atermitter"
mac: 00:E0:0B:14:35:7A
host: 10.0.1.73
turn_off:
service: shell_command.turn_off_atermitter_pc
# pc BUREAU Aorus
- platform: wake_on_lan
name: "pc_Aorus"
mac: 18:C0:4D:B5:65:74
host: 10.0.0.128
turn_off:
service: shell_command.turn_off_pc_aorus
# pc solar-deb
- platform: wake_on_lan
name: "solar-deb"
mac: 9c:8e:99:c7:83:29
host: 10.0.0.14
turn_off:
service: mqtt.publish
data_template:
topic: "cmnd/solar-deb"
payload: "shutdown"
# pc yoga
- platform: wake_on_lan
name: "pc-yoga"
mac: 60:57:18:99:ed:05
host: 10.0.0.24
turn_off:
service: shell_command.turn_off_pc_yoga
#screen_off
- platform: template
switches:
yoga_screen:
friendly_name: "Screen PC Yoga"
unique_id: "pc-yoga1"
turn_on:
service: shell_command.screen_on_pc_yoga
turn_off:
service: shell_command.screen_off_pc_yoga

View File

@@ -0,0 +1,208 @@
- sensor:
- name: "Alerte Orages"
unique_id: sensor.alerte_orages
state: >-
{% set wa = (state_attr('sensor.43_weather_alert', 'Orages') ) %}
{% set at = (state_attr('binary_sensor.meteoalarm','awareness_type') ) %}
{% set ev = (state_attr('binary_sensor.meteoalarm','event') ) %}
{% if wa in ['Vert', 'Jaune', 'Orange', 'Rouge'] %}
{% set al = wa %}
{% elif 'jaune orages' in ev %}
{% set al = 'Jaune' %}
{% elif 'orange orages' in ev %}
{% set al = 'Orange' %}
{% elif 'rouge orages' in ev %}
{% set al = 'Rouge' %}
{% else %}
{% set al = 'Vert' %}
{% endif %}
{{ al }}
icon: mdi:weather-lightning
- name: "Alerte Vent violent"
unique_id: sensor.alerte_vent_violent
state: >-
{% set wa = (state_attr('sensor.43_weather_alert', 'Vent violent') ) %}
{% set at = (state_attr('binary_sensor.meteoalarm','awareness_type') ) %}
{% set ev = (state_attr('binary_sensor.meteoalarm','event') ) %}
{% if wa in ['Vert', 'Jaune', 'Orange', 'Rouge'] %}
{% set al = wa %}
{% elif 'jaune vent-violent' in ev %}
{% set al = 'Jaune' %}
{% elif 'orange vent-violent' in ev %}
{% set al = 'Orange' %}
{% elif 'rouge vent-violent' in ev %}
{% set al = 'Rouge' %}
{% else %}
{% set al = 'Vert' %}
{% endif %}
{{ al }}
icon: mdi:weather-windy
- name: "Alerte Pluie Inondation"
unique_id: sensor.alerte_pluie_inondation
state: >-
{% set wa = (state_attr('sensor.43_weather_alert', 'Pluie_inondation') ) %}
{% set at = (state_attr('binary_sensor.meteoalarm','awareness_type') ) %}
{% set ev = (state_attr('binary_sensor.meteoalarm','event') ) %}
{% if wa in ['Vert', 'Jaune', 'Orange', 'Rouge'] %}
{% set al = wa %}
{% elif 'jaune pluie_inondation' in ev %}
{% set al = 'Jaune' %}
{% elif 'orange pluie_inondation' in ev %}
{% set al = 'Orange' %}
{% elif 'rouge pluie_inondation' in ev %}
{% set al = 'Rouge' %}
{% else %}
{% set al = 'Vert' %}
{% endif %}
{{ al }}
icon: mdi:weather-pouring
- name: "Alerte Inondation"
unique_id: sensor.alerte_inondation
state: >-
{% set wa = (state_attr('sensor.43_weather_alert', 'Inondation') ) %}
{% set at = (state_attr('binary_sensor.meteoalarm','awareness_type') ) %}
{% set ev = (state_attr('binary_sensor.meteoalarm','event') ) %}
{% if wa in ['Vert', 'Jaune', 'Orange', 'Rouge'] %}
{% set al = wa %}
{% elif 'jaune inondation' in ev %}
{% set al = 'Jaune' %}
{% elif 'orange inondation' in ev %}
{% set al = 'Orange' %}
{% elif 'rouge inondation' in ev %}
{% set al = 'Rouge' %}
{% else %}
{% set al = 'Vert' %}
{% endif %}
{{ al }}
icon: mdi:waves-arrow-up
- name: "Alerte Canicule"
unique_id: sensor.alerte_canicule
state: >-
{% set wa = (state_attr('sensor.43_weather_alert', 'Canicule') ) %}
{% set at = (state_attr('binary_sensor.meteoalarm','awareness_type') ) %}
{% set ev = (state_attr('binary_sensor.meteoalarm','event') ) %}
{% if wa in ['Vert', 'Jaune', 'Orange', 'Rouge'] %}
{% set al = wa %}
{% elif 'jaune canicule' in ev %}
{% set al = 'Jaune' %}
{% elif 'orange canicule' in ev %}
{% set al = 'Orange' %}
{% elif 'rouge canicule' in ev %}
{% set al = 'Rouge' %}
{% else %}
{% set al = 'Vert' %}
{% endif %}
{{ al }}
icon: mdi:weather-sunny
- name: "Alerte Neige-verglas"
unique_id: sensor.alerte_neige_verglas
state: >-
{% set wa = (state_attr('sensor.43_weather_alert', 'Neige-verglas') ) %}
{% set at = (state_attr('binary_sensor.meteoalarm','awareness_type') ) %}
{% set ev = (state_attr('binary_sensor.meteoalarm','event') ) %}
{% if wa in ['Vert', 'Jaune', 'Orange', 'Rouge'] %}
{% set al = wa %}
{% elif 'jaune neige-verglas' in ev %}
{% set al = 'Jaune' %}
{% elif 'orange neige-verglas' in ev %}
{% set al = 'Orange' %}
{% elif 'rouge neige-verglas' in ev %}
{% set al = 'Rouge' %}
{% else %}
{% set al = 'Vert' %}
{% endif %}
{{ al }}
icon: mdi:snowflake
- name: "Alerte Grand-froid"
unique_id: sensor.alerte_grand_froid
state: >-
{% set wa = (state_attr('sensor.43_weather_alert', 'Grand-froid') ) %}
{% set at = (state_attr('binary_sensor.meteoalarm','awareness_type') ) %}
{% set ev = (state_attr('binary_sensor.meteoalarm','event') ) %}
{% if wa in ['Vert', 'Jaune', 'Orange', 'Rouge'] %}
{% set al = wa %}
{% elif 'jaune grand-froid' in ev %}
{% set al = 'Jaune' %}
{% elif 'orange grand-froid' in ev %}
{% set al = 'Orange' %}
{% elif 'rouge grand-froid' in ev %}
{% set al = 'Rouge' %}
{% else %}
{% set al = 'Vert' %}
{% endif %}
{{ al }}
icon: mdi:snowman
- name: "Alerte Météo"
unique_id: sensor.alerte_meteo
state: >-
{% set wa = (states('sensor.43_weather_alert') ) %}
{% set al = (state_attr('binary_sensor.meteoalarm','awareness_level') ) %}
{% if wa in ['Vert', 'Jaune', 'Orange', 'Rouge'] %}
{% set al = wa %}
{% elif '1' in al %}
{% set al = 'Vert' %}
{% elif '2' in al %}
{% set al = 'Jaune' %}
{% elif '3' in al %}
{% set al = 'Orange' %}
{% elif '4' in al %}
{% set al = 'Rouge' %}
{% else %}
{% set al = 'unknown' %}
{% endif %}
{{ al }}
attributes:
Date: >-
{% set dt = (state_attr('binary_sensor.meteoalarm','urgency') ) %}
{% if dt == 'Future' %}
{% set val = 'Demain' %}
{% else %}
{% set val = dt %}
{% endif %}
{{ val }}
Orages: >-
{% set val = states('sensor.alerte_orages') %}
{{ val }}
Vent Violent: >-
{% set val = states('sensor.alerte_vent_violent') %}
{{ val }}
Pluie Inondation: >-
{% set val = states('sensor.alerte_pluie_inondation') %}
{{ val }}
Inondation: >-
{% set val = states('sensor.alerte_Inondation') %}
{{ val }}
Canicule: >-
{% set val = states('sensor.alerte_canicule') %}
{{ val }}
Grand Froid: >-
{% set val = states('sensor.alerte_grand_froid') %}
{{ val }}
Neige Verglas: >-
{% set val = states('sensor.alerte_neige_verglas') %}
{{ val }}
icon: mdi:weather-cloudy-alert
- binary_sensor:
- state: "{{states('sensor.athom_smart_plug_elegoomars_1_wattage') | float(default =0) > 5}}"
name: impression SLA en cours
unique_id: is_sla_printinig_running
device_class: running
delay_off: "00:02:00"
# - state: "{{states('sensor.bl0937_power_4') | float(default =0) > 5}}"
# name: Machine a laver en cours
# unique_id: is_washing_machine_running
# device_class: running
# delay_off: "00:02:00"
# - state: "{{states('sensor.bl0937_power_sl') | float(default =0) > 5}}"
# name: SecheLinge en cours
# unique_id: is_sechelinge_running
# device_class: running
# delay_off: "00:02:00"

View File

@@ -1,3 +1,23 @@
homeassistant:
# auth_providers:
# - type: trusted_networks
# trusted_networks:
# - 10.0.0.128
# - 10.0.0.113
# - 10.0.0.216
# - 10.0.0.217
# - 10.0.0.1
# allow_bypass_login: true
# allowlist_external_dirs:
# - /config/capture
customize: !include customize.yaml
packages: !include_dir_named packages
http: #si cette balise n'existe pas, ajoutez la, sinon ajouter seulement la suite (pas de duplication)
use_x_forwarded_for: true
trusted_proxies:
- 10.0.0.16
# Loads default set of integrations. Do not remove.
default_config:
@@ -6,6 +26,73 @@ default_config:
frontend:
themes: !include_dir_merge_named themes
zone:
- name: Travail
latitude: 45.26994161017957
longitude: 4.111876806976284
radius: 200
icon: mdi:factory
# This will override the default home zone
- name: home
latitude: 45.14203329491988
longitude: 4.075071876335187
radius: 20
icon: mdi:home
- name: Near_Home
latitude: 45.14203329491988
longitude: 4.075071876335187
radius: 200
icon: mdi:broadcast
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
group: !include groups.yaml
automation old: !include_dir_merge_list automation/
switch: !include_dir_merge_list 01capteur/switch/
light: !include_dir_merge_list 01capteur/light/
sensor: !include_dir_merge_list 01capteur/sensor/
binary_sensor: !include_dir_merge_list 01capteur/binary_sensor/
template: !include_dir_merge_list 01capteur/template/
cover: !include_dir_merge_list 01capteur/cover/
mqtt: !include 01capteur/mqtt/mqtt.yaml
device_tracker: !include_dir_merge_list 01capteur/device_tracker/
#openhasp: !include 01capteur/openhasp.yaml
irrigation_unlimited: !include 01capteur/irrigation.yaml
shell_command: !include 01capteur/shell_commands.yaml
#modbus: !include_dir_merge_list 01capteur/modbus/
#recorder: !include_dir_merge_list 01capteur/recorder/
duckdns:
domain: maison43
access_token: 0fd20557-743c-41da-858e-724143602751
recorder:
db_url: mysql://homeassistant:homeassistant@core-mariadb/homeassistant?charset=utf8mb4
purge_keep_days: 30
auto_purge: true
auto_repack: true
commit_interval: 5
alarm_control_panel:
- platform: manual
name: Home Alarm
code: "1974"
arming_time: 30
delay_time: 20
trigger_time: 4
disarmed:
trigger_time: 0
armed_home:
arming_time: 0
delay_time: 0
armed_night:
arming_time: 0
delay_time: 0
api:
wake_on_lan:

View File

@@ -0,0 +1,350 @@
#!/usr/bin/env python3
import socket
import binascii
import logging
import time
from datetime import datetime
_LOGGER = logging.getLogger(__name__)
class APSystemsInvalidData(Exception):
pass
class APSystemsSocket:
def __init__(self, ipaddr, nographs, port=8899, raw_ecu=None, raw_inverter=None):
global no_graphs
no_graphs = nographs
self.ipaddr = ipaddr
self.port = port
# what do we expect socket data to end in
self.recv_suffix = b'END\n'
# how long to wait on socket commands until we get our recv_suffix
self.timeout = 10
# how big of a buffer to read at a time from the socket
# https://github.com/ksheumaker/homeassistant-apsystems_ecur/issues/108
self.recv_size = 1024
# how long to wait between socket open/closes
self.socket_sleep_time = 5
self.cmd_suffix = "END\n"
self.ecu_query = "APS1100160001" + self.cmd_suffix
self.inverter_query_prefix = "APS1100280002"
self.inverter_query_suffix = self.cmd_suffix
self.inverter_signal_prefix = "APS1100280030"
self.inverter_signal_suffix = self.cmd_suffix
self.ecu_id = None
self.qty_of_inverters = 0
self.qty_of_online_inverters = 0
self.lifetime_energy = 0
self.current_power = 0
self.today_energy = 0
self.inverters = {}
self.firmware = None
self.timezone = None
self.last_update = None
self.vsl = 0
self.tsl = 0
self.ecu_raw_data = raw_ecu
self.inverter_raw_data = raw_inverter
self.inverter_raw_signal = None
self.read_buffer = b''
self.socket = None
self.socket_open = False
self.errors = []
def send_read_from_socket(self, cmd):
try:
self.sock.settimeout(self.timeout)
self.sock.sendall(cmd.encode('utf-8'))
time.sleep(self.socket_sleep_time)
self.read_buffer = b''
self.sock.settimeout(self.timeout)
# An infinite loop was causing the integration to block
# https://github.com/ksheumaker/homeassistant-apsystems_ecur/issues/115
# Solution might cause a new issue when large solar array's applies
self.read_buffer = self.sock.recv(self.recv_size)
return self.read_buffer
except Exception as err:
self.close_socket()
raise APSystemsInvalidData(err)
def close_socket(self):
try:
if self.socket_open:
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
self.socket_open = False
except Exception as err:
raise APSystemsInvalidData(err)
def open_socket(self):
self.socket_open = False
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.sock.settimeout(self.timeout)
self.sock.connect((self.ipaddr, self.port))
self.socket_open = True
except Exception as err:
raise APSystemsInvalidData(err)
def query_ecu(self):
#read ECU data
self.open_socket()
self.ecu_raw_data = self.send_read_from_socket(self.ecu_query)
self.close_socket()
try:
self.process_ecu_data()
except Exception as err:
raise APSystemsInvalidData(err)
#read inverter data
# Some ECUs like the socket to be closed and re-opened between commands
self.open_socket()
cmd = self.inverter_query_prefix + self.ecu_id + self.inverter_query_suffix
self.inverter_raw_data = self.send_read_from_socket(cmd)
self.close_socket()
#read signal data
# Some ECUs like the socket to be closed and re-opened between commands
self.open_socket()
cmd = self.inverter_signal_prefix + self.ecu_id + self.inverter_signal_suffix
self.inverter_raw_signal = self.send_read_from_socket(cmd)
self.close_socket()
data = self.process_inverter_data()
data["ecu_id"] = self.ecu_id
if self.lifetime_energy != 0:
data["lifetime_energy"] = self.lifetime_energy
data["current_power"] = self.current_power
# apply filter for ECU-R-pro firmware bug where both are zero
if self.qty_of_inverters > 0:
data["qty_of_inverters"] = self.qty_of_inverters
data["today_energy"] = self.today_energy
data["qty_of_online_inverters"] = self.qty_of_online_inverters
return(data)
def aps_int_from_bytes(self, codec: bytes, start: int, length: int) -> int:
try:
return int (binascii.b2a_hex(codec[(start):(start+length)]), 16)
except ValueError as err:
debugdata = binascii.b2a_hex(codec)
error = f"Unable to convert binary to int with length={length} at location={start} with data={debugdata}"
raise APSystemsInvalidData(error)
def aps_uid(self, codec, start):
return str(binascii.b2a_hex(codec[(start):(start+12)]))[2:14]
def aps_str(self, codec, start, amount):
return str(codec[start:(start+amount)])[2:(amount+2)]
def aps_datetimestamp(self, codec, start, amount):
timestr=str(binascii.b2a_hex(codec[start:(start+amount)]))[2:(amount+2)]
return timestr[0:4]+"-"+timestr[4:6]+"-"+timestr[6:8]+" "+timestr[8:10]+":"+timestr[10:12]+":"+timestr[12:14]
def check_ecu_checksum(self, data, cmd):
datalen = len(data) - 1
try:
checksum = int(data[5:9])
except ValueError as err:
debugdata = binascii.b2a_hex(data)
error = f"could not extract checksum int from '{cmd}' data={debugdata}"
raise APSystemsInvalidData(error)
if datalen != checksum:
debugdata = binascii.b2a_hex(data)
error = f"Checksum on '{cmd}' failed checksum={checksum} datalen={datalen} data={debugdata}"
raise APSystemsInvalidData(error)
start_str = self.aps_str(data, 0, 3)
end_str = self.aps_str(data, len(data) - 4, 3)
if start_str != 'APS':
debugdata = binascii.b2a_hex(data)
error = f"Result on '{cmd}' incorrect start signature '{start_str}' != APS data={debugdata}"
raise APSystemsInvalidData(error)
if end_str != 'END':
debugdata = binascii.b2a_hex(data)
error = f"Result on '{cmd}' incorrect end signature '{end_str}' != END data={debugdata}"
raise APSystemsInvalidData(error)
return True
def process_ecu_data(self, data=None):
if self.ecu_raw_data != '' and (self.aps_str(self.ecu_raw_data,9,4)) == '0001':
data = self.ecu_raw_data
_LOGGER.debug(binascii.b2a_hex(data))
self.check_ecu_checksum(data, "ECU Query")
self.ecu_id = self.aps_str(data, 13, 12)
self.lifetime_energy = self.aps_int_from_bytes(data, 27, 4) / 10
self.current_power = self.aps_int_from_bytes(data, 31, 4)
self.today_energy = self.aps_int_from_bytes(data, 35, 4) / 100
if self.aps_str(data,25,2) == "01":
self.qty_of_inverters = self.aps_int_from_bytes(data, 46, 2)
self.qty_of_online_inverters = self.aps_int_from_bytes(data, 48, 2)
self.vsl = int(self.aps_str(data, 52, 3))
self.firmware = self.aps_str(data, 55, self.vsl)
self.tsl = int(self.aps_str(data, 55 + self.vsl, 3))
self.timezone = self.aps_str(data, 58 + self.vsl, self.tsl)
elif self.aps_str(data,25,2) == "02":
self.qty_of_inverters = self.aps_int_from_bytes(data, 39, 2)
self.qty_of_online_inverters = self.aps_int_from_bytes(data, 41, 2)
self.vsl = int(self.aps_str(data, 49, 3))
self.firmware = self.aps_str(data, 52, self.vsl)
def process_signal_data(self, data=None):
signal_data = {}
if self.inverter_raw_signal != '' and (self.aps_str(self.inverter_raw_signal,9,4)) == '0030':
data = self.inverter_raw_signal
_LOGGER.debug(binascii.b2a_hex(data))
self.check_ecu_checksum(data, "Signal Query")
if not self.qty_of_inverters:
return signal_data
location = 15
for i in range(0, self.qty_of_inverters):
uid = self.aps_uid(data, location)
location += 6
strength = data[location]
location += 1
strength = int((strength / 255) * 100)
signal_data[uid] = strength
return signal_data
def process_inverter_data(self, data=None):
output = {}
if self.inverter_raw_data != '' and (self.aps_str(self.inverter_raw_data,9,4)) == '0002':
data = self.inverter_raw_data
_LOGGER.debug(binascii.b2a_hex(data))
self.check_ecu_checksum(data, "Inverter data")
istr = ''
cnt1 = 0
cnt2 = 26
if self.aps_str(data, 14, 2) == '00':
timestamp = self.aps_datetimestamp(data, 19, 14)
inverter_qty = self.aps_int_from_bytes(data, 17, 2)
self.last_update = timestamp
output["timestamp"] = timestamp
output["inverters"] = {}
signal = self.process_signal_data()
inverters = {}
while cnt1 < inverter_qty:
inv={}
if self.aps_str(data, 15, 2) == '01':
inverter_uid = self.aps_uid(data, cnt2)
inv["uid"] = inverter_uid
inv["online"] = bool(self.aps_int_from_bytes(data, cnt2 + 6, 1))
istr = self.aps_str(data, cnt2 + 7, 2)
# Should graphs be updated?
if inv["online"] == False and no_graphs == True:
inv["signal"] = None
else:
inv["signal"] = signal.get(inverter_uid, 0)
# Distinguishes the different inverters from this point down
if istr in [ '01', '04', '05']:
power = []
voltages = []
# Should graphs be updated?
if inv["online"] == True:
inv["temperature"] = self.aps_int_from_bytes(data, cnt2 + 11, 2) - 100
if inv["online"] == False and no_graphs == True:
inv["frequency"] = None
power.append(None)
voltages.append(None)
power.append(None)
voltages.append(None)
else:
inv["frequency"] = self.aps_int_from_bytes(data, cnt2 + 9, 2) / 10
power.append(self.aps_int_from_bytes(data, cnt2 + 13, 2))
voltages.append(self.aps_int_from_bytes(data, cnt2 + 15, 2))
power.append(self.aps_int_from_bytes(data, cnt2 + 17, 2))
voltages.append(self.aps_int_from_bytes(data, cnt2 + 19, 2))
inv_details = {
"model" : "YC600/DS3 series",
"channel_qty" : 2,
"power" : power,
"voltage" : voltages
}
inv.update(inv_details)
cnt2 = cnt2 + 21
elif istr == '02':
power = []
voltages = []
# Should graphs be updated?
if inv["online"]:
inv["temperature"] = self.aps_int_from_bytes(data, cnt2 + 11, 2) - 100
if inv["online"] == False and no_graphs == True:
inv["frequency"] = None
power.append(None)
voltages.append(None)
power.append(None)
voltages.append(None)
power.append(None)
voltages.append(None)
power.append(None)
else:
inv["frequency"] = self.aps_int_from_bytes(data, cnt2 + 9, 2) / 10
power.append(self.aps_int_from_bytes(data, cnt2 + 13, 2))
voltages.append(self.aps_int_from_bytes(data, cnt2 + 15, 2))
power.append(self.aps_int_from_bytes(data, cnt2 + 17, 2))
voltages.append(self.aps_int_from_bytes(data, cnt2 + 19, 2))
power.append(self.aps_int_from_bytes(data, cnt2 + 21, 2))
voltages.append(self.aps_int_from_bytes(data, cnt2 + 23, 2))
power.append(self.aps_int_from_bytes(data, cnt2 + 25, 2))
inv_details = {
"model" : "YC1000/QT2",
"channel_qty" : 4,
"power" : power,
"voltage" : voltages
}
inv.update(inv_details)
cnt2 = cnt2 + 27
elif istr == '03':
power = []
voltages = []
# Should graphs be updated?
if inv["online"]:
inv["temperature"] = self.aps_int_from_bytes(data, cnt2 + 11, 2) - 100
if inv["online"] == False and no_graphs == True:
inv["frequency"] = None
power.append(None)
voltages.append(None)
power.append(None)
power.append(None)
power.append(None)
else:
inv["frequency"] = self.aps_int_from_bytes(data, cnt2 + 9, 2) / 10
power.append(self.aps_int_from_bytes(data, cnt2 + 13, 2))
voltages.append(self.aps_int_from_bytes(data, cnt2 + 15, 2))
power.append(self.aps_int_from_bytes(data, cnt2 + 17, 2))
power.append(self.aps_int_from_bytes(data, cnt2 + 19, 2))
power.append(self.aps_int_from_bytes(data, cnt2 + 21, 2))
inv_details = {
"model" : "QS1",
"channel_qty" : 4,
"power" : power,
"voltage" : voltages
}
inv.update(inv_details)
cnt2 = cnt2 + 23
else:
cnt2 = cnt2 + 9
inverters[inverter_uid] = inv
cnt1 = cnt1 + 1
self.inverters = inverters
output["inverters"] = inverters
return (output)

View File

@@ -0,0 +1,241 @@
import logging
import requests
import voluptuous as vol
import traceback
import datetime as dt
from datetime import timedelta
from .APSystemsSocket import APSystemsSocket, APSystemsInvalidData
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.entity import Entity
from homeassistant import config_entries, exceptions
from homeassistant.helpers import device_registry as dr
from homeassistant.components.persistent_notification import (
create as create_persistent_notification
)
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [ "sensor", "binary_sensor", "switch" ]
class WiFiSet():
ipaddr = ""
ssid = ""
wpa = ""
cache = 3
WiFiSet = WiFiSet()
# handle all the communications with the ECUR class and deal with our need for caching, etc
class ECUR():
def __init__(self, ipaddr, ssid, wpa, cache, nographs):
self.ecu = APSystemsSocket(ipaddr, nographs)
self.cache_count = 0
self.data_from_cache = False
self.querying = True
self.inverters_online = True
self.ecu_restarting = False
self.cached_data = {}
WiFiSet.ipaddr = ipaddr
WiFiSet.ssid = ssid
WiFiSet.wpa = wpa
WiFiSet.cache = cache
def stop_query(self):
self.querying = False
def start_query(self):
self.querying = True
def inverters_off(self):
headers = {'X-Requested-With': 'XMLHttpRequest'}
url = 'http://'+ str(WiFiSet.ipaddr) + '/index.php/configuration/set_switch_all_off'
try:
get_url = requests.post(url, headers=headers)
self.inverters_online = False
_LOGGER.debug(f"Response from ECU on switching the inverters off: {str(get_url.status_code)}")
except Exception as err:
_LOGGER.warning(f"Attempt to switch inverters off failed with error: {err} (This switch is only compatible with ECU-R pro and ECU-C type ECU's)")
def inverters_on(self):
headers = {'X-Requested-With': 'XMLHttpRequest'}
url = 'http://'+ str(WiFiSet.ipaddr) + '/index.php/configuration/set_switch_all_on'
try:
get_url = requests.post(url, headers=headers)
self.inverters_online = True
_LOGGER.debug(f"Response from ECU on switching the inverters on: {str(get_url.status_code)}")
except Exception as err:
_LOGGER.warning(f"Attempt to switch inverters on failed with error: {err} (This switch is only compatible with ECU-R pro and ECU-C type ECU's)")
def use_cached_data(self, msg):
# we got invalid data, so we need to pull from cache
self.error_msg = msg
self.cache_count += 1
self.data_from_cache = True
if self.cache_count == WiFiSet.cache:
_LOGGER.warning(f"Communication with the ECU failed after {WiFiSet.cache} repeated attempts.")
data = {'SSID': WiFiSet.ssid, 'channel': 0, 'method': 2, 'psk_wep': '', 'psk_wpa': WiFiSet.wpa}
_LOGGER.debug(f"Data sent with URL: {data}")
# Determine ECU type to decide ECU restart (for ECU-C and ECU-R with sunspec only)
if (self.cached_data.get("ecu_id", None)[0:3] == "215") or (self.cached_data.get("ecu_id", None)[0:4] == "2162"):
url = 'http://' + str(WiFiSet.ipaddr) + '/index.php/management/set_wlan_ap'
headers = {'X-Requested-With': 'XMLHttpRequest'}
try:
get_url = requests.post(url, headers=headers, data=data)
_LOGGER.debug(f"Response from ECU on restart: {str(get_url.status_code)}")
self.ecu_restarting = True
except Exception as err:
_LOGGER.warning(f"Attempt to restart ECU failed with error: {err}. Querying is stopped automatically.")
self.querying = False
else:
# Older ECU-R models starting with 2160
_LOGGER.warning("Try manually power cycling the ECU. Querying is stopped automatically, turn switch back on after restart of ECU.")
self.querying = False
if self.cached_data.get("ecu_id", None) == None:
_LOGGER.debug(f"Cached data {self.cached_data}")
raise UpdateFailed(f"Unable to get correct data from ECU, and no cached data. See log for details, and try power cycling the ECU.")
return self.cached_data
def update(self):
data = {}
# if we aren't actively quering data, pull data form the cache
# this is so we can stop querying after sunset
if not self.querying:
_LOGGER.debug("Not querying ECU due to query=False")
data = self.cached_data
self.data_from_cache = True
data["data_from_cache"] = self.data_from_cache
data["querying"] = self.querying
return self.cached_data
_LOGGER.debug("Querying ECU...")
try:
data = self.ecu.query_ecu()
_LOGGER.debug("Got data from ECU")
# we got good results, so we store it and set flags about our cache state
if data["ecu_id"] != None:
self.cached_data = data
self.cache_count = 0
self.data_from_cache = False
self.ecu_restarting = False
self.error_message = ""
else:
msg = f"Using cached data from last successful communication from ECU. Error: no ecu_id returned"
_LOGGER.warning(msg)
data = self.use_cached_data(msg)
except APSystemsInvalidData as err:
msg = f"Using cached data from last successful communication from ECU. Invalid data error: {err}"
if str(err) != 'timed out':
_LOGGER.warning(msg)
data = self.use_cached_data(msg)
except Exception as err:
msg = f"Using cached data from last successful communication from ECU. Exception error: {err}"
_LOGGER.warning(msg)
data = self.use_cached_data(msg)
data["data_from_cache"] = self.data_from_cache
data["querying"] = self.querying
data["restart_ecu"] = self.ecu_restarting
_LOGGER.debug(f"Returning {data}")
if data.get("ecu_id", None) == None:
raise UpdateFailed(f"Somehow data doesn't contain a valid ecu_id")
return data
async def update_listener(hass, config):
# Handle options update being triggered by config entry options updates
_LOGGER.debug(f"Configuration updated: {config.as_dict()}")
ecu = ECUR(config.data["host"],
config.data["SSID"],
config.data["WPA-PSK"],
config.data["CACHE"],
config.data["stop_graphs"]
)
async def async_setup_entry(hass, config):
# Setup the APsystems platform """
hass.data.setdefault(DOMAIN, {})
host = config.data["host"]
interval = timedelta(seconds=config.data["scan_interval"])
# Defaults for new parameters that might not have been set yet from previous integration versions
cache = config.data.get("CACHE", 5)
ssid = config.data.get("SSID", "ECU-WiFi_SSID")
wpa = config.data.get("WPA-PSK", "myWiFipassword")
nographs = config.data.get("stop_graphs", False)
ecu = ECUR(host, ssid, wpa, cache, nographs)
async def do_ecu_update():
return await hass.async_add_executor_job(ecu.update)
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=DOMAIN,
update_method=do_ecu_update,
update_interval=interval,
)
hass.data[DOMAIN] = {
"ecu" : ecu,
"coordinator" : coordinator
}
await coordinator.async_config_entry_first_refresh()
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=config.entry_id,
identifiers={(DOMAIN, f"ecu_{ecu.ecu.ecu_id}")},
manufacturer="APSystems",
suggested_area="Roof",
name=f"ECU {ecu.ecu.ecu_id}",
model=ecu.ecu.firmware,
sw_version=ecu.ecu.firmware,
)
inverters = coordinator.data.get("inverters", {})
for uid,inv_data in inverters.items():
model = inv_data.get("model", "Inverter")
device_registry.async_get_or_create(
config_entry_id=config.entry_id,
identifiers={(DOMAIN, f"inverter_{uid}")},
manufacturer="APSystems",
suggested_area="Roof",
name=f"Inverter {uid}",
model=inv_data.get("model")
)
await hass.config_entries.async_forward_entry_setups(config, PLATFORMS)
config.async_on_unload(config.add_update_listener(update_listener))
return True
async def async_remove_config_entry_device(hass, config, device_entry) -> bool:
if device_entry is not None:
# Notify the user that the device has been removed
create_persistent_notification(
hass,
title="Important notification",
message=f"The following device was removed from the system: {device_entry}"
)
return True
else:
return False
async def async_unload_entry(hass, config):
unload_ok = await hass.config_entries.async_unload_platforms(config, PLATFORMS)
coordinator = hass.data[DOMAIN].get("coordinator")
ecu = hass.data[DOMAIN].get("ecu")
ecu.stop_query()
if unload_ok:
hass.data[DOMAIN].pop(config.entry_id)
return unload_ok

View File

@@ -0,0 +1,92 @@
from datetime import timedelta
import logging
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
)
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)
from .const import (
DOMAIN,
RELOAD_ICON,
CACHE_ICON,
RESTART_ICON
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config, add_entities, discovery_info=None):
ecu = hass.data[DOMAIN].get("ecu")
coordinator = hass.data[DOMAIN].get("coordinator")
sensors = [
APSystemsECUBinarySensor(coordinator, ecu, "data_from_cache",
label="Using Cached Data", icon=CACHE_ICON),
APSystemsECUBinarySensor(coordinator, ecu, "restart_ecu",
label="Restart", icon=RESTART_ICON)
]
add_entities(sensors)
class APSystemsECUBinarySensor(CoordinatorEntity, BinarySensorEntity):
def __init__(self, coordinator, ecu, field, label=None, devclass=None, icon=None):
super().__init__(coordinator)
self.coordinator = coordinator
self._ecu = ecu
self._field = field
self._label = label
if not label:
self._label = field
self._icon = icon
self._name = f"ECU {self._label}"
self._state = None
@property
def unique_id(self):
return f"{self._ecu.ecu.ecu_id}_{self._field}"
@property
def name(self):
return self._name
@property
def is_on(self):
return self.coordinator.data.get(self._field)
@property
def icon(self):
return self._icon
@property
def extra_state_attributes(self):
attrs = {
"ecu_id" : self._ecu.ecu.ecu_id,
"firmware" : self._ecu.ecu.firmware,
"timezone" : self._ecu.ecu.timezone,
"last_update" : self._ecu.ecu.last_update
}
return attrs
@property
def entity_category(self):
return EntityCategory.DIAGNOSTIC
@property
def device_info(self):
parent = f"ecu_{self._ecu.ecu.ecu_id}"
return {
"identifiers": {
(DOMAIN, parent),
}
}

View File

@@ -0,0 +1,103 @@
import logging
import voluptuous as vol
import traceback
from datetime import timedelta
from homeassistant.core import callback
from .APSystemsSocket import APSystemsSocket, APSystemsInvalidData
from homeassistant import config_entries, exceptions
from homeassistant.const import CONF_HOST, CONF_SCAN_INTERVAL
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
from .const import DOMAIN, CONF_SSID, CONF_WPA_PSK, CONF_CACHE, CONF_STOP_GRAPHS
STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str,
vol.Required(CONF_SCAN_INTERVAL, default=300): int,
vol.Optional(CONF_CACHE, default=5): int,
vol.Optional(CONF_SSID, default="ECU-WIFI_local"): str,
vol.Optional(CONF_WPA_PSK, default="default"): str,
vol.Optional(CONF_STOP_GRAPHS, default=False): bool,
})
@config_entries.HANDLERS.register(DOMAIN)
class APSsystemsFlowHandler(config_entries.ConfigFlow):
VERSION = 1
def __init__(self):
_LOGGER.debug("Starting config flow class...")
async def async_step_user(self, user_input=None):
_LOGGER.debug("Starting user step")
errors = {}
if user_input is None:
_LOGGER.debug("Show form because user input is empty")
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
_LOGGER.debug("User input is not empty, processing input")
try:
_LOGGER.debug("Initial attempt to query ECU")
ap_ecu = APSystemsSocket(user_input["host"], user_input["stop_graphs"])
test_query = await self.hass.async_add_executor_job(ap_ecu.query_ecu)
ecu_id = test_query.get("ecu_id", None)
if ecu_id != None:
return self.async_create_entry(title=f"ECU: {ecu_id}", data=user_input)
else:
errors["host"] = "no_ecuid"
except APSystemsInvalidData as err:
_LOGGER.exception(f"APSystemsInvalidData exception: {err}")
errors["host"] = "cannot_connect"
except Exception as err:
_LOGGER.exception(f"Unknown error occurred during setup: {err}")
errors["host"] = "unknown"
@staticmethod
@callback
def async_get_options_flow(config_entry):
_LOGGER.debug("get options flow")
return APSsystemsOptionsFlowHandler(config_entry)
class APSsystemsOptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
_LOGGER.debug("Starting options flow step class")
self.config_entry = config_entry
async def async_step_init(self, user_input=None):
errors = {}
if user_input is None:
return self.async_show_form(
step_id="init",
errors=errors,
data_schema=vol.Schema({
vol.Required(CONF_HOST, default=self.config_entry.data.get(CONF_HOST)): str,
vol.Optional(CONF_SCAN_INTERVAL, default=300,
description={"suggested_value": self.config_entry.data.get(CONF_SCAN_INTERVAL)}): int,
vol.Optional(CONF_CACHE, default=5,
description={"suggested_value": self.config_entry.data.get(CONF_CACHE)}): int,
vol.Optional(CONF_SSID, default="ECU-WiFi_SSID",
description={"suggested_value": self.config_entry.data.get(CONF_SSID)}): str,
vol.Optional(CONF_WPA_PSK, default="myWiFipassword",
description={"suggested_value": self.config_entry.data.get(CONF_WPA_PSK)}): str,
vol.Optional(CONF_STOP_GRAPHS, default=self.config_entry.data.get(CONF_STOP_GRAPHS)): bool
})
)
try:
ap_ecu = APSystemsSocket(user_input["host"], user_input["stop_graphs"])
_LOGGER.debug("Attempt to query ECU")
test_query = await self.hass.async_add_executor_job(ap_ecu.query_ecu)
ecu_id = test_query.get("ecu_id", None)
if ecu_id != None:
self.hass.config_entries.async_update_entry(
self.config_entry, data=user_input, options=self.config_entry.options
)
coordinator = self.hass.data[DOMAIN].get("coordinator")
coordinator.update_interval = timedelta(seconds=self.config_entry.data.get(CONF_SCAN_INTERVAL))
return self.async_create_entry(title=f"ECU: {ecu_id}", data={})
else:
errors["host"] = "no_ecuid"
except APSystemsInvalidData as err:
errors["host"] = "cannot_connect"
except Exception as err:
_LOGGER.debug(f"Unknown error occurred during setup: {err}")
errors["host"] = "unknown"

View File

@@ -0,0 +1,13 @@
DOMAIN = 'apsystems_ecur'
SOLAR_ICON = "mdi:solar-power"
FREQ_ICON = "mdi:sine-wave"
SIGNAL_ICON = "mdi:signal"
RELOAD_ICON = "mdi:reload"
CACHE_ICON = "mdi:cached"
RESTART_ICON = "mdi:restart"
POWER_ICON = "mdi:power"
CONF_SSID = "SSID"
CONF_WPA_PSK = "WPA-PSK"
CONF_CACHE = "CACHE"
CONF_STOP_GRAPHS = "stop_graphs"

View File

@@ -0,0 +1,30 @@
from __future__ import annotations
import logging
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_TOKEN
from homeassistant.core import HomeAssistant
TO_REDACT = {CONF_TOKEN}
from .const import (
DOMAIN
)
_LOGGER = logging.getLogger(__name__)
async def async_get_device_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry, device: DeviceEntry
) -> dict:
"""Return diagnostics for a config entry."""
_LOGGER.debug("Diagnostics being called")
ecu = hass.data[DOMAIN].get("ecu")
_LOGGER.debug(f"Diagnostics being called {ecu}")
diag_data = {"entry": async_redact_data(ecu.ecu.dump_data(), TO_REDACT)}
return diag_data

View File

@@ -0,0 +1,14 @@
{
"domain": "apsystems_ecur",
"name": "APSystems PV solar ECU",
"codeowners": ["@ksheumaker"],
"config_flow": true,
"dependencies": [],
"documentation": "https://github.com/ksheumaker/homeassistant-apsystems_ecur",
"integration_type": "hub",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/ksheumaker/homeassistant-apsystems_ecur/issues",
"loggers": ["custom_components.apsystems_ecur"],
"requirements": [],
"version": "v1.4.3"
}

View File

@@ -0,0 +1,284 @@
from datetime import timedelta, datetime, date
import logging
import async_timeout
from homeassistant.util import dt as dt_util
from homeassistant.components.sensor import SensorEntity
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorStateClass,
)
from .const import (
DOMAIN,
SOLAR_ICON,
FREQ_ICON,
SIGNAL_ICON
)
from homeassistant.const import (
UnitOfPower,
UnitOfEnergy,
UnitOfTemperature,
UnitOfElectricPotential,
UnitOfFrequency,
PERCENTAGE
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config, add_entities, discovery_info=None):
ecu = hass.data[DOMAIN].get("ecu")
coordinator = hass.data[DOMAIN].get("coordinator")
sensors = [
APSystemsECUSensor(coordinator, ecu, "current_power",
label="Current Power",
unit=UnitOfPower.WATT,
devclass=SensorDeviceClass.POWER,
icon=SOLAR_ICON,
stateclass=SensorStateClass.MEASUREMENT
),
APSystemsECUSensor(coordinator, ecu, "today_energy",
label="Today Energy",
unit=UnitOfEnergy.KILO_WATT_HOUR,
devclass=SensorDeviceClass.ENERGY,
icon=SOLAR_ICON,
stateclass=SensorStateClass.TOTAL_INCREASING
),
APSystemsECUSensor(coordinator, ecu, "lifetime_energy",
label="Lifetime Energy",
unit=UnitOfEnergy.KILO_WATT_HOUR,
devclass=SensorDeviceClass.ENERGY,
icon=SOLAR_ICON,
stateclass=SensorStateClass.TOTAL_INCREASING
),
APSystemsECUSensor(coordinator, ecu, "qty_of_inverters",
label="Inverters",
icon=SOLAR_ICON,
entity_category=EntityCategory.DIAGNOSTIC
),
APSystemsECUSensor(coordinator, ecu, "qty_of_online_inverters",
label="Inverters Online",
icon=SOLAR_ICON,
entity_category=EntityCategory.DIAGNOSTIC
),
]
inverters = coordinator.data.get("inverters", {})
for uid,inv_data in inverters.items():
_LOGGER.debug(f"Inverter {uid} {inv_data.get('channel_qty')}")
# https://github.com/ksheumaker/homeassistant-apsystems_ecur/issues/110
if inv_data.get("channel_qty") != None:
sensors.extend([
APSystemsECUInverterSensor(coordinator, ecu, uid, "temperature",
label="Temperature",
unit=UnitOfTemperature.CELSIUS,
devclass=SensorDeviceClass.TEMPERATURE,
stateclass=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC
),
APSystemsECUInverterSensor(coordinator, ecu, uid, "frequency",
label="Frequency",
unit=UnitOfFrequency.HERTZ,
stateclass=SensorStateClass.MEASUREMENT,
devclass=SensorDeviceClass.FREQUENCY,
icon=FREQ_ICON,
entity_category=EntityCategory.DIAGNOSTIC
),
APSystemsECUInverterSensor(coordinator, ecu, uid, "voltage",
label="Voltage",
unit=UnitOfElectricPotential.VOLT,
stateclass=SensorStateClass.MEASUREMENT,
devclass=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC
),
APSystemsECUInverterSensor(coordinator, ecu, uid, "signal",
label="Signal",
unit=PERCENTAGE,
stateclass=SensorStateClass.MEASUREMENT,
devclass=SensorDeviceClass.SIGNAL_STRENGTH,
icon=SIGNAL_ICON,
entity_category=EntityCategory.DIAGNOSTIC
)
])
for i in range(0, inv_data.get("channel_qty", 0)):
sensors.append(
APSystemsECUInverterSensor(coordinator, ecu, uid, f"power",
index=i, label=f"Power Ch {i+1}",
unit=UnitOfPower.WATT,
devclass=SensorDeviceClass.POWER,
icon=SOLAR_ICON,
stateclass=SensorStateClass.MEASUREMENT
)
)
add_entities(sensors)
class APSystemsECUInverterSensor(CoordinatorEntity, SensorEntity):
def __init__(self, coordinator, ecu, uid, field, index=0, label=None, icon=None, unit=None, devclass=None, stateclass=None, entity_category=None):
super().__init__(coordinator)
self.coordinator = coordinator
self._index = index
self._uid = uid
self._ecu = ecu
self._field = field
self._devclass = devclass
self._label = label
if not label:
self._label = field
self._icon = icon
self._unit = unit
self._stateclass = stateclass
self._entity_category = entity_category
self._name = f"Inverter {self._uid} {self._label}"
self._state = None
@property
def unique_id(self):
field = self._field
if self._index != None:
field = f"{field}_{self._index}"
return f"{self._ecu.ecu.ecu_id}_{self._uid}_{field}"
@property
def device_class(self):
return self._devclass
@property
def name(self):
return self._name
@property
def state(self):
_LOGGER.debug(f"State called for {self._field}")
if self._field == "voltage":
return self.coordinator.data.get("inverters", {}).get(self._uid, {}).get("voltage", [])[0]
elif self._field == "power":
_LOGGER.debug(f"POWER {self._uid} {self._index}")
return self.coordinator.data.get("inverters", {}).get(self._uid, {}).get("power", [])[self._index]
else:
return self.coordinator.data.get("inverters", {}).get(self._uid, {}).get(self._field)
@property
def icon(self):
return self._icon
@property
def unit_of_measurement(self):
return self._unit
@property
def extra_state_attributes(self):
attrs = {
"ecu_id" : self._ecu.ecu.ecu_id,
"inverter_uid" : self._uid,
"last_update" : self._ecu.ecu.last_update,
}
return attrs
@property
def state_class(self):
_LOGGER.debug(f"State class {self._stateclass} - {self._field}")
return self._stateclass
@property
def device_info(self):
parent = f"inverter_{self._uid}"
return {
"identifiers": {
(DOMAIN, parent),
}
}
@property
def entity_category(self):
return self._entity_category
class APSystemsECUSensor(CoordinatorEntity, SensorEntity):
def __init__(self, coordinator, ecu, field, label=None, icon=None, unit=None, devclass=None, stateclass=None, entity_category=None):
super().__init__(coordinator)
self.coordinator = coordinator
self._ecu = ecu
self._field = field
self._label = label
if not label:
self._label = field
self._icon = icon
self._unit = unit
self._devclass = devclass
self._stateclass = stateclass
self._entity_category = entity_category
self._name = f"ECU {self._label}"
self._state = None
@property
def unique_id(self):
return f"{self._ecu.ecu.ecu_id}_{self._field}"
@property
def name(self):
return self._name
@property
def device_class(self):
return self._devclass
@property
def state(self):
_LOGGER.debug(f"State called for {self._field}")
return self.coordinator.data.get(self._field)
@property
def icon(self):
return self._icon
@property
def unit_of_measurement(self):
return self._unit
@property
def extra_state_attributes(self):
attrs = {
"ecu_id" : self._ecu.ecu.ecu_id,
"Firmware" : self._ecu.ecu.firmware,
"Timezone" : self._ecu.ecu.timezone,
"last_update" : self._ecu.ecu.last_update
}
return attrs
@property
def state_class(self):
_LOGGER.debug(f"State class {self._stateclass} - {self._field}")
return self._stateclass
@property
def device_info(self):
parent = f"ecu_{self._ecu.ecu.ecu_id}"
return {
"identifiers": {
(DOMAIN, parent),
}
}
@property
def entity_category(self):
return self._entity_category

View File

@@ -0,0 +1,132 @@
import logging
from homeassistant.util import dt as dt_util
from homeassistant.components.switch import SwitchEntity
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity
)
from .const import (
DOMAIN,
RELOAD_ICON,
POWER_ICON
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config, add_entities, discovery_info=None):
ecu = hass.data[DOMAIN].get("ecu")
coordinator = hass.data[DOMAIN].get("coordinator")
switches = [
APSystemsECUQuerySwitch(coordinator, ecu, "query_device",
label="Query Device", icon=RELOAD_ICON),
APSystemsECUInvertersSwitch(coordinator, ecu, "inverters_online",
label="Inverters Online", icon=POWER_ICON),
]
add_entities(switches)
class APSystemsECUQuerySwitch(CoordinatorEntity, SwitchEntity):
def __init__(self, coordinator, ecu, field, label=None, icon=None):
super().__init__(coordinator)
self.coordinator = coordinator
self._ecu = ecu
self._field = field
self._label = label
if not label:
self._label = field
self._icon = icon
self._name = f"ECU {self._label}"
self._state = True
@property
def unique_id(self):
return f"{self._ecu.ecu.ecu_id}_{self._field}"
@property
def name(self):
return self._name
@property
def icon(self):
return self._icon
@property
def device_info(self):
parent = f"ecu_{self._ecu.ecu.ecu_id}"
return {
"identifiers": {
(DOMAIN, parent),
}
}
@property
def entity_category(self):
return EntityCategory.CONFIG
@property
def is_on(self):
return self._ecu.querying
def turn_off(self, **kwargs):
self._ecu.stop_query()
self._state = False
self.schedule_update_ha_state()
def turn_on(self, **kwargs):
self._ecu.start_query()
self._state = True
self.schedule_update_ha_state()
class APSystemsECUInvertersSwitch(CoordinatorEntity, SwitchEntity):
def __init__(self, coordinator, ecu, field, label=None, icon=None):
super().__init__(coordinator)
self.coordinator = coordinator
self._ecu = ecu
self._field = field
self._label = label
if not label:
self._label = field
self._icon = icon
self._name = f"ECU {self._label}"
self._state = True
@property
def unique_id(self):
return f"{self._ecu.ecu.ecu_id}_{self._field}"
@property
def name(self):
return self._name
@property
def icon(self):
return self._icon
@property
def device_info(self):
parent = f"ecu_{self._ecu.ecu.ecu_id}"
return {
"identifiers": {
(DOMAIN, parent),
}
}
@property
def entity_category(self):
return EntityCategory.CONFIG
@property
def is_on(self):
return self._ecu.inverters_online
def turn_off(self, **kwargs):
self._ecu.inverters_off()
self._state = False
self.schedule_update_ha_state()
def turn_on(self, **kwargs):
self._ecu.inverters_on()
self._state = True
self.schedule_update_ha_state()

View File

@@ -0,0 +1,48 @@
{
"options": {
"step": {
"init": {
"data": {
"host": "ECU IP-Adresse (bitte Verbindungstabelle in der readme prüfen).",
"scan_interval": "ECU Abfrage Frequenz in Sekunden (minimum 300 empfohlen).",
"CACHE": "Wiederholungen, wenn ECU ausfällt (Bereich zwischen 1 - 5 empfohlen)",
"SSID": "SSID angeben (nur für Modelle ECU-R (sunspec) und ECU-C)",
"WPA-PSK": "Kennwort angeben (nur für Modelle ECU-R (sunspec) und ECU-C)",
"stop_graphs": "Aktualisieren die Diagramme nicht, wenn die Wechselrichter offline sind"
},
"title": "APsystems ECU Konfiguration"
}
},
"error": {
"cannot_connect": "Es wurde keine ECU unter dieser IP-Adresse gefunden oder life-time energy ist Null.",
"no_ecuid": "Es wurde keine ECU ID von der ECU zurückgegeben.",
"unknown": "Unbekannter Fehler, bitte Logs überprüfen."
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"config": {
"step": {
"user": {
"data": {
"host": "ECU IP-Adresse (bitte Verbindungstabelle in der readme prüfen).",
"scan_interval": "ECU Abfrage Frequenz in Sekunden (minimum 300 empfohlen).",
"CACHE": "Wiederholungen, wenn ECU ausfällt (Bereich zwischen 1 - 5 empfohlen)",
"SSID": "SSID angeben (nur für Modelle ECU-R (sunspec) und ECU-C)",
"WPA-PSK": "Kennwort angeben (nur für Modelle ECU-R (sunspec) und ECU-C)",
"stop_graphs": "Aktualisieren die Diagramme nicht, wenn die Wechselrichter offline sind"
},
"title": "APsystems ECU Optionen"
}
},
"error": {
"cannot_connect": "Es wurde keine ECU unter dieser IP-Adresse gefunden oder life-time energy ist Null.",
"no_ecuid": "Es wurde keine ECU ID von der ECU zurückgegeben.",
"unknown": "Unbekannter Fehler, bitte Logs überprüfen."
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@@ -0,0 +1,48 @@
{
"options": {
"step": {
"init": {
"data": {
"host": "ECU IP address (follow connection method table in readme)",
"scan_interval": "ECU query interval in seconds (minimum 300 recommended)",
"CACHE": "Retries when ECU fails (range between 1 - 5 recommended)",
"SSID": "Specify SSID (For ECU-R (sunspec) and ECU-C models only)",
"WPA-PSK": "Specify password (For ECU-R (sunspec) and ECU-C models only)",
"stop_graphs": "Do not update graphs when inverters are offline"
},
"title": "APsystems ECU Config"
}
},
"error": {
"cannot_connect": "Can't find ECU at this IP-Address or life-time energy is zero",
"no_ecuid": "No ECU ID returned from ECU",
"unknown": "Unknown error, see log for details"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"config": {
"step": {
"user": {
"data": {
"host": "ECU IP address (follow connection method table in readme)",
"scan_interval": "ECU query interval in seconds (minimum 300 recommended)",
"CACHE": "Retries when ECU fails (range between 1 - 5 recommended)",
"SSID": "Specify SSID (For ECU-R (sunspec) and ECU-C models only)",
"WPA-PSK": "Specify password (For ECU-R (sunspec) and ECU-C models only)",
"stop_graphs": "Do not update graphs when inverters are offline"
},
"title": "APsystems ECU Options"
}
},
"error": {
"cannot_connect": "Can't find ECU at this IP-Address or life-time energy is zero",
"no_ecuid": "No ECU ID returned from ECU",
"unknown": "Unknown error, see log for details"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@@ -0,0 +1,48 @@
{
"options": {
"step": {
"init": {
"data": {
"host": "Dirección IP de la ECU (Sigue el método para conectarse en la tabla del archivo readme)",
"scan_interval": "Intervalo de conexión a la ECU en segundos (Mínimo 300 recomendado)",
"CACHE": "Reintentos cuando la ECU falla (Rango entre 1 - 5 recomendado)",
"SSID": "Introduce SSID (Solo para modelos ECU-R (sunspec) and ECU-C)",
"WPA-PSK": "Introduce contraseña (Solo para modelos ECU-R (sunspec) and ECU-C)",
"stop_graphs": "No actualice los gráficos cuando los inversores estén fuera de línea"
},
"title": "Configuración APsystems ECU"
}
},
"error": {
"cannot_connect": "No puedo encontrar la ECU en esta dirección IP o la energía life-time es cero",
"no_ecuid": "La ECU no ha devuelto ningún ECU ID",
"unknown": "Error desconocido, lee el log para mas detalles"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"config": {
"step": {
"user": {
"data": {
"host": "Dirección IP de la ECU (Sigue el método para conectarse en la tabla del archivo readme)",
"scan_interval": "Intervalo de conexión a la ECU en segundos (Mínimo 300 recomendado)",
"CACHE": "Reintentos cuando la ECU falla (Rango entre 1 - 5 recomendado)",
"SSID": "Introduce SSID (Solo para modelos ECU-R (sunspec) and ECU-C)",
"WPA-PSK": "Introduce contraseña (Solo para modelos ECU-R (sunspec) and ECU-C)",
"stop_graphs": "No actualice los gráficos cuando los inversores estén fuera de línea"
},
"title": "Configuración APsystems ECU"
}
},
"error": {
"cannot_connect": "No puedo encontrar la ECU en esta dirección IP o la energía life-time es cero",
"no_ecuid": "La ECU no ha devuelto ningún ECU ID",
"unknown": "Error desconocido, lee el log para mas detalles"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@@ -0,0 +1,49 @@
{
"options": {
"step": {
"init": {
"data": {
"host": "Adresse IP ECU (Voir “Prerequisites” dans le fichier Readme)",
"scan_interval": "Intervalle des requêtes sur l'ECU en secondes (Min de 300 recommandées)",
"CACHE": "Nombre de tentatives en cas d'échec de communication (1 à 5 recommandée)",
"SSID": "Spécifier le SSID (Pour ECU-R (Sunspec) et ECU-C seulement)",
"WPA-PSK": "Spécifier le mot de passe (Pour ECU-R (Sunspec) et ECU-C seulement)",
"stop_graphs": "Ne pas mettre à jour les graphiques lorsque les onduleurs sont hors ligne"
},
"title": "Configuration ECU APsystems"
}
},
"error": {
"cannot_connect": "Ne trouve pas d'ECU à cette adresse IP ou énergie totale produite nulle",
"no_ecuid": "Pas d'ID ECU retourné pour cet ECU",
"unknown": "Erreur inconnue, veuillez consulter le journal des logs pour plus de détails"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"config": {
"step": {
"user": {
"data": {
"host": "Adresse IP ECU (Voir “Prerequisites” dans le fichier Readme)",
"scan_interval": "Intervalle des requêtes sur l'ECU en secondes (Min de 300 recommandées)",
"CACHE": "Nombre de tentatives en cas d'échec de communication (1 à 5 recommandée)",
"SSID": "Spécifier le SSID (Pour ECU-R (Sunspec) et ECU-C seulement)",
"WPA-PSK": "Spécifier le mot de passe (Pour ECU-R (Sunspec) et ECU-C seulement)",
"stop_graphs": "Ne pas mettre à jour les graphiques lorsque les onduleurs sont hors ligne"
},
"title": "Options ECU APsystems"
}
},
"error": {
"cannot_connect": "Ne trouve pas d'ECU à cette adresse IP ou énergie totale produite nulle",
"inverter": "Type d'onduleur inconnu, veuillez vérifier les journaux",
"no_ecuid": "Pas d'ID ECU retourné pour cet ECU",
"unknown": "Erreur inconnue, veuillez consulter le journal des logs pour plus de détails"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@@ -0,0 +1,48 @@
{
"options": {
"step": {
"init": {
"data": {
"host": "ECU IP-adres (volg de connectie methode tabel in de readme)",
"scan_interval": "ECU query interval in seconden (minimum 300 aanbevolen)",
"CACHE": "Pogingen als de ECU niet reageert (tussen 1 - 5 aanbevolen)",
"SSID": "Specificeer SSID (Alleen voor ECU-R (sunspec) en ECU-C modellen)",
"WPA-PSK": "Specificeer wachtwoord (voor ECU-R (sunspec) en ECU-C modellen)",
"stop_graphs": "Werk grafieken niet bij als de omvormers offline zijn"
},
"title": "APsystems ECU Configuratie"
}
},
"error": {
"cannot_connect": "Kan de ECU niet vinden op dit IP-adres of life-time energy is nul",
"no_ecuid": "Geen ECU ID ontvangen",
"unknown": "Onbekende fout, zie het log for details"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"config": {
"step": {
"user": {
"data": {
"host": "ECU IP-adres (volg de connectie methode tabel in de readme)",
"scan_interval": "ECU query interval in seconden (minimum 300 aanbevolen)",
"CACHE": "Pogingen als de ECU niet reageert (tussen 1 - 5 aanbevolen)",
"SSID": "Specificeer SSID (Alleen voor ECU-R (sunspec) en ECU-C modellen)",
"WPA-PSK": "Specificeer wachtwoord (voor ECU-R (sunspec) en ECU-C modellen)",
"stop_graphs": "Werk grafieken niet bij als de omvormers offline zijn"
},
"title": "APsystems ECU Opties"
}
},
"error": {
"cannot_connect": "Kan de ECU niet vinden op dit IP-adres of life-time energy is nul",
"no_ecuid": "Geen ECU ID ontvangen",
"unknown": "Onbekende fout, zie het log voor details"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@@ -0,0 +1,48 @@
# Copyright (c) 2019-2022, Andrey "Limych" Khrolenok <andrey@khrolenok.ru>
# Creative Commons BY-NC-SA 4.0 International Public License
# (see LICENSE.md or https://creativecommons.org/licenses/by-nc-sa/4.0/)
"""
The Average Sensor.
For more details about this sensor, please refer to the documentation at
https://github.com/Limych/ha-average/
"""
from __future__ import annotations
import logging
import voluptuous as vol
from homeassistant.const import SERVICE_RELOAD
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.reload import async_reload_integration_platforms
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN, PLATFORMS, STARTUP_MESSAGE
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the platforms."""
# Print startup message
_LOGGER.info(STARTUP_MESSAGE)
# await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
component = EntityComponent(_LOGGER, DOMAIN, hass)
async def reload_service_handler(service: ServiceCall) -> None:
"""Reload all average sensors from config."""
print("+++++++++++++++++++++++++")
print(component)
# print(hass.data[DATA_INSTANCES]["sensor"].entities[0])
await async_reload_integration_platforms(hass, DOMAIN, PLATFORMS)
hass.services.async_register(
DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({})
)
return True

View File

@@ -0,0 +1,65 @@
"""The Average Sensor.
For more details about this sensor, please refer to the documentation at
https://github.com/Limych/ha-average/
"""
from datetime import timedelta
from typing import Final
# Base component constants
from homeassistant.const import Platform
NAME: Final = "Average Sensor"
DOMAIN: Final = "average"
VERSION: Final = "2.3.4"
ISSUE_URL: Final = "https://github.com/Limych/ha-average/issues"
STARTUP_MESSAGE: Final = f"""
-------------------------------------------------------------------
{NAME}
Version: {VERSION}
This is a custom integration!
If you have ANY issues with this you need to open an issue here:
{ISSUE_URL}
-------------------------------------------------------------------
"""
PLATFORMS = [
Platform.SENSOR,
]
# Configuration and options
CONF_START: Final = "start"
CONF_END: Final = "end"
CONF_DURATION: Final = "duration"
CONF_PRECISION: Final = "precision"
CONF_PERIOD_KEYS: Final = [CONF_START, CONF_END, CONF_DURATION]
CONF_PROCESS_UNDEF_AS: Final = "process_undef_as"
# Defaults
DEFAULT_NAME: Final = "Average"
DEFAULT_PRECISION: Final = 2
# Attributes
ATTR_START: Final = "start"
ATTR_END: Final = "end"
ATTR_SOURCES: Final = "sources"
ATTR_COUNT_SOURCES: Final = "count_sources"
ATTR_AVAILABLE_SOURCES: Final = "available_sources"
ATTR_COUNT: Final = "count"
ATTR_MIN_VALUE: Final = "min_value"
ATTR_MAX_VALUE: Final = "max_value"
#
ATTR_TO_PROPERTY: Final = [
ATTR_START,
ATTR_END,
ATTR_SOURCES,
ATTR_COUNT_SOURCES,
ATTR_AVAILABLE_SOURCES,
ATTR_COUNT,
ATTR_MAX_VALUE,
ATTR_MIN_VALUE,
]
UPDATE_MIN_TIME: Final = timedelta(seconds=20)

View File

@@ -0,0 +1,19 @@
{
"domain": "average",
"name": "Average Sensor",
"after_dependencies": [
"history",
"recorder",
"weather"
],
"codeowners": [
"@Limych"
],
"config_flow": false,
"dependencies": [],
"documentation": "https://github.com/Limych/ha-average",
"iot_class": "calculated",
"issue_tracker": "https://github.com/Limych/ha-average/issues",
"requirements": [],
"version": "2.3.4"
}

View File

@@ -0,0 +1,561 @@
# Copyright (c) 2019-2022, Andrey "Limych" Khrolenok <andrey@khrolenok.ru>
# Creative Commons BY-NC-SA 4.0 International Public License
# (see LICENSE.md or https://creativecommons.org/licenses/by-nc-sa/4.0/)
"""The Average Sensor.
For more details about this sensor, please refer to the documentation at
https://github.com/Limych/ha-average/
"""
from __future__ import annotations
from collections.abc import Mapping
import datetime
import logging
import math
import numbers
from typing import Any, Optional
from _sha1 import sha1
import voluptuous as vol
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.group import expand_entity_ids
from homeassistant.components.recorder import get_instance, history
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.components.water_heater import DOMAIN as WATER_HEATER_DOMAIN
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT,
CONF_ENTITIES,
CONF_NAME,
CONF_UNIQUE_ID,
EVENT_HOMEASSISTANT_START,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, State, callback, split_entity_id
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.helpers.event import async_track_state_change
from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util
from homeassistant.util.unit_conversion import TemperatureConverter
from homeassistant.util.unit_system import TEMPERATURE_UNITS
from .const import (
ATTR_AVAILABLE_SOURCES,
ATTR_COUNT,
ATTR_COUNT_SOURCES,
ATTR_END,
ATTR_MAX_VALUE,
ATTR_MIN_VALUE,
ATTR_START,
ATTR_TO_PROPERTY,
CONF_DURATION,
CONF_END,
CONF_PERIOD_KEYS,
CONF_PRECISION,
CONF_PROCESS_UNDEF_AS,
CONF_START,
DEFAULT_NAME,
DEFAULT_PRECISION,
UPDATE_MIN_TIME,
)
_LOGGER = logging.getLogger(__name__)
def check_period_keys(conf):
"""Ensure maximum 2 of CONF_PERIOD_KEYS are provided."""
count = sum(param in conf for param in CONF_PERIOD_KEYS)
if (count == 1 and CONF_DURATION not in conf) or count > 2:
raise vol.Invalid(
"You must provide none, only "
+ CONF_DURATION
+ " or maximum 2 of the following: "
", ".join(CONF_PERIOD_KEYS)
)
return conf
PLATFORM_SCHEMA = vol.All(
PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_ENTITIES): cv.entity_ids,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_START): cv.template,
vol.Optional(CONF_END): cv.template,
vol.Optional(CONF_DURATION): cv.positive_time_period,
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): int,
vol.Optional(CONF_PROCESS_UNDEF_AS): vol.Any(int, float),
}
),
check_period_keys,
)
# pylint: disable=unused-argument
async def async_setup_platform(
hass: HomeAssistant, config, async_add_entities, discovery_info=None
):
"""Set up platform."""
start = config.get(CONF_START)
end = config.get(CONF_END)
for template in [start, end]:
if template is not None:
template.hass = hass
async_add_entities(
[
AverageSensor(
hass,
config.get(CONF_UNIQUE_ID),
config.get(CONF_NAME),
start,
end,
config.get(CONF_DURATION),
config.get(CONF_ENTITIES),
config.get(CONF_PRECISION),
config.get(CONF_PROCESS_UNDEF_AS),
)
]
)
# pylint: disable=too-many-instance-attributes
class AverageSensor(SensorEntity):
"""Implementation of an Average sensor."""
_unrecorded_attributes = frozenset(
{
ATTR_START,
ATTR_END,
ATTR_COUNT_SOURCES,
ATTR_AVAILABLE_SOURCES,
ATTR_COUNT,
ATTR_MAX_VALUE,
ATTR_MIN_VALUE,
}
)
# pylint: disable=too-many-arguments
def __init__(
self,
hass: HomeAssistant,
unique_id: Optional[str],
name: str,
start,
end,
duration,
entity_ids: list,
precision: int,
undef,
):
"""Initialize the sensor."""
self._start_template = start
self._end_template = end
self._duration = duration
self._period = self.start = self.end = None
self._precision = precision
self._undef = undef
self._temperature_mode = None
self.sources = expand_entity_ids(hass, entity_ids)
self.count_sources = len(self.sources)
self.available_sources = 0
self.count = 0
self.min_value = self.max_value = None
self._attr_name = name
self._attr_native_value = None
self._attr_native_unit_of_measurement = None
self._attr_icon = None
self._attr_state_class = SensorStateClass.MEASUREMENT
self._attr_device_class = None
#
self._attr_unique_id = (
str(
sha1(
";".join(
[str(start), str(duration), str(end), ",".join(self.sources)]
).encode("utf-8")
).hexdigest()
)
if unique_id == "__legacy__"
else unique_id
)
@property
def _has_period(self) -> bool:
"""Return True if sensor has any period setting."""
return (
self._start_template is not None
or self._end_template is not None
or self._duration is not None
)
@property
def should_poll(self) -> bool:
"""Return the polling state."""
return self._has_period
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self.available_sources > 0 and self._has_state(self._attr_native_value)
@property
def extra_state_attributes(self) -> Optional[Mapping[str, Any]]:
"""Return entity specific state attributes."""
state_attr = {
attr: getattr(self, attr)
for attr in ATTR_TO_PROPERTY
if getattr(self, attr) is not None
}
return state_attr
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
# pylint: disable=unused-argument
@callback
async def async_sensor_state_listener(entity, old_state, new_state):
"""Handle device state changes."""
last_state = self._attr_native_value
await self._async_update_state()
if last_state != self._attr_native_value:
self.async_schedule_update_ha_state(True)
# pylint: disable=unused-argument
@callback
async def async_sensor_startup(event):
"""Update template on startup."""
if self._has_period:
self.async_schedule_update_ha_state(True)
else:
async_track_state_change(
self.hass, self.sources, async_sensor_state_listener
)
await async_sensor_state_listener(None, None, None)
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_sensor_startup)
@staticmethod
def _has_state(state) -> bool:
"""Return True if state has any value."""
return state is not None and state not in [
STATE_UNKNOWN,
STATE_UNAVAILABLE,
"None",
"",
]
def _get_temperature(self, state: State) -> Optional[float]:
"""Get temperature value from entity."""
ha_unit = self.hass.config.units.temperature_unit
domain = split_entity_id(state.entity_id)[0]
if domain == WEATHER_DOMAIN:
temperature = state.attributes.get("temperature")
entity_unit = ha_unit
elif domain in (CLIMATE_DOMAIN, WATER_HEATER_DOMAIN):
temperature = state.attributes.get("current_temperature")
entity_unit = ha_unit
else:
temperature = state.state
entity_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if not self._has_state(temperature):
return None
try:
temperature = TemperatureConverter.convert(
float(temperature), entity_unit, ha_unit
)
except ValueError as exc:
_LOGGER.error('Could not convert value "%s" to float: %s', state, exc)
return None
return temperature
def _get_state_value(self, state: State) -> Optional[float]:
"""Return value of given entity state and count some sensor attributes."""
state = self._get_temperature(state) if self._temperature_mode else state.state
if not self._has_state(state):
return self._undef
try:
state = float(state)
except ValueError as exc:
_LOGGER.error('Could not convert value "%s" to float: %s', state, exc)
return None
self.count += 1
rstate = round(state, self._precision)
if self.min_value is None:
self.min_value = self.max_value = rstate
else:
self.min_value = min(self.min_value, rstate)
self.max_value = max(self.max_value, rstate)
return state
@Throttle(UPDATE_MIN_TIME)
async def async_update(self):
"""Update the sensor state if it needed."""
if self._has_period:
await self._async_update_state()
@staticmethod
def handle_template_exception(exc, field):
"""Log an error nicely if the template cannot be interpreted."""
if exc.args and exc.args[0].startswith(
"UndefinedError: 'None' has no attribute"
):
# Common during HA startup - so just a warning
_LOGGER.warning(exc)
else:
_LOGGER.error('Error parsing template for field "%s": %s', field, exc)
async def _async_update_period(self): # pylint: disable=too-many-branches
"""Parse the templates and calculate a datetime tuples."""
start = end = None
now = dt_util.now()
# Parse start
if self._start_template is not None:
_LOGGER.debug("Process start template: %s", self._start_template)
try:
start_rendered = self._start_template.async_render()
except (TemplateError, TypeError) as ex:
self.handle_template_exception(ex, "start")
return
if isinstance(start_rendered, str):
start = dt_util.parse_datetime(start_rendered)
if start is None:
try:
start = dt_util.as_local(
dt_util.utc_from_timestamp(math.floor(float(start_rendered)))
)
except ValueError:
_LOGGER.error(
'Parsing error: field "start" must be a datetime or a timestamp'
)
return
# Parse end
if self._end_template is not None:
_LOGGER.debug("Process end template: %s", self._end_template)
try:
end_rendered = self._end_template.async_render()
except (TemplateError, TypeError) as ex:
self.handle_template_exception(ex, "end")
return
if isinstance(end_rendered, str):
end = dt_util.parse_datetime(end_rendered)
if end is None:
try:
end = dt_util.as_local(
dt_util.utc_from_timestamp(math.floor(float(end_rendered)))
)
except ValueError:
_LOGGER.error(
'Parsing error: field "end" must be a datetime or a timestamp'
)
return
# Calculate start or end using the duration
if self._duration is not None:
_LOGGER.debug("Process duration: %s", self._duration)
if start is None:
if end is None:
end = now
start = end - self._duration
else:
end = start + self._duration
_LOGGER.debug("Calculation period: start=%s, end=%s", start, end)
if start is None or end is None:
return
if start > end:
start, end = end, start
if start > now:
# History hasn't been written yet for this period
return
end = min(end, now) # No point in making stats of the future
self._period = start, end
self.start = start.replace(microsecond=0).isoformat()
self.end = end.replace(microsecond=0).isoformat()
def _init_mode(self, state: State):
"""Initialize sensor mode."""
if self._temperature_mode is not None:
return
domain = split_entity_id(state.entity_id)[0]
self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS)
self._attr_native_unit_of_measurement = state.attributes.get(
ATTR_UNIT_OF_MEASUREMENT
)
self._temperature_mode = (
self._attr_device_class == SensorDeviceClass.TEMPERATURE
or domain in (WEATHER_DOMAIN, CLIMATE_DOMAIN, WATER_HEATER_DOMAIN)
or self._attr_native_unit_of_measurement in TEMPERATURE_UNITS
)
if self._temperature_mode:
_LOGGER.debug("%s is a temperature entity.", state.entity_id)
self._attr_device_class = SensorDeviceClass.TEMPERATURE
self._attr_native_unit_of_measurement = (
self.hass.config.units.temperature_unit
)
else:
_LOGGER.debug("%s is NOT a temperature entity.", state.entity_id)
self._attr_icon = state.attributes.get(ATTR_ICON)
async def _async_update_state(
self,
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
"""Update the sensor state."""
_LOGGER.debug('Updating sensor "%s"', self.name)
start = end = start_ts = end_ts = None
p_period = self._period
# Parse templates
await self._async_update_period()
if self._period is not None:
now = datetime.datetime.now()
start, end = self._period
if p_period is None:
p_start = p_end = now
else:
p_start, p_end = p_period
# Convert times to UTC
start = dt_util.as_utc(start)
end = dt_util.as_utc(end)
p_start = dt_util.as_utc(p_start)
p_end = dt_util.as_utc(p_end)
# Compute integer timestamps
now_ts = math.floor(dt_util.as_timestamp(now))
start_ts = math.floor(dt_util.as_timestamp(start))
end_ts = math.floor(dt_util.as_timestamp(end))
p_start_ts = math.floor(dt_util.as_timestamp(p_start))
p_end_ts = math.floor(dt_util.as_timestamp(p_end))
# If period has not changed and current time after the period end..
if start_ts == p_start_ts and end_ts == p_end_ts and end_ts <= now_ts:
# Don't compute anything as the value cannot have changed
return
self.available_sources = 0
values = []
self.count = 0
self.min_value = self.max_value = None
# pylint: disable=too-many-nested-blocks
for entity_id in self.sources:
_LOGGER.debug('Processing entity "%s"', entity_id)
state = self.hass.states.get(entity_id) # type: State
if state is None:
_LOGGER.error('Unable to find an entity "%s"', entity_id)
continue
self._init_mode(state)
value = 0
elapsed = 0
if self._period is None:
# Get current state
value = self._get_state_value(state)
_LOGGER.debug("Current state: %s", value)
else:
# Get history between start and now
history_list = await get_instance(self.hass).async_add_executor_job(
history.state_changes_during_period,
self.hass,
start,
end,
str(entity_id),
)
if (
entity_id not in history_list.keys()
or history_list[entity_id] is None
or len(history_list[entity_id]) == 0
):
value = self._get_state_value(state)
_LOGGER.warning(
'Historical data not found for entity "%s". '
"Current state used: %s",
entity_id,
value,
)
else:
# Get the first state
item = history_list[entity_id][0]
_LOGGER.debug("Initial historical state: %s", item)
last_state = None
last_time = start_ts
if item is not None and self._has_state(item.state):
last_state = self._get_state_value(item)
# Get the other states
for item in history_list.get(entity_id):
_LOGGER.debug("Historical state: %s", item)
current_state = self._get_state_value(item)
current_time = item.last_changed.timestamp()
if last_state is not None:
last_elapsed = current_time - last_time
value += last_state * last_elapsed
elapsed += last_elapsed
last_state = current_state
last_time = current_time
# Count time elapsed between last history state and now
if last_state is None:
value = None
else:
last_elapsed = end_ts - last_time
value += last_state * last_elapsed
elapsed += last_elapsed
if elapsed:
value /= elapsed
_LOGGER.debug("Historical average state: %s", value)
if isinstance(value, numbers.Number):
values.append(value)
self.available_sources += 1
if values:
self._attr_native_value = round(sum(values) / len(values), self._precision)
if self._precision < 1:
self._attr_native_value = int(self._attr_native_value)
else:
self._attr_native_value = None
_LOGGER.debug(
"Total average state: %s %s",
self._attr_native_value,
self._attr_native_unit_of_measurement,
)

View File

@@ -0,0 +1,3 @@
reload:
name: Reload
description: Reload all average sensor entities

View File

@@ -0,0 +1,294 @@
"""
HACS gives you a powerful UI to handle downloads of all your custom needs.
For more details about this integration, please refer to the documentation at
https://hacs.xyz/
"""
from __future__ import annotations
import os
from typing import Any
from aiogithubapi import AIOGitHubAPIException, GitHub, GitHubAPI
from aiogithubapi.const import ACCEPT_HEADERS
from awesomeversion import AwesomeVersion
from homeassistant.components.lovelace.system_health import system_health_info
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import Platform, __version__ as HAVERSION
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.start import async_at_start
from homeassistant.loader import async_get_integration
import voluptuous as vol
from .base import HacsBase
from .const import DOMAIN, MINIMUM_HA_VERSION, STARTUP
from .data_client import HacsDataClient
from .enums import ConfigurationType, HacsDisabledReason, HacsStage, LovelaceMode
from .frontend import async_register_frontend
from .utils.configuration_schema import hacs_config_combined
from .utils.data import HacsData
from .utils.logger import LOGGER
from .utils.queue_manager import QueueManager
from .utils.version import version_left_higher_or_equal_then_right
from .websocket import async_register_websocket_commands
CONFIG_SCHEMA = vol.Schema({DOMAIN: hacs_config_combined()}, extra=vol.ALLOW_EXTRA)
async def async_initialize_integration(
hass: HomeAssistant,
*,
config_entry: ConfigEntry | None = None,
config: dict[str, Any] | None = None,
) -> bool:
"""Initialize the integration"""
hass.data[DOMAIN] = hacs = HacsBase()
hacs.enable_hacs()
if config is not None:
if DOMAIN not in config:
return True
if hacs.configuration.config_type == ConfigurationType.CONFIG_ENTRY:
return True
hacs.configuration.update_from_dict(
{
"config_type": ConfigurationType.YAML,
**config[DOMAIN],
"config": config[DOMAIN],
}
)
if config_entry is not None:
if config_entry.source == SOURCE_IMPORT:
hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
return False
hacs.configuration.update_from_dict(
{
"config_entry": config_entry,
"config_type": ConfigurationType.CONFIG_ENTRY,
**config_entry.data,
**config_entry.options,
}
)
integration = await async_get_integration(hass, DOMAIN)
hacs.set_stage(None)
hacs.log.info(STARTUP, integration.version)
clientsession = async_get_clientsession(hass)
hacs.integration = integration
hacs.version = integration.version
hacs.configuration.dev = integration.version == "0.0.0"
hacs.hass = hass
hacs.queue = QueueManager(hass=hass)
hacs.data = HacsData(hacs=hacs)
hacs.data_client = HacsDataClient(
session=clientsession,
client_name=f"HACS/{integration.version}",
)
hacs.system.running = True
hacs.session = clientsession
hacs.core.lovelace_mode = LovelaceMode.YAML
try:
lovelace_info = await system_health_info(hacs.hass)
hacs.core.lovelace_mode = LovelaceMode(lovelace_info.get("mode", "yaml"))
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
# If this happens, the users YAML is not valid, we assume YAML mode
pass
hacs.log.debug("Configuration type: %s", hacs.configuration.config_type)
hacs.core.config_path = hacs.hass.config.path()
if hacs.core.ha_version is None:
hacs.core.ha_version = AwesomeVersion(HAVERSION)
## Legacy GitHub client
hacs.github = GitHub(
hacs.configuration.token,
clientsession,
headers={
"User-Agent": f"HACS/{hacs.version}",
"Accept": ACCEPT_HEADERS["preview"],
},
)
## New GitHub client
hacs.githubapi = GitHubAPI(
token=hacs.configuration.token,
session=clientsession,
**{"client_name": f"HACS/{hacs.version}"},
)
async def async_startup():
"""HACS startup tasks."""
hacs.enable_hacs()
for location in (
hass.config.path("custom_components/custom_updater.py"),
hass.config.path("custom_components/custom_updater/__init__.py"),
):
if os.path.exists(location):
hacs.log.critical(
"This cannot be used with custom_updater. "
"To use this you need to remove custom_updater form %s",
location,
)
hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
return False
if not version_left_higher_or_equal_then_right(
hacs.core.ha_version.string,
MINIMUM_HA_VERSION,
):
hacs.log.critical(
"You need HA version %s or newer to use this integration.",
MINIMUM_HA_VERSION,
)
hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
return False
if not await hacs.data.restore():
hacs.disable_hacs(HacsDisabledReason.RESTORE)
return False
if not hacs.configuration.experimental:
can_update = await hacs.async_can_update()
hacs.log.debug("Can update %s repositories", can_update)
hacs.set_active_categories()
async_register_websocket_commands(hass)
async_register_frontend(hass, hacs)
if hacs.configuration.config_type == ConfigurationType.YAML:
hass.async_create_task(
async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, hacs.configuration.config)
)
hacs.log.info("Update entities are only supported when using UI configuration")
else:
await hass.config_entries.async_forward_entry_setups(
config_entry,
[Platform.SENSOR, Platform.UPDATE]
if hacs.configuration.experimental
else [Platform.SENSOR],
)
hacs.set_stage(HacsStage.SETUP)
if hacs.system.disabled:
return False
# Schedule startup tasks
async_at_start(hass=hass, at_start_cb=hacs.startup_tasks)
hacs.set_stage(HacsStage.WAITING)
hacs.log.info("Setup complete, waiting for Home Assistant before startup tasks starts")
return not hacs.system.disabled
async def async_try_startup(_=None):
"""Startup wrapper for yaml config."""
try:
startup_result = await async_startup()
except AIOGitHubAPIException:
startup_result = False
if not startup_result:
if (
hacs.configuration.config_type == ConfigurationType.YAML
or hacs.system.disabled_reason != HacsDisabledReason.INVALID_TOKEN
):
hacs.log.info("Could not setup HACS, trying again in 15 min")
async_call_later(hass, 900, async_try_startup)
return
hacs.enable_hacs()
await async_try_startup()
# Mischief managed!
return True
async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
"""Set up this integration using yaml."""
if DOMAIN in config:
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml_configuration",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml_configuration",
learn_more_url="https://hacs.xyz/docs/configuration/options",
)
LOGGER.warning(
"YAML configuration of HACS is deprecated and will be "
"removed in version 2.0.0, there will be no automatic "
"import of this. "
"Please remove it from your configuration, "
"restart Home Assistant and use the UI to configure it instead."
)
return await async_initialize_integration(hass=hass, config=config)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up this integration using UI."""
config_entry.async_on_unload(config_entry.add_update_listener(async_reload_entry))
setup_result = await async_initialize_integration(hass=hass, config_entry=config_entry)
hacs: HacsBase = hass.data[DOMAIN]
return setup_result and not hacs.system.disabled
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Handle removal of an entry."""
hacs: HacsBase = hass.data[DOMAIN]
if hacs.queue.has_pending_tasks:
hacs.log.warning("Pending tasks, can not unload, try again later.")
return False
# Clear out pending queue
hacs.queue.clear()
for task in hacs.recuring_tasks:
# Cancel all pending tasks
task()
# Store data
await hacs.data.async_write(force=True)
try:
if hass.data.get("frontend_panels", {}).get("hacs"):
hacs.log.info("Removing sidepanel")
hass.components.frontend.async_remove_panel("hacs")
except AttributeError:
pass
platforms = ["sensor"]
if hacs.configuration.experimental:
platforms.append("update")
unload_ok = await hass.config_entries.async_unload_platforms(config_entry, platforms)
hacs.set_stage(None)
hacs.disable_hacs(HacsDisabledReason.REMOVED)
hass.data.pop(DOMAIN, None)
return unload_ok
async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Reload the HACS config entry."""
if not await async_unload_entry(hass, config_entry):
return
await async_setup_entry(hass, config_entry)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,247 @@
"""Adds config flow for HACS."""
from __future__ import annotations
import asyncio
from contextlib import suppress
from typing import TYPE_CHECKING
from aiogithubapi import (
GitHubDeviceAPI,
GitHubException,
GitHubLoginDeviceModel,
GitHubLoginOauthModel,
)
from aiogithubapi.common.const import OAUTH_USER_LOGIN
from awesomeversion import AwesomeVersion
from homeassistant.config_entries import ConfigFlow, OptionsFlow
from homeassistant.const import __version__ as HAVERSION
from homeassistant.core import callback
from homeassistant.data_entry_flow import UnknownFlow
from homeassistant.helpers import aiohttp_client
from homeassistant.loader import async_get_integration
import voluptuous as vol
from .base import HacsBase
from .const import CLIENT_ID, DOMAIN, LOCALE, MINIMUM_HA_VERSION
from .enums import ConfigurationType
from .utils.configuration_schema import (
APPDAEMON,
COUNTRY,
DEBUG,
EXPERIMENTAL,
NETDAEMON,
RELEASE_LIMIT,
SIDEPANEL_ICON,
SIDEPANEL_TITLE,
)
from .utils.logger import LOGGER
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
class HacsFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow for HACS."""
VERSION = 1
hass: HomeAssistant
activation_task: asyncio.Task | None = None
device: GitHubDeviceAPI | None = None
_registration: GitHubLoginDeviceModel | None = None
_activation: GitHubLoginOauthModel | None = None
_reauth: bool = False
def __init__(self) -> None:
"""Initialize."""
self._errors = {}
self._user_input = {}
async def async_step_user(self, user_input):
"""Handle a flow initialized by the user."""
self._errors = {}
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
if self.hass.data.get(DOMAIN):
return self.async_abort(reason="single_instance_allowed")
if user_input:
if [x for x in user_input if x.startswith("acc_") and not user_input[x]]:
self._errors["base"] = "acc"
return await self._show_config_form(user_input)
self._user_input = user_input
return await self.async_step_device(user_input)
## Initial form
return await self._show_config_form(user_input)
@callback
def async_remove(self):
"""Cleanup."""
if self.activation_task and not self.activation_task.done():
self.activation_task.cancel()
async def async_step_device(self, _user_input):
"""Handle device steps."""
async def _wait_for_activation() -> None:
try:
response = await self.device.activation(device_code=self._registration.device_code)
self._activation = response.data
finally:
async def _progress():
with suppress(UnknownFlow):
await self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
self.hass.async_create_task(_progress())
if not self.device:
integration = await async_get_integration(self.hass, DOMAIN)
self.device = GitHubDeviceAPI(
client_id=CLIENT_ID,
session=aiohttp_client.async_get_clientsession(self.hass),
**{"client_name": f"HACS/{integration.version}"},
)
try:
response = await self.device.register()
self._registration = response.data
except GitHubException as exception:
LOGGER.exception(exception)
return self.async_abort(reason="could_not_register")
if self.activation_task is None:
self.activation_task = self.hass.async_create_task(_wait_for_activation())
if self.activation_task.done():
if (exception := self.activation_task.exception()) is not None:
LOGGER.exception(exception)
return self.async_show_progress_done(next_step_id="could_not_register")
return self.async_show_progress_done(next_step_id="device_done")
return self.async_show_progress(
step_id="device",
progress_action="wait_for_device",
description_placeholders={
"url": OAUTH_USER_LOGIN,
"code": self._registration.user_code,
},
)
async def _show_config_form(self, user_input):
"""Show the configuration form to edit location data."""
if not user_input:
user_input = {}
if AwesomeVersion(HAVERSION) < MINIMUM_HA_VERSION:
return self.async_abort(
reason="min_ha_version",
description_placeholders={"version": MINIMUM_HA_VERSION},
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required("acc_logs", default=user_input.get("acc_logs", False)): bool,
vol.Required("acc_addons", default=user_input.get("acc_addons", False)): bool,
vol.Required(
"acc_untested", default=user_input.get("acc_untested", False)
): bool,
vol.Required("acc_disable", default=user_input.get("acc_disable", False)): bool,
vol.Optional(
"experimental", default=user_input.get("experimental", False)
): bool,
}
),
errors=self._errors,
)
async def async_step_device_done(self, user_input: dict[str, bool] | None = None):
"""Handle device steps"""
if self._reauth:
existing_entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
self.hass.config_entries.async_update_entry(
existing_entry, data={**existing_entry.data, "token": self._activation.access_token}
)
await self.hass.config_entries.async_reload(existing_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(
title="",
data={
"token": self._activation.access_token,
},
options={
"experimental": self._user_input.get("experimental", False),
},
)
async def async_step_could_not_register(self, _user_input=None):
"""Handle issues that need transition await from progress step."""
return self.async_abort(reason="could_not_register")
async def async_step_reauth(self, _user_input=None):
"""Perform reauth upon an API authentication error."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(self, user_input=None):
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
self._reauth = True
return await self.async_step_device(None)
@staticmethod
@callback
def async_get_options_flow(config_entry):
return HacsOptionsFlowHandler(config_entry)
class HacsOptionsFlowHandler(OptionsFlow):
"""HACS config flow options handler."""
def __init__(self, config_entry):
"""Initialize HACS options flow."""
self.config_entry = config_entry
async def async_step_init(self, _user_input=None):
"""Manage the options."""
return await self.async_step_user()
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
hacs: HacsBase = self.hass.data.get(DOMAIN)
if user_input is not None:
limit = int(user_input.get(RELEASE_LIMIT, 5))
if limit <= 0 or limit > 100:
return self.async_abort(reason="release_limit_value")
return self.async_create_entry(title="", data=user_input)
if hacs is None or hacs.configuration is None:
return self.async_abort(reason="not_setup")
if hacs.queue.has_pending_tasks:
return self.async_abort(reason="pending_tasks")
if hacs.configuration.config_type == ConfigurationType.YAML:
schema = {vol.Optional("not_in_use", default=""): str}
else:
schema = {
vol.Optional(SIDEPANEL_TITLE, default=hacs.configuration.sidepanel_title): str,
vol.Optional(SIDEPANEL_ICON, default=hacs.configuration.sidepanel_icon): str,
vol.Optional(RELEASE_LIMIT, default=hacs.configuration.release_limit): int,
vol.Optional(COUNTRY, default=hacs.configuration.country): vol.In(LOCALE),
vol.Optional(APPDAEMON, default=hacs.configuration.appdaemon): bool,
vol.Optional(NETDAEMON, default=hacs.configuration.netdaemon): bool,
vol.Optional(DEBUG, default=hacs.configuration.debug): bool,
vol.Optional(EXPERIMENTAL, default=hacs.configuration.experimental): bool,
}
return self.async_show_form(step_id="user", data_schema=vol.Schema(schema))

View File

@@ -0,0 +1,293 @@
"""Constants for HACS"""
from typing import TypeVar
from aiogithubapi.common.const import ACCEPT_HEADERS
NAME_SHORT = "HACS"
DOMAIN = "hacs"
CLIENT_ID = "395a8e669c5de9f7c6e8"
MINIMUM_HA_VERSION = "2023.6.0"
URL_BASE = "/hacsfiles"
TV = TypeVar("TV")
PACKAGE_NAME = "custom_components.hacs"
DEFAULT_CONCURRENT_TASKS = 15
DEFAULT_CONCURRENT_BACKOFF_TIME = 1
HACS_REPOSITORY_ID = "172733314"
HACS_ACTION_GITHUB_API_HEADERS = {
"User-Agent": "HACS/action",
"Accept": ACCEPT_HEADERS["preview"],
}
VERSION_STORAGE = "6"
STORENAME = "hacs"
HACS_SYSTEM_ID = "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd"
STARTUP = """
-------------------------------------------------------------------
HACS (Home Assistant Community Store)
Version: %s
This is a custom integration
If you have any issues with this you need to open an issue here:
https://github.com/hacs/integration/issues
-------------------------------------------------------------------
"""
LOCALE = [
"ALL",
"AF",
"AL",
"DZ",
"AS",
"AD",
"AO",
"AI",
"AQ",
"AG",
"AR",
"AM",
"AW",
"AU",
"AT",
"AZ",
"BS",
"BH",
"BD",
"BB",
"BY",
"BE",
"BZ",
"BJ",
"BM",
"BT",
"BO",
"BQ",
"BA",
"BW",
"BV",
"BR",
"IO",
"BN",
"BG",
"BF",
"BI",
"KH",
"CM",
"CA",
"CV",
"KY",
"CF",
"TD",
"CL",
"CN",
"CX",
"CC",
"CO",
"KM",
"CG",
"CD",
"CK",
"CR",
"HR",
"CU",
"CW",
"CY",
"CZ",
"CI",
"DK",
"DJ",
"DM",
"DO",
"EC",
"EG",
"SV",
"GQ",
"ER",
"EE",
"ET",
"FK",
"FO",
"FJ",
"FI",
"FR",
"GF",
"PF",
"TF",
"GA",
"GM",
"GE",
"DE",
"GH",
"GI",
"GR",
"GL",
"GD",
"GP",
"GU",
"GT",
"GG",
"GN",
"GW",
"GY",
"HT",
"HM",
"VA",
"HN",
"HK",
"HU",
"IS",
"IN",
"ID",
"IR",
"IQ",
"IE",
"IM",
"IL",
"IT",
"JM",
"JP",
"JE",
"JO",
"KZ",
"KE",
"KI",
"KP",
"KR",
"KW",
"KG",
"LA",
"LV",
"LB",
"LS",
"LR",
"LY",
"LI",
"LT",
"LU",
"MO",
"MK",
"MG",
"MW",
"MY",
"MV",
"ML",
"MT",
"MH",
"MQ",
"MR",
"MU",
"YT",
"MX",
"FM",
"MD",
"MC",
"MN",
"ME",
"MS",
"MA",
"MZ",
"MM",
"NA",
"NR",
"NP",
"NL",
"NC",
"NZ",
"NI",
"NE",
"NG",
"NU",
"NF",
"MP",
"NO",
"OM",
"PK",
"PW",
"PS",
"PA",
"PG",
"PY",
"PE",
"PH",
"PN",
"PL",
"PT",
"PR",
"QA",
"RO",
"RU",
"RW",
"RE",
"BL",
"SH",
"KN",
"LC",
"MF",
"PM",
"VC",
"WS",
"SM",
"ST",
"SA",
"SN",
"RS",
"SC",
"SL",
"SG",
"SX",
"SK",
"SI",
"SB",
"SO",
"ZA",
"GS",
"SS",
"ES",
"LK",
"SD",
"SR",
"SJ",
"SZ",
"SE",
"CH",
"SY",
"TW",
"TJ",
"TZ",
"TH",
"TL",
"TG",
"TK",
"TO",
"TT",
"TN",
"TR",
"TM",
"TC",
"TV",
"UG",
"UA",
"AE",
"GB",
"US",
"UM",
"UY",
"UZ",
"VU",
"VE",
"VN",
"VG",
"VI",
"WF",
"EH",
"YE",
"ZM",
"ZW",
]

View File

@@ -0,0 +1,57 @@
"""HACS Data client."""
from __future__ import annotations
import asyncio
from typing import Any
from aiohttp import ClientSession, ClientTimeout
from .exceptions import HacsException, HacsNotModifiedException
class HacsDataClient:
"""HACS Data client."""
def __init__(self, session: ClientSession, client_name: str) -> None:
"""Initialize."""
self._client_name = client_name
self._etags = {}
self._session = session
async def _do_request(
self,
filename: str,
section: str | None = None,
) -> dict[str, dict[str, Any]] | list[str]:
"""Do request."""
endpoint = "/".join([v for v in [section, filename] if v is not None])
try:
response = await self._session.get(
f"https://data-v2.hacs.xyz/{endpoint}",
timeout=ClientTimeout(total=60),
headers={
"User-Agent": self._client_name,
"If-None-Match": self._etags.get(endpoint, ""),
},
)
if response.status == 304:
raise HacsNotModifiedException() from None
response.raise_for_status()
except HacsNotModifiedException:
raise
except asyncio.TimeoutError:
raise HacsException("Timeout of 60s reached") from None
except Exception as exception:
raise HacsException(f"Error fetching data from HACS: {exception}") from exception
self._etags[endpoint] = response.headers.get("etag")
return await response.json()
async def get_data(self, section: str | None) -> dict[str, dict[str, Any]]:
"""Get data."""
return await self._do_request(filename="data.json", section=section)
async def get_repositories(self, section: str) -> list[str]:
"""Get repositories."""
return await self._do_request(filename="repositories.json", section=section)

View File

@@ -0,0 +1,82 @@
"""Diagnostics support for HACS."""
from __future__ import annotations
from typing import Any
from aiogithubapi import GitHubException
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .base import HacsBase
from .const import DOMAIN
from .utils.configuration_schema import TOKEN
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
entry: ConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
hacs: HacsBase = hass.data[DOMAIN]
data = {
"entry": entry.as_dict(),
"hacs": {
"stage": hacs.stage,
"version": hacs.version,
"disabled_reason": hacs.system.disabled_reason,
"new": hacs.status.new,
"startup": hacs.status.startup,
"categories": hacs.common.categories,
"renamed_repositories": hacs.common.renamed_repositories,
"archived_repositories": hacs.common.archived_repositories,
"ignored_repositories": hacs.common.ignored_repositories,
"lovelace_mode": hacs.core.lovelace_mode,
"configuration": {},
},
"custom_repositories": [
repo.data.full_name
for repo in hacs.repositories.list_all
if not hacs.repositories.is_default(str(repo.data.id))
],
"repositories": [],
}
for key in (
"appdaemon",
"country",
"debug",
"dev",
"experimental",
"netdaemon",
"python_script",
"release_limit",
"theme",
):
data["hacs"]["configuration"][key] = getattr(hacs.configuration, key, None)
for repository in hacs.repositories.list_downloaded:
data["repositories"].append(
{
"data": repository.data.to_json(),
"integration_manifest": repository.integration_manifest,
"repository_manifest": repository.repository_manifest.to_dict(),
"ref": repository.ref,
"paths": {
"localpath": repository.localpath.replace(hacs.core.config_path, "/config"),
"local": repository.content.path.local.replace(
hacs.core.config_path, "/config"
),
"remote": repository.content.path.remote,
},
}
)
try:
rate_limit_response = await hacs.githubapi.rate_limit()
data["rate_limit"] = rate_limit_response.data.as_dict
except GitHubException as exception:
data["rate_limit"] = str(exception)
return async_redact_data(data, (TOKEN,))

View File

@@ -0,0 +1,119 @@
"""HACS Base entities."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from .const import DOMAIN, HACS_SYSTEM_ID, NAME_SHORT
from .enums import HacsDispatchEvent, HacsGitHubRepo
if TYPE_CHECKING:
from .base import HacsBase
from .repositories.base import HacsRepository
def system_info(hacs: HacsBase) -> dict:
"""Return system info."""
return {
"identifiers": {(DOMAIN, HACS_SYSTEM_ID)},
"name": NAME_SHORT,
"manufacturer": "hacs.xyz",
"model": "",
"sw_version": str(hacs.version),
"configuration_url": "homeassistant://hacs",
"entry_type": DeviceEntryType.SERVICE,
}
class HacsBaseEntity(Entity):
"""Base HACS entity."""
repository: HacsRepository | None = None
_attr_should_poll = False
def __init__(self, hacs: HacsBase) -> None:
"""Initialize."""
self.hacs = hacs
async def async_added_to_hass(self) -> None:
"""Register for status events."""
self.async_on_remove(
async_dispatcher_connect(
self.hass,
HacsDispatchEvent.REPOSITORY,
self._update_and_write_state,
)
)
@callback
def _update(self) -> None:
"""Update the sensor."""
async def async_update(self) -> None:
"""Manual updates of the sensor."""
self._update()
@callback
def _update_and_write_state(self, _: Any) -> None:
"""Update the entity and write state."""
self._update()
self.async_write_ha_state()
class HacsSystemEntity(HacsBaseEntity):
"""Base system entity."""
_attr_icon = "hacs:hacs"
_attr_unique_id = HACS_SYSTEM_ID
@property
def device_info(self) -> dict[str, any]:
"""Return device information about HACS."""
return system_info(self.hacs)
class HacsRepositoryEntity(HacsBaseEntity):
"""Base repository entity."""
def __init__(
self,
hacs: HacsBase,
repository: HacsRepository,
) -> None:
"""Initialize."""
super().__init__(hacs=hacs)
self.repository = repository
self._attr_unique_id = str(repository.data.id)
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self.hacs.repositories.is_downloaded(repository_id=str(self.repository.data.id))
@property
def device_info(self) -> dict[str, any]:
"""Return device information about HACS."""
if self.repository.data.full_name == HacsGitHubRepo.INTEGRATION:
return system_info(self.hacs)
return {
"identifiers": {(DOMAIN, str(self.repository.data.id))},
"name": self.repository.display_name,
"model": self.repository.data.category,
"manufacturer": ", ".join(
author.replace("@", "") for author in self.repository.data.authors
),
"configuration_url": "homeassistant://hacs",
"entry_type": DeviceEntryType.SERVICE,
}
@callback
def _update_and_write_state(self, data: dict) -> None:
"""Update the entity and write state."""
if data.get("repository_id") == self.repository.data.id:
self._update()
self.async_write_ha_state()

View File

@@ -0,0 +1,90 @@
"""Helper constants."""
# pylint: disable=missing-class-docstring
import sys
if sys.version_info.minor >= 11:
# Needs Python 3.11
from enum import StrEnum # # pylint: disable=no-name-in-module
else:
try:
# https://github.com/home-assistant/core/blob/dev/homeassistant/backports/enum.py
# Considered internal to Home Assistant, can be removed whenever.
from homeassistant.backports.enum import StrEnum
except ImportError:
from enum import Enum
class StrEnum(str, Enum):
pass
class HacsGitHubRepo(StrEnum):
"""HacsGitHubRepo."""
DEFAULT = "hacs/default"
INTEGRATION = "hacs/integration"
class HacsCategory(StrEnum):
APPDAEMON = "appdaemon"
INTEGRATION = "integration"
LOVELACE = "lovelace"
PLUGIN = "plugin" # Kept for legacy purposes
NETDAEMON = "netdaemon"
PYTHON_SCRIPT = "python_script"
TEMPLATE = "template"
THEME = "theme"
REMOVED = "removed"
def __str__(self):
return str(self.value)
class HacsDispatchEvent(StrEnum):
"""HacsDispatchEvent."""
CONFIG = "hacs_dispatch_config"
ERROR = "hacs_dispatch_error"
RELOAD = "hacs_dispatch_reload"
REPOSITORY = "hacs_dispatch_repository"
REPOSITORY_DOWNLOAD_PROGRESS = "hacs_dispatch_repository_download_progress"
STAGE = "hacs_dispatch_stage"
STARTUP = "hacs_dispatch_startup"
STATUS = "hacs_dispatch_status"
class RepositoryFile(StrEnum):
"""Repository file names."""
HACS_JSON = "hacs.json"
MAINIFEST_JSON = "manifest.json"
class ConfigurationType(StrEnum):
YAML = "yaml"
CONFIG_ENTRY = "config_entry"
class LovelaceMode(StrEnum):
"""Lovelace Modes."""
STORAGE = "storage"
AUTO = "auto"
AUTO_GEN = "auto-gen"
YAML = "yaml"
class HacsStage(StrEnum):
SETUP = "setup"
STARTUP = "startup"
WAITING = "waiting"
RUNNING = "running"
BACKGROUND = "background"
class HacsDisabledReason(StrEnum):
RATE_LIMIT = "rate_limit"
REMOVED = "removed"
INVALID_TOKEN = "invalid_token"
CONSTRAINS = "constrains"
LOAD_HACS = "load_hacs"
RESTORE = "restore"

View File

@@ -0,0 +1,49 @@
"""Custom Exceptions for HACS."""
class HacsException(Exception):
"""Super basic."""
class HacsRepositoryArchivedException(HacsException):
"""For repositories that are archived."""
class HacsNotModifiedException(HacsException):
"""For responses that are not modified."""
class HacsExpectedException(HacsException):
"""For stuff that are expected."""
class HacsRepositoryExistException(HacsException):
"""For repositories that are already exist."""
class HacsExecutionStillInProgress(HacsException):
"""Exception to raise if execution is still in progress."""
class AddonRepositoryException(HacsException):
"""Exception to raise when user tries to add add-on repository."""
exception_message = (
"The repository does not seem to be a integration, "
"but an add-on repository. HACS does not manage add-ons."
)
def __init__(self) -> None:
super().__init__(self.exception_message)
class HomeAssistantCoreRepositoryException(HacsException):
"""Exception to raise when user tries to add the home-assistant/core repository."""
exception_message = (
"You can not add homeassistant/core, to use core integrations "
"check the Home Assistant documentation for how to add them."
)
def __init__(self) -> None:
super().__init__(self.exception_message)

View File

@@ -0,0 +1,85 @@
""""Starting setup task: Frontend"."""
from __future__ import annotations
import os
from typing import TYPE_CHECKING
from homeassistant.core import HomeAssistant, callback
from .const import DOMAIN, URL_BASE
from .hacs_frontend import VERSION as FE_VERSION, locate_dir
from .hacs_frontend_experimental import (
VERSION as EXPERIMENTAL_FE_VERSION,
locate_dir as experimental_locate_dir,
)
try:
from homeassistant.components.frontend import add_extra_js_url
except ImportError:
def add_extra_js_url(hass: HomeAssistant, url: str, es5: bool = False) -> None:
hacs: HacsBase = hass.data.get(DOMAIN)
hacs.log.error("Could not import add_extra_js_url from frontend.")
if "frontend_extra_module_url" not in hass.data:
hass.data["frontend_extra_module_url"] = set()
hass.data["frontend_extra_module_url"].add(url)
if TYPE_CHECKING:
from .base import HacsBase
@callback
def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
"""Register the frontend."""
# Setup themes endpoint if needed
hacs.async_setup_frontend_endpoint_themes()
# Register frontend
if hacs.configuration.dev and (frontend_path := os.getenv("HACS_FRONTEND_DIR")):
hacs.log.warning(
"<HacsFrontend> Frontend development mode enabled. Do not run in production!"
)
hass.http.register_static_path(
f"{URL_BASE}/frontend", f"{frontend_path}/hacs_frontend", cache_headers=False
)
elif hacs.configuration.experimental:
hacs.log.info("<HacsFrontend> Using experimental frontend")
hass.http.register_static_path(
f"{URL_BASE}/frontend", experimental_locate_dir(), cache_headers=False
)
else:
#
hass.http.register_static_path(f"{URL_BASE}/frontend", locate_dir(), cache_headers=False)
# Custom iconset
hass.http.register_static_path(
f"{URL_BASE}/iconset.js", str(hacs.integration_dir / "iconset.js")
)
add_extra_js_url(hass, f"{URL_BASE}/iconset.js")
hacs.frontend_version = (
FE_VERSION if not hacs.configuration.experimental else EXPERIMENTAL_FE_VERSION
)
# Add to sidepanel if needed
if DOMAIN not in hass.data.get("frontend_panels", {}):
hass.components.frontend.async_register_built_in_panel(
component_name="custom",
sidebar_title=hacs.configuration.sidepanel_title,
sidebar_icon=hacs.configuration.sidepanel_icon,
frontend_url_path=DOMAIN,
config={
"_panel_custom": {
"name": "hacs-frontend",
"embed_iframe": True,
"trust_external": False,
"js_url": f"/hacsfiles/frontend/entrypoint.js?hacstag={hacs.frontend_version}",
}
},
require_admin=True,
)
# Setup plugin endpoint if needed
hacs.async_setup_frontend_endpoint_plugin()

View File

@@ -0,0 +1,5 @@
"""HACS Frontend"""
from .version import VERSION
def locate_dir():
return __path__[0]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
import{a as t,r as i,n as a}from"./main-ad130be7.js";import{L as n,s}from"./c.82eccc94.js";let r=t([a("ha-list-item")],(function(t,a){return{F:class extends a{constructor(...i){super(...i),t(this)}},d:[{kind:"get",static:!0,key:"styles",value:function(){return[s,i`
:host {
padding-left: var(--mdc-list-side-padding, 20px);
padding-right: var(--mdc-list-side-padding, 20px);
}
:host([graphic="avatar"]:not([twoLine])),
:host([graphic="icon"]:not([twoLine])) {
height: 48px;
}
span.material-icons:first-of-type {
margin-inline-start: 0px !important;
margin-inline-end: var(
--mdc-list-item-graphic-margin,
16px
) !important;
direction: var(--direction);
}
span.material-icons:last-of-type {
margin-inline-start: auto !important;
margin-inline-end: 0px !important;
direction: var(--direction);
}
`]}}]}}),n);const e=t=>`https://brands.home-assistant.io/${t.useFallback?"_/":""}${t.domain}/${t.darkOptimized?"dark_":""}${t.type}.png`,o=t=>t.split("/")[4],p=t=>t.startsWith("https://brands.home-assistant.io/");export{r as H,e as b,o as e,p as i};

View File

@@ -0,0 +1,24 @@
import{a as e,h as t,Y as i,e as n,i as o,$ as r,L as l,N as a,r as d,n as s}from"./main-ad130be7.js";import"./c.9b92f489.js";e([s("ha-button-menu")],(function(e,t){class s extends t{constructor(...t){super(...t),e(this)}}return{F:s,d:[{kind:"field",key:i,value:void 0},{kind:"field",decorators:[n()],key:"corner",value:()=>"TOP_START"},{kind:"field",decorators:[n()],key:"menuCorner",value:()=>"START"},{kind:"field",decorators:[n({type:Number})],key:"x",value:()=>null},{kind:"field",decorators:[n({type:Number})],key:"y",value:()=>null},{kind:"field",decorators:[n({type:Boolean})],key:"multi",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"activatable",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"disabled",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"fixed",value:()=>!1},{kind:"field",decorators:[o("mwc-menu",!0)],key:"_menu",value:void 0},{kind:"get",key:"items",value:function(){var e;return null===(e=this._menu)||void 0===e?void 0:e.items}},{kind:"get",key:"selected",value:function(){var e;return null===(e=this._menu)||void 0===e?void 0:e.selected}},{kind:"method",key:"focus",value:function(){var e,t;null!==(e=this._menu)&&void 0!==e&&e.open?this._menu.focusItemAtIndex(0):null===(t=this._triggerButton)||void 0===t||t.focus()}},{kind:"method",key:"render",value:function(){return r`
<div @click=${this._handleClick}>
<slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
</div>
<mwc-menu
.corner=${this.corner}
.menuCorner=${this.menuCorner}
.fixed=${this.fixed}
.multi=${this.multi}
.activatable=${this.activatable}
.y=${this.y}
.x=${this.x}
>
<slot></slot>
</mwc-menu>
`}},{kind:"method",key:"firstUpdated",value:function(e){l(a(s.prototype),"firstUpdated",this).call(this,e),"rtl"===document.dir&&this.updateComplete.then((()=>{this.querySelectorAll("mwc-list-item").forEach((e=>{const t=document.createElement("style");t.innerHTML="span.material-icons:first-of-type { margin-left: var(--mdc-list-item-graphic-margin, 32px) !important; margin-right: 0px !important;}",e.shadowRoot.appendChild(t)}))}))}},{kind:"method",key:"_handleClick",value:function(){this.disabled||(this._menu.anchor=this,this._menu.show())}},{kind:"get",key:"_triggerButton",value:function(){return this.querySelector('ha-icon-button[slot="trigger"], mwc-button[slot="trigger"]')}},{kind:"method",key:"_setTriggerAria",value:function(){this._triggerButton&&(this._triggerButton.ariaHasPopup="menu")}},{kind:"get",static:!0,key:"styles",value:function(){return d`
:host {
display: inline-block;
position: relative;
}
::slotted([disabled]) {
color: var(--disabled-text-color);
}
`}}]}}),t);

View File

@@ -0,0 +1,390 @@
import{a as e,h as t,e as i,g as a,t as s,$ as o,j as r,R as n,w as l,r as h,n as c,m as d,L as p,N as u,o as v,b as f,aI as b,ai as m,c as k,E as g,aJ as y,aC as w,aK as x,aL as $,d as _,s as R}from"./main-ad130be7.js";import{f as z}from"./c.3243a8b0.js";import{c as j}from"./c.4a97632a.js";import"./c.f1291e50.js";import"./c.2d5ed670.js";import"./c.97b7c4b0.js";import{r as F}from"./c.4204ca09.js";import{i as P}from"./c.21c042d4.js";import{s as I}from"./c.2645c235.js";import"./c.a5f69ed4.js";import"./c.3f859915.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.8e28b461.js";import"./c.4feb0cb8.js";import"./c.0ca5587f.js";import"./c.5d3ce9d6.js";import"./c.f6611997.js";import"./c.743a15a1.js";import"./c.4266acdb.js";e([c("ha-tab")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"active",value:()=>!1},{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"narrow",value:()=>!1},{kind:"field",decorators:[i()],key:"name",value:void 0},{kind:"field",decorators:[a("mwc-ripple")],key:"_ripple",value:void 0},{kind:"field",decorators:[s()],key:"_shouldRenderRipple",value:()=>!1},{kind:"method",key:"render",value:function(){return o`
<div
tabindex="0"
role="tab"
aria-selected=${this.active}
aria-label=${r(this.name)}
@focus=${this.handleRippleFocus}
@blur=${this.handleRippleBlur}
@mousedown=${this.handleRippleActivate}
@mouseup=${this.handleRippleDeactivate}
@mouseenter=${this.handleRippleMouseEnter}
@mouseleave=${this.handleRippleMouseLeave}
@touchstart=${this.handleRippleActivate}
@touchend=${this.handleRippleDeactivate}
@touchcancel=${this.handleRippleDeactivate}
@keydown=${this._handleKeyDown}
>
${this.narrow?o`<slot name="icon"></slot>`:""}
<span class="name">${this.name}</span>
${this._shouldRenderRipple?o`<mwc-ripple></mwc-ripple>`:""}
</div>
`}},{kind:"field",key:"_rippleHandlers",value(){return new n((()=>(this._shouldRenderRipple=!0,this._ripple)))}},{kind:"method",key:"_handleKeyDown",value:function(e){13===e.keyCode&&e.target.click()}},{kind:"method",decorators:[l({passive:!0})],key:"handleRippleActivate",value:function(e){this._rippleHandlers.startPress(e)}},{kind:"method",key:"handleRippleDeactivate",value:function(){this._rippleHandlers.endPress()}},{kind:"method",key:"handleRippleMouseEnter",value:function(){this._rippleHandlers.startHover()}},{kind:"method",key:"handleRippleMouseLeave",value:function(){this._rippleHandlers.endHover()}},{kind:"method",key:"handleRippleFocus",value:function(){this._rippleHandlers.startFocus()}},{kind:"method",key:"handleRippleBlur",value:function(){this._rippleHandlers.endFocus()}},{kind:"get",static:!0,key:"styles",value:function(){return h`
div {
padding: 0 32px;
display: flex;
flex-direction: column;
text-align: center;
box-sizing: border-box;
align-items: center;
justify-content: center;
width: 100%;
height: var(--header-height);
cursor: pointer;
position: relative;
outline: none;
}
.name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
:host([active]) {
color: var(--primary-color);
}
:host(:not([narrow])[active]) div {
border-bottom: 2px solid var(--primary-color);
}
:host([narrow]) {
min-width: 0;
display: flex;
justify-content: center;
overflow: hidden;
}
:host([narrow]) div {
padding: 0 4px;
}
`}}]}}),t),e([c("hass-tabs-subpage")],(function(e,t){class a extends t{constructor(...t){super(...t),e(this)}}return{F:a,d:[{kind:"field",decorators:[i({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[i({type:Boolean})],key:"supervisor",value:()=>!1},{kind:"field",decorators:[i({attribute:!1})],key:"localizeFunc",value:void 0},{kind:"field",decorators:[i({type:String,attribute:"back-path"})],key:"backPath",value:void 0},{kind:"field",decorators:[i()],key:"backCallback",value:void 0},{kind:"field",decorators:[i({type:Boolean,attribute:"main-page"})],key:"mainPage",value:()=>!1},{kind:"field",decorators:[i({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"tabs",value:void 0},{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"narrow",value:()=>!1},{kind:"field",decorators:[i({type:Boolean,reflect:!0,attribute:"is-wide"})],key:"isWide",value:()=>!1},{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"rtl",value:()=>!1},{kind:"field",decorators:[s()],key:"_activeTab",value:void 0},{kind:"field",decorators:[F(".content")],key:"_savedScrollPos",value:void 0},{kind:"field",key:"_getTabs",value(){return d(((e,t,i,a,s,r,n)=>{const l=e.filter((e=>(!e.component||e.core||P(this.hass,e.component))&&(!e.advancedOnly||i)));if(l.length<2){if(1===l.length){const e=l[0];return[e.translationKey?n(e.translationKey):e.name]}return[""]}return l.map((e=>o`
<a href=${e.path}>
<ha-tab
.hass=${this.hass}
.active=${e.path===(null==t?void 0:t.path)}
.narrow=${this.narrow}
.name=${e.translationKey?n(e.translationKey):e.name}
>
${e.iconPath?o`<ha-svg-icon
slot="icon"
.path=${e.iconPath}
></ha-svg-icon>`:""}
</ha-tab>
</a>
`))}))}},{kind:"method",key:"willUpdate",value:function(e){if(e.has("route")&&(this._activeTab=this.tabs.find((e=>`${this.route.prefix}${this.route.path}`.includes(e.path)))),e.has("hass")){const t=e.get("hass");t&&t.language===this.hass.language||(this.rtl=j(this.hass))}p(u(a.prototype),"willUpdate",this).call(this,e)}},{kind:"method",key:"render",value:function(){var e,t;const i=this._getTabs(this.tabs,this._activeTab,null===(e=this.hass.userData)||void 0===e?void 0:e.showAdvanced,this.hass.config.components,this.hass.language,this.narrow,this.localizeFunc||this.hass.localize),a=i.length>1;return o`
<div class="toolbar">
${this.mainPage||!this.backPath&&null!==(t=history.state)&&void 0!==t&&t.root?o`
<ha-menu-button
.hassio=${this.supervisor}
.hass=${this.hass}
.narrow=${this.narrow}
></ha-menu-button>
`:this.backPath?o`
<a href=${this.backPath}>
<ha-icon-button-arrow-prev
.hass=${this.hass}
></ha-icon-button-arrow-prev>
</a>
`:o`
<ha-icon-button-arrow-prev
.hass=${this.hass}
@click=${this._backTapped}
></ha-icon-button-arrow-prev>
`}
${this.narrow||!a?o`<div class="main-title">
<slot name="header">${a?"":i[0]}</slot>
</div>`:""}
${a?o`
<div id="tabbar" class=${v({"bottom-bar":this.narrow})}>
${i}
</div>
`:""}
<div id="toolbar-icon">
<slot name="toolbar-icon"></slot>
</div>
</div>
<div
class="content ${v({tabs:a})}"
@scroll=${this._saveScrollPos}
>
<slot></slot>
</div>
<div id="fab" class=${v({tabs:a})}>
<slot name="fab"></slot>
</div>
`}},{kind:"method",decorators:[l({passive:!0})],key:"_saveScrollPos",value:function(e){this._savedScrollPos=e.target.scrollTop}},{kind:"method",key:"_backTapped",value:function(){this.backCallback?this.backCallback():history.back()}},{kind:"get",static:!0,key:"styles",value:function(){return h`
:host {
display: block;
height: 100%;
background-color: var(--primary-background-color);
}
:host([narrow]) {
width: 100%;
position: fixed;
}
ha-menu-button {
margin-right: 24px;
}
.toolbar {
display: flex;
align-items: center;
font-size: 20px;
height: var(--header-height);
background-color: var(--sidebar-background-color);
font-weight: 400;
border-bottom: 1px solid var(--divider-color);
padding: 0 16px;
box-sizing: border-box;
}
.toolbar a {
color: var(--sidebar-text-color);
text-decoration: none;
}
.bottom-bar a {
width: 25%;
}
#tabbar {
display: flex;
font-size: 14px;
overflow: hidden;
}
#tabbar > a {
overflow: hidden;
max-width: 45%;
}
#tabbar.bottom-bar {
position: absolute;
bottom: 0;
left: 0;
padding: 0 16px;
box-sizing: border-box;
background-color: var(--sidebar-background-color);
border-top: 1px solid var(--divider-color);
justify-content: space-around;
z-index: 2;
font-size: 12px;
width: 100%;
padding-bottom: env(safe-area-inset-bottom);
}
#tabbar:not(.bottom-bar) {
flex: 1;
justify-content: center;
}
:host(:not([narrow])) #toolbar-icon {
min-width: 40px;
}
ha-menu-button,
ha-icon-button-arrow-prev,
::slotted([slot="toolbar-icon"]) {
display: flex;
flex-shrink: 0;
pointer-events: auto;
color: var(--sidebar-icon-color);
}
.main-title {
flex: 1;
max-height: var(--header-height);
line-height: 20px;
color: var(--sidebar-text-color);
margin: var(--main-title-margin, 0 0 0 24px);
}
.content {
position: relative;
width: calc(
100% - env(safe-area-inset-left) - env(safe-area-inset-right)
);
margin-left: env(safe-area-inset-left);
margin-right: env(safe-area-inset-right);
height: calc(100% - 1px - var(--header-height));
height: calc(
100% - 1px - var(--header-height) - env(safe-area-inset-bottom)
);
overflow: auto;
-webkit-overflow-scrolling: touch;
}
:host([narrow]) .content.tabs {
height: calc(100% - 2 * var(--header-height));
height: calc(
100% - 2 * var(--header-height) - env(safe-area-inset-bottom)
);
}
#fab {
position: fixed;
right: calc(16px + env(safe-area-inset-right));
bottom: calc(16px + env(safe-area-inset-bottom));
z-index: 1;
}
:host([narrow]) #fab.tabs {
bottom: calc(84px + env(safe-area-inset-bottom));
}
#fab[is-wide] {
bottom: 24px;
right: 24px;
}
:host([rtl]) #fab {
right: auto;
left: calc(16px + env(safe-area-inset-left));
}
:host([rtl][is-wide]) #fab {
bottom: 24px;
left: 24px;
right: auto;
}
`}}]}}),t);let E=e([c("hacs-store-panel")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[i({attribute:!1})],key:"filters",value:()=>({})},{kind:"field",decorators:[i({attribute:!1})],key:"hacs",value:void 0},{kind:"field",decorators:[i()],key:"_searchInput",value:()=>""},{kind:"field",decorators:[i({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"narrow",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"isWide",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"sections",value:void 0},{kind:"field",decorators:[i()],key:"section",value:void 0},{kind:"field",key:"_repositoriesInActiveSection",value(){return d(((e,t)=>[(null==e?void 0:e.filter((e=>{var i,a,s;return(null===(i=this.hacs.sections)||void 0===i||null===(a=i.find((e=>e.id===t)))||void 0===a||null===(s=a.categories)||void 0===s?void 0:s.includes(e.category))&&e.installed})))||[],(null==e?void 0:e.filter((e=>{var i,a,s;return(null===(i=this.hacs.sections)||void 0===i||null===(a=i.find((e=>e.id===t)))||void 0===a||null===(s=a.categories)||void 0===s?void 0:s.includes(e.category))&&e.new&&!e.installed})))||[]]))}},{kind:"get",key:"allRepositories",value:function(){const[e,t]=this._repositoriesInActiveSection(this.hacs.repositories,this.section);return t.concat(e)}},{kind:"field",key:"_filterRepositories",value:()=>d(z)},{kind:"get",key:"visibleRepositories",value:function(){const e=this.allRepositories.filter((e=>{var t,i;return null===(t=this.filters[this.section])||void 0===t||null===(i=t.find((t=>t.id===e.category)))||void 0===i?void 0:i.checked}));return this._filterRepositories(e,this._searchInput)}},{kind:"method",key:"firstUpdated",value:async function(){this.addEventListener("filter-change",(e=>this._updateFilters(e)))}},{kind:"method",key:"_updateFilters",value:function(e){var t;const i=null===(t=this.filters[this.section])||void 0===t?void 0:t.find((t=>t.id===e.detail.id));this.filters[this.section].find((e=>e.id===i.id)).checked=!i.checked,this.requestUpdate()}},{kind:"method",key:"render",value:function(){var e;if(!this.hacs)return o``;const t=this._repositoriesInActiveSection(this.hacs.repositories,this.section)[1];if(!this.filters[this.section]&&this.hacs.info.categories){var i;const e=null===(i=f(this.hacs.language,this.route))||void 0===i?void 0:i.categories;this.filters[this.section]=[],null==e||e.filter((e=>{var t;return null===(t=this.hacs.info)||void 0===t?void 0:t.categories.includes(e)})).forEach((e=>{this.filters[this.section].push({id:e,value:e,checked:!0})}))}return o`<hass-tabs-subpage
back-path="/hacs/entry"
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${this.hacs.sections}
hasFab
>
<ha-icon-overflow-menu
slot="toolbar-icon"
narrow
.hass=${this.hass}
.items=${[{path:b,label:this.hacs.localize("menu.documentation"),action:()=>m.open("https://hacs.xyz/","_blank","noreferrer=true")},{path:k,label:"GitHub",action:()=>m.open("https://github.com/hacs","_blank","noreferrer=true")},{path:g,label:this.hacs.localize("menu.open_issue"),action:()=>m.open("https://hacs.xyz/docs/issues","_blank","noreferrer=true")},{path:y,label:this.hacs.localize("menu.custom_repositories"),disabled:this.hacs.info.disabled_reason,action:()=>this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"custom-repositories",repositories:this.hacs.repositories},bubbles:!0,composed:!0}))},{path:w,label:this.hacs.localize("menu.about"),action:()=>I(this,this.hacs)}]}
>
</ha-icon-overflow-menu>
${this.narrow?o`
<search-input
.hass=${this.hass}
class="header"
slot="header"
.label=${this.hacs.localize("search.downloaded")}
.filter=${this._searchInput||""}
@value-changed=${this._inputValueChanged}
></search-input>
`:o`<div class="search">
<search-input
.hass=${this.hass}
.label=${0===t.length?this.hacs.localize("search.downloaded"):this.hacs.localize("search.downloaded_new")}
.filter=${this._searchInput||""}
@value-changed=${this._inputValueChanged}
></search-input>
</div>`}
<div class="content ${this.narrow?"narrow-content":""}">
${(null===(e=this.filters[this.section])||void 0===e?void 0:e.length)>1?o`<div class="filters">
<hacs-filter
.hacs=${this.hacs}
.filters="${this.filters[this.section]}"
></hacs-filter>
</div>`:""}
${null!=t&&t.length?o`<ha-alert .rtl=${j(this.hass)}>
${this.hacs.localize("store.new_repositories_note")}
<mwc-button
class="max-content"
slot="action"
.label=${this.hacs.localize("menu.dismiss")}
@click=${this._clearAllNewRepositories}
>
</mwc-button>
</ha-alert> `:""}
<div class="container ${this.narrow?"narrow":""}">
${void 0===this.hacs.repositories?"":0===this.allRepositories.length?this._renderEmpty():0===this.visibleRepositories.length?this._renderNoResultsFound():this._renderRepositories()}
</div>
</div>
<ha-fab
slot="fab"
.label=${this.hacs.localize("store.explore")}
.extended=${!this.narrow}
@click=${this._addRepository}
>
<ha-svg-icon slot="icon" .path=${x}></ha-svg-icon>
</ha-fab>
</hass-tabs-subpage>`}},{kind:"method",key:"_renderRepositories",value:function(){return this.visibleRepositories.map((e=>o`<hacs-repository-card
.hass=${this.hass}
.hacs=${this.hacs}
.repository=${e}
.narrow=${this.narrow}
?narrow=${this.narrow}
></hacs-repository-card>`))}},{kind:"method",key:"_clearAllNewRepositories",value:async function(){var e;await $(this.hass,{categories:(null===(e=f(this.hacs.language,this.route))||void 0===e?void 0:e.categories)||[]})}},{kind:"method",key:"_renderNoResultsFound",value:function(){return o`<ha-alert
.rtl=${j(this.hass)}
alert-type="warning"
.title="${this.hacs.localize("store.no_repositories")} 😕"
>
${this.hacs.localize("store.no_repositories_found_desc1",{searchInput:this._searchInput})}
<br />
${this.hacs.localize("store.no_repositories_found_desc2")}
</ha-alert>`}},{kind:"method",key:"_renderEmpty",value:function(){return o`<ha-alert
.title="${this.hacs.localize("store.no_repositories")} 😕"
.rtl=${j(this.hass)}
>
${this.hacs.localize("store.no_repositories_desc1")}
<br />
${this.hacs.localize("store.no_repositories_desc2")}
</ha-alert>`}},{kind:"method",key:"_inputValueChanged",value:function(e){this._searchInput=e.detail.value,window.localStorage.setItem("hacs-search",this._searchInput)}},{kind:"method",key:"_addRepository",value:function(){this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"add-repository",repositories:this.hacs.repositories,section:this.section},bubbles:!0,composed:!0}))}},{kind:"get",static:!0,key:"styles",value:function(){return[_,R,h`
.filter {
border-bottom: 1px solid var(--divider-color);
}
.content {
height: calc(100vh - 128px);
overflow: auto;
}
.narrow-content {
height: calc(100vh - 128px);
}
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));
justify-items: center;
grid-gap: 8px 8px;
padding: 8px 16px 16px;
margin-bottom: 64px;
}
ha-svg-icon {
color: var(--hcv-text-color-on-background);
}
hacs-repository-card {
max-width: 500px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
hacs-repository-card[narrow] {
width: 100%;
}
hacs-repository-card[narrow]:last-of-type {
margin-bottom: 64px;
}
ha-alert {
color: var(--hcv-text-color-primary);
display: block;
margin-top: -4px;
}
.narrow {
width: 100%;
display: block;
padding: 0px;
margin: 0;
}
search-input {
display: block;
}
search-input.header {
padding: 0;
}
.bottom-bar {
position: fixed !important;
}
.max-content {
width: max-content;
}
`]}}]}}),t);export{E as HacsStorePanel};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
import{a as e,e as t,i,L as a,N as d,$ as r,r as n,n as o}from"./main-ad130be7.js";import{H as s}from"./c.0a1cf8d0.js";e([o("ha-clickable-list-item")],(function(e,o){class s extends o{constructor(...t){super(...t),e(this)}}return{F:s,d:[{kind:"field",decorators:[t()],key:"href",value:void 0},{kind:"field",decorators:[t({type:Boolean})],key:"disableHref",value:()=>!1},{kind:"field",decorators:[t({type:Boolean,reflect:!0})],key:"openNewTab",value:()=>!1},{kind:"field",decorators:[i("a")],key:"_anchor",value:void 0},{kind:"method",key:"render",value:function(){const e=a(d(s.prototype),"render",this).call(this),t=this.href||"";return r`${this.disableHref?r`<a aria-role="option">${e}</a>`:r`<a
aria-role="option"
target=${this.openNewTab?"_blank":""}
href=${t}
>${e}</a
>`}`}},{kind:"method",key:"firstUpdated",value:function(){a(d(s.prototype),"firstUpdated",this).call(this),this.addEventListener("keydown",(e=>{"Enter"!==e.key&&" "!==e.key||this._anchor.click()}))}},{kind:"get",static:!0,key:"styles",value:function(){return[a(d(s),"styles",this),n`
a {
width: 100%;
height: 100%;
display: flex;
align-items: center;
padding-left: var(--mdc-list-side-padding, 20px);
padding-right: var(--mdc-list-side-padding, 20px);
overflow: hidden;
}
`]}}]}}),s);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
const n=(n,o)=>n&&n.config.components.includes(o);export{n as i};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{al as e,am as a,aj as s,an as r,ao as u}from"./main-ad130be7.js";async function i(i,o,n){const t=new e("updateLovelaceResources"),l=await a(i),d=`/hacsfiles/${o.full_name.split("/")[1]}`,c=s({repository:o,version:n}),p=l.find((e=>e.url.includes(d)));t.debug({namespace:d,url:c,exsisting:p}),p&&p.url!==c?(t.debug(`Updating exsusting resource for ${d}`),await r(i,{url:c,resource_id:p.id,res_type:p.type})):l.map((e=>e.url)).includes(c)||(t.debug(`Adding ${c} to Lovelace resources`),await u(i,{url:c,res_type:"module"}))}export{i as u};

Some files were not shown because too many files have changed in this diff Show More