Configure laundry drying in Home Assistant
This commit is contained in:
parent
2bd4dac0c5
commit
5e1ec01c51
3 changed files with 238 additions and 257 deletions
25
AGENTS.md
25
AGENTS.md
|
|
@ -600,6 +600,31 @@ Cuidados:
|
||||||
- Se mover a origem dos logs do Codex, ajuste `CODEX_SESSION_ROOT` no servico `codex-status-export`.
|
- Se mover a origem dos logs do Codex, ajuste `CODEX_SESSION_ROOT` no servico `codex-status-export`.
|
||||||
- O dashboard do Home Assistant continua em storage mode; a fonte declarativa aqui cobre os sensores, nao os cards da UI.
|
- O dashboard do Home Assistant continua em storage mode; a fonte declarativa aqui cobre os sensores, nao os cards da UI.
|
||||||
|
|
||||||
|
### Home Assistant / secagem de roupas
|
||||||
|
|
||||||
|
Fonte principal:
|
||||||
|
|
||||||
|
- `services.home-assistant.config` em `hosts/Nixos/configuration.nix`
|
||||||
|
|
||||||
|
Estado atual:
|
||||||
|
|
||||||
|
- O helper `input_boolean.secar_roupas` e declarado pelo repo.
|
||||||
|
- A automacao `secar_roupas_start` inicia o script ao ligar o helper.
|
||||||
|
- A automacao `secar_roupas_stop` desliga `climate.ar` ao desligar o helper.
|
||||||
|
- O script `script.secar_roupas_com_ar_condicionado` usa `climate.ar`.
|
||||||
|
- A temperatura vem de `state_attr('climate.ar', 'current_temperature')`; nao ha sensor de umidade dedicado.
|
||||||
|
- O modo dominante e `dry`.
|
||||||
|
- `heat` so e usado abaixo de 21 C, com setpoint 24 C.
|
||||||
|
- `cool` so entra em pulsos curtos se o `dry` estiver idle e a temperatura permitir.
|
||||||
|
- Fan mode e definido apenas se o valor existir em `state_attr('climate.ar', 'fan_modes')`; tenta `medium`/`high` e cai para `auto`.
|
||||||
|
|
||||||
|
Cuidados:
|
||||||
|
|
||||||
|
- Se trocar a entidade do ar, atualize `haAirConditioner` no `let` de `hosts/Nixos/configuration.nix`.
|
||||||
|
- Se trocar o helper, atualize `haLaundryDryingToggle` no mesmo `let`.
|
||||||
|
- Se a integracao do ar nao expuser `hvac_action = idle`, o fallback com `cool` nao dispara; o script continua funcionando em `dry`/`heat`.
|
||||||
|
- Nao mova essa logica para arquivos em `/var/lib/hass` ou pela UI sem refletir a mudanca no repo.
|
||||||
|
|
||||||
### Home Assistant / dashboard do Codex
|
### Home Assistant / dashboard do Codex
|
||||||
|
|
||||||
Arquivo principal:
|
Arquivo principal:
|
||||||
|
|
|
||||||
|
|
@ -1,257 +0,0 @@
|
||||||
# Script standalone para Home Assistant.
|
|
||||||
# Uso previsto:
|
|
||||||
# 1. Crie o helper `input_boolean.secar_roupas` pela UI do Home Assistant.
|
|
||||||
# 2. Cole o bloco abaixo em `scripts.yaml` ou importe pelo editor YAML de Scripts.
|
|
||||||
# 3. Se o seu ar nao aceitar `medium` em `fan_mode`, troque para `auto`.
|
|
||||||
|
|
||||||
secar_roupas_com_ar_condicionado:
|
|
||||||
alias: Secar roupas com ar-condicionado
|
|
||||||
description: >
|
|
||||||
Usa Dry como modo dominante para secar roupas em uma sala fechada.
|
|
||||||
Recorre a Heat apenas se a sala esfriar demais e usa Cool em pulsos curtos
|
|
||||||
quando o Dry aparenta ficar idle em temperatura ainda aceitavel.
|
|
||||||
mode: restart
|
|
||||||
icon: mdi:hanger
|
|
||||||
sequence:
|
|
||||||
- variables:
|
|
||||||
toggle_entity: input_boolean.secar_roupas
|
|
||||||
climate_entity: climate.ar
|
|
||||||
min_heat_temp: 21
|
|
||||||
heat_target_temp: 24
|
|
||||||
cool_pulse_target_temp: 22
|
|
||||||
warm_room_threshold: 22
|
|
||||||
hot_room_threshold: 28
|
|
||||||
dry_fan_mode: medium
|
|
||||||
heat_fan_mode: high
|
|
||||||
cool_fan_mode: medium
|
|
||||||
|
|
||||||
- condition: state
|
|
||||||
entity_id: input_boolean.secar_roupas
|
|
||||||
state: "on"
|
|
||||||
|
|
||||||
# Fase inicial: comeca sempre em Dry para puxar umidade das roupas.
|
|
||||||
- service: climate.set_hvac_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
hvac_mode: dry
|
|
||||||
|
|
||||||
- service: climate.set_fan_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
fan_mode: "{{ dry_fan_mode }}"
|
|
||||||
|
|
||||||
- wait_template: "{{ is_state(toggle_entity, 'off') }}"
|
|
||||||
timeout: "00:30:00"
|
|
||||||
continue_on_timeout: true
|
|
||||||
|
|
||||||
- repeat:
|
|
||||||
while:
|
|
||||||
- condition: state
|
|
||||||
entity_id: input_boolean.secar_roupas
|
|
||||||
state: "on"
|
|
||||||
sequence:
|
|
||||||
- variables:
|
|
||||||
current_temp: >-
|
|
||||||
{{ state_attr(climate_entity, 'current_temperature') | float(0) }}
|
|
||||||
current_action: >-
|
|
||||||
{{ state_attr(climate_entity, 'hvac_action') | default('', true) | string | lower }}
|
|
||||||
|
|
||||||
- choose:
|
|
||||||
# Sala fria: usa Heat apenas para nao deixar o ambiente frio demais.
|
|
||||||
- conditions:
|
|
||||||
- condition: template
|
|
||||||
value_template: "{{ current_temp < min_heat_temp }}"
|
|
||||||
sequence:
|
|
||||||
- service: climate.set_hvac_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
hvac_mode: heat
|
|
||||||
|
|
||||||
- service: climate.set_temperature
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
temperature: "{{ heat_target_temp }}"
|
|
||||||
|
|
||||||
- service: climate.set_fan_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
fan_mode: "{{ heat_fan_mode }}"
|
|
||||||
|
|
||||||
- wait_template: "{{ is_state(toggle_entity, 'off') }}"
|
|
||||||
timeout: "00:20:00"
|
|
||||||
continue_on_timeout: true
|
|
||||||
|
|
||||||
- choose:
|
|
||||||
- conditions:
|
|
||||||
- condition: state
|
|
||||||
entity_id: input_boolean.secar_roupas
|
|
||||||
state: "on"
|
|
||||||
sequence:
|
|
||||||
- service: climate.set_hvac_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
hvac_mode: dry
|
|
||||||
|
|
||||||
- service: climate.set_fan_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
fan_mode: "{{ dry_fan_mode }}"
|
|
||||||
|
|
||||||
- wait_template: "{{ is_state(toggle_entity, 'off') }}"
|
|
||||||
timeout: "00:10:00"
|
|
||||||
continue_on_timeout: true
|
|
||||||
|
|
||||||
# Sala muito quente: nunca aquece. Mantem Dry ou usa um Cool curto
|
|
||||||
# apenas se o atributo `hvac_action` existir e vier como `idle`.
|
|
||||||
- conditions:
|
|
||||||
- condition: template
|
|
||||||
value_template: "{{ current_temp >= hot_room_threshold }}"
|
|
||||||
sequence:
|
|
||||||
- choose:
|
|
||||||
- conditions:
|
|
||||||
- condition: template
|
|
||||||
value_template: "{{ current_action == 'idle' }}"
|
|
||||||
sequence:
|
|
||||||
- service: climate.set_hvac_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
hvac_mode: cool
|
|
||||||
|
|
||||||
- service: climate.set_temperature
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
temperature: "{{ cool_pulse_target_temp }}"
|
|
||||||
|
|
||||||
- service: climate.set_fan_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
fan_mode: "{{ cool_fan_mode }}"
|
|
||||||
|
|
||||||
- wait_template: "{{ is_state(toggle_entity, 'off') }}"
|
|
||||||
timeout: "00:20:00"
|
|
||||||
continue_on_timeout: true
|
|
||||||
|
|
||||||
- conditions: []
|
|
||||||
sequence:
|
|
||||||
- service: climate.set_hvac_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
hvac_mode: dry
|
|
||||||
|
|
||||||
- service: climate.set_fan_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
fan_mode: "{{ dry_fan_mode }}"
|
|
||||||
|
|
||||||
- wait_template: "{{ is_state(toggle_entity, 'off') }}"
|
|
||||||
timeout: "00:45:00"
|
|
||||||
continue_on_timeout: true
|
|
||||||
|
|
||||||
- choose:
|
|
||||||
- conditions:
|
|
||||||
- condition: state
|
|
||||||
entity_id: input_boolean.secar_roupas
|
|
||||||
state: "on"
|
|
||||||
sequence:
|
|
||||||
- service: climate.set_hvac_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
hvac_mode: dry
|
|
||||||
|
|
||||||
- service: climate.set_fan_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
fan_mode: "{{ dry_fan_mode }}"
|
|
||||||
|
|
||||||
# Dry parece parado/idle e a sala ainda esta relativamente quente:
|
|
||||||
# aplica um pulso curto de Cool e volta para Dry.
|
|
||||||
# Se o seu climate nao expuser `hvac_action`, esta heuristica nao dispara.
|
|
||||||
- conditions:
|
|
||||||
- condition: template
|
|
||||||
value_template: >-
|
|
||||||
{{
|
|
||||||
current_temp > warm_room_threshold
|
|
||||||
and current_temp < hot_room_threshold
|
|
||||||
and current_action == 'idle'
|
|
||||||
}}
|
|
||||||
sequence:
|
|
||||||
- service: climate.set_hvac_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
hvac_mode: cool
|
|
||||||
|
|
||||||
- service: climate.set_temperature
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
temperature: "{{ cool_pulse_target_temp }}"
|
|
||||||
|
|
||||||
- service: climate.set_fan_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
fan_mode: "{{ cool_fan_mode }}"
|
|
||||||
|
|
||||||
- wait_template: "{{ is_state(toggle_entity, 'off') }}"
|
|
||||||
timeout: "00:15:00"
|
|
||||||
continue_on_timeout: true
|
|
||||||
|
|
||||||
- choose:
|
|
||||||
- conditions:
|
|
||||||
- condition: state
|
|
||||||
entity_id: input_boolean.secar_roupas
|
|
||||||
state: "on"
|
|
||||||
sequence:
|
|
||||||
- service: climate.set_hvac_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
hvac_mode: dry
|
|
||||||
|
|
||||||
- service: climate.set_fan_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
fan_mode: "{{ dry_fan_mode }}"
|
|
||||||
|
|
||||||
- wait_template: "{{ is_state(toggle_entity, 'off') }}"
|
|
||||||
timeout: "00:40:00"
|
|
||||||
continue_on_timeout: true
|
|
||||||
|
|
||||||
default:
|
|
||||||
# Faixa normal: prioriza Dry por blocos longos.
|
|
||||||
- service: climate.set_hvac_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
hvac_mode: dry
|
|
||||||
|
|
||||||
- service: climate.set_fan_mode
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
data:
|
|
||||||
fan_mode: "{{ dry_fan_mode }}"
|
|
||||||
|
|
||||||
- wait_template: "{{ is_state(toggle_entity, 'off') }}"
|
|
||||||
timeout: "00:45:00"
|
|
||||||
continue_on_timeout: true
|
|
||||||
|
|
||||||
# Ao desativar o helper, desliga o ar para sair em estado seguro.
|
|
||||||
- service: climate.turn_off
|
|
||||||
target:
|
|
||||||
entity_id: "{{ climate_entity }}"
|
|
||||||
|
|
@ -8,6 +8,59 @@ let
|
||||||
homeDir = "/home/${username}";
|
homeDir = "/home/${username}";
|
||||||
repoDir = "${homeDir}/Projetos/Sistemas/nixos-config";
|
repoDir = "${homeDir}/Projetos/Sistemas/nixos-config";
|
||||||
codexStatusPath = "/var/lib/hass/codex_status.json";
|
codexStatusPath = "/var/lib/hass/codex_status.json";
|
||||||
|
haAirConditioner = "climate.ar";
|
||||||
|
haLaundryDryingToggle = "input_boolean.secar_roupas";
|
||||||
|
haClimateAction = action: data: {
|
||||||
|
inherit action;
|
||||||
|
target.entity_id = haAirConditioner;
|
||||||
|
inherit data;
|
||||||
|
};
|
||||||
|
haSetHvacMode = mode:
|
||||||
|
haClimateAction "climate.set_hvac_mode" { hvac_mode = mode; };
|
||||||
|
haSetTemperature = temperature:
|
||||||
|
haClimateAction "climate.set_temperature" { inherit temperature; };
|
||||||
|
haSetFanMode = fan_mode:
|
||||||
|
haClimateAction "climate.set_fan_mode" { inherit fan_mode; };
|
||||||
|
haTurnOffClimate = haClimateAction "climate.turn_off" { };
|
||||||
|
haWaitForDryingToggleOff = timeout: {
|
||||||
|
wait_template = "{{ is_state('${haLaundryDryingToggle}', 'off') }}";
|
||||||
|
inherit timeout;
|
||||||
|
continue_on_timeout = true;
|
||||||
|
};
|
||||||
|
haSetFanModeIfAvailable = preferred: fallback: {
|
||||||
|
choose = [
|
||||||
|
{
|
||||||
|
conditions = [{
|
||||||
|
condition = "template";
|
||||||
|
value_template =
|
||||||
|
"{{ '${preferred}' in (state_attr('${haAirConditioner}', 'fan_modes') or []) }}";
|
||||||
|
}];
|
||||||
|
sequence = [ (haSetFanMode preferred) ];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
conditions = [{
|
||||||
|
condition = "template";
|
||||||
|
value_template =
|
||||||
|
"{{ '${fallback}' in (state_attr('${haAirConditioner}', 'fan_modes') or []) }}";
|
||||||
|
}];
|
||||||
|
sequence = [ (haSetFanMode fallback) ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
haDryCycle = [
|
||||||
|
(haSetHvacMode "dry")
|
||||||
|
(haSetFanModeIfAvailable "medium" "auto")
|
||||||
|
];
|
||||||
|
haHeatCycle = [
|
||||||
|
(haSetHvacMode "heat")
|
||||||
|
(haSetTemperature 24)
|
||||||
|
(haSetFanModeIfAvailable "high" "auto")
|
||||||
|
];
|
||||||
|
haCoolPulse = [
|
||||||
|
(haSetHvacMode "cool")
|
||||||
|
(haSetTemperature 22)
|
||||||
|
(haSetFanModeIfAvailable "medium" "auto")
|
||||||
|
];
|
||||||
zenExtension = shortId: guid: {
|
zenExtension = shortId: guid: {
|
||||||
name = guid;
|
name = guid;
|
||||||
value = {
|
value = {
|
||||||
|
|
@ -240,6 +293,166 @@ in {
|
||||||
|
|
||||||
default_config = { };
|
default_config = { };
|
||||||
|
|
||||||
|
input_boolean = {
|
||||||
|
secar_roupas = {
|
||||||
|
name = "Secar roupas";
|
||||||
|
icon = "mdi:hanger";
|
||||||
|
initial = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
script = {
|
||||||
|
secar_roupas_com_ar_condicionado = {
|
||||||
|
alias = "Secar roupas com ar-condicionado";
|
||||||
|
description =
|
||||||
|
"Seca roupas em uma sala fechada usando Dry como modo dominante, Heat apenas contra frio excessivo e Cool em pulsos curtos quando o Dry fica idle.";
|
||||||
|
mode = "restart";
|
||||||
|
icon = "mdi:hanger";
|
||||||
|
sequence = [
|
||||||
|
{
|
||||||
|
condition = "state";
|
||||||
|
entity_id = haLaundryDryingToggle;
|
||||||
|
state = "on";
|
||||||
|
}
|
||||||
|
] ++ haDryCycle ++ [
|
||||||
|
(haWaitForDryingToggleOff "00:30:00")
|
||||||
|
{
|
||||||
|
repeat = {
|
||||||
|
while = [{
|
||||||
|
condition = "state";
|
||||||
|
entity_id = haLaundryDryingToggle;
|
||||||
|
state = "on";
|
||||||
|
}];
|
||||||
|
sequence = [
|
||||||
|
{
|
||||||
|
variables = {
|
||||||
|
current_temp =
|
||||||
|
"{{ state_attr('${haAirConditioner}', 'current_temperature') | float(24) }}";
|
||||||
|
current_action =
|
||||||
|
"{{ state_attr('${haAirConditioner}', 'hvac_action') | default('', true) | string | lower }}";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
choose = [
|
||||||
|
{
|
||||||
|
conditions = [{
|
||||||
|
condition = "template";
|
||||||
|
value_template =
|
||||||
|
"{{ current_temp < 21 }}";
|
||||||
|
}];
|
||||||
|
sequence = haHeatCycle ++ [
|
||||||
|
(haWaitForDryingToggleOff "00:20:00")
|
||||||
|
{
|
||||||
|
choose = [{
|
||||||
|
conditions = [{
|
||||||
|
condition = "state";
|
||||||
|
entity_id = haLaundryDryingToggle;
|
||||||
|
state = "on";
|
||||||
|
}];
|
||||||
|
sequence = haDryCycle
|
||||||
|
++ [ (haWaitForDryingToggleOff "00:10:00") ];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
conditions = [{
|
||||||
|
condition = "template";
|
||||||
|
value_template =
|
||||||
|
"{{ current_temp >= 28 }}";
|
||||||
|
}];
|
||||||
|
sequence = [
|
||||||
|
{
|
||||||
|
choose = [{
|
||||||
|
conditions = [{
|
||||||
|
condition = "template";
|
||||||
|
value_template =
|
||||||
|
"{{ current_action == 'idle' }}";
|
||||||
|
}];
|
||||||
|
sequence = haCoolPulse ++ [
|
||||||
|
(haWaitForDryingToggleOff "00:20:00")
|
||||||
|
{
|
||||||
|
choose = [{
|
||||||
|
conditions = [{
|
||||||
|
condition = "state";
|
||||||
|
entity_id = haLaundryDryingToggle;
|
||||||
|
state = "on";
|
||||||
|
}];
|
||||||
|
sequence = haDryCycle ++ [
|
||||||
|
(haWaitForDryingToggleOff "00:40:00")
|
||||||
|
];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}];
|
||||||
|
default = haDryCycle
|
||||||
|
++ [ (haWaitForDryingToggleOff "00:45:00") ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
conditions = [{
|
||||||
|
condition = "template";
|
||||||
|
value_template = ''
|
||||||
|
{{
|
||||||
|
current_temp > 22
|
||||||
|
and current_temp < 28
|
||||||
|
and current_action == 'idle'
|
||||||
|
}}
|
||||||
|
'';
|
||||||
|
}];
|
||||||
|
sequence = haCoolPulse ++ [
|
||||||
|
(haWaitForDryingToggleOff "00:15:00")
|
||||||
|
{
|
||||||
|
choose = [{
|
||||||
|
conditions = [{
|
||||||
|
condition = "state";
|
||||||
|
entity_id = haLaundryDryingToggle;
|
||||||
|
state = "on";
|
||||||
|
}];
|
||||||
|
sequence = haDryCycle
|
||||||
|
++ [ (haWaitForDryingToggleOff "00:40:00") ];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
default = haDryCycle
|
||||||
|
++ [ (haWaitForDryingToggleOff "00:45:00") ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
haTurnOffClimate
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
automation = [
|
||||||
|
{
|
||||||
|
id = "secar_roupas_start";
|
||||||
|
alias = "Iniciar secagem de roupas";
|
||||||
|
mode = "single";
|
||||||
|
trigger = [{
|
||||||
|
platform = "state";
|
||||||
|
entity_id = haLaundryDryingToggle;
|
||||||
|
to = "on";
|
||||||
|
}];
|
||||||
|
action = [{ action = "script.secar_roupas_com_ar_condicionado"; }];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
id = "secar_roupas_stop";
|
||||||
|
alias = "Parar secagem de roupas";
|
||||||
|
mode = "single";
|
||||||
|
trigger = [{
|
||||||
|
platform = "state";
|
||||||
|
entity_id = haLaundryDryingToggle;
|
||||||
|
to = "off";
|
||||||
|
}];
|
||||||
|
action = [ haTurnOffClimate ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
command_line = [
|
command_line = [
|
||||||
{
|
{
|
||||||
sensor = {
|
sensor = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue