Add lock component documentation (#1867)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
5934c83969
commit
7fd533fe17
|
@ -2,25 +2,40 @@ const source = new EventSource("/events");
|
|||
|
||||
source.addEventListener('log', function (e) {
|
||||
const log = document.getElementById("log");
|
||||
let log_prefs = [
|
||||
["\u001b[1;31m", 'e'],
|
||||
["\u001b[0;33m", 'w'],
|
||||
["\u001b[0;32m", 'i'],
|
||||
["\u001b[0;35m", 'c'],
|
||||
["\u001b[0;36m", 'd'],
|
||||
["\u001b[0;37m", 'v'],
|
||||
];
|
||||
|
||||
let klass = '';
|
||||
if (e.data.startsWith("[1;31m")) {
|
||||
klass = 'e';
|
||||
} else if (e.data.startsWith("[0;33m")) {
|
||||
klass = 'w';
|
||||
} else if (e.data.startsWith("[0;32m")) {
|
||||
klass = 'i';
|
||||
} else if (e.data.startsWith("[0;35m")) {
|
||||
klass = 'c';
|
||||
} else if (e.data.startsWith("[0;36m")) {
|
||||
klass = 'd';
|
||||
} else if (e.data.startsWith("[0;37m")) {
|
||||
klass = 'v';
|
||||
} else {
|
||||
for (const log_pref of log_prefs){
|
||||
if (e.data.startsWith(log_pref[0])) {
|
||||
klass = log_pref[1];
|
||||
}
|
||||
}
|
||||
if (klass == ''){
|
||||
log.innerHTML += e.data + '\n';
|
||||
}
|
||||
log.innerHTML += '<span class="' + klass + '">' + e.data.substr(7, e.data.length - 10) + "</span>\n";
|
||||
log.innerHTML += '<span class="' + klass + '">' + e.data.substr(7, e.data.length - 11) + "</span>\n";
|
||||
});
|
||||
|
||||
actions = [
|
||||
["switch", ["toggle"]],
|
||||
["light", ["toggle"]],
|
||||
["fan", ["toggle"]],
|
||||
["cover", ["open", "close"]],
|
||||
["button", ["press"]],
|
||||
["lock", ["lock", "unlock", "open"]],
|
||||
];
|
||||
multi_actions = [
|
||||
["select", "option"],
|
||||
["number", "value"],
|
||||
];
|
||||
|
||||
source.addEventListener('state', function (e) {
|
||||
const data = JSON.parse(e.data);
|
||||
document.getElementById(data.id).children[1].innerText = data.state;
|
||||
|
@ -32,73 +47,27 @@ for (; row = states.rows[i]; i++) {
|
|||
if (!row.children[2].children.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row.classList.contains("switch")) {
|
||||
(function(id) {
|
||||
row.children[2].children[0].addEventListener('click', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/switch/' + id.substr(7) + '/toggle', true);
|
||||
xhr.send();
|
||||
});
|
||||
})(row.id);
|
||||
}
|
||||
if (row.classList.contains("fan")) {
|
||||
(function(id) {
|
||||
row.children[2].children[0].addEventListener('click', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/fan/' + id.substr(4) + '/toggle', true);
|
||||
xhr.send();
|
||||
});
|
||||
})(row.id);
|
||||
}
|
||||
if (row.classList.contains("light")) {
|
||||
(function(id) {
|
||||
row.children[2].children[0].addEventListener('click', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/light/' + id.substr(6) + '/toggle', true);
|
||||
xhr.send();
|
||||
});
|
||||
})(row.id);
|
||||
}
|
||||
if (row.classList.contains("cover")) {
|
||||
(function(id) {
|
||||
row.children[2].children[0].addEventListener('click', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/cover/' + id.substr(6) + '/open', true);
|
||||
xhr.send();
|
||||
});
|
||||
row.children[2].children[1].addEventListener('click', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/cover/' + id.substr(6) + '/close', true);
|
||||
xhr.send();
|
||||
});
|
||||
})(row.id);
|
||||
}
|
||||
if (row.classList.contains("select")) {
|
||||
(function(id) {
|
||||
|
||||
for (const domain of actions){
|
||||
if (row.classList.contains(domain[0])) {
|
||||
let id = row.id.substr(domain[0].length+1);
|
||||
for (let j=0;j<row.children[2].children.length && j < domain[1].length; j++){
|
||||
row.children[2].children[j].addEventListener('click', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/'+domain[0]+'/' + id + '/' + domain[1][j], true);
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const domain of multi_actions){
|
||||
if (row.classList.contains(domain[0])) {
|
||||
let id = row.id.substr(domain[0].length+1);
|
||||
row.children[2].children[0].addEventListener('change', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/select/' + id.substr(7) + '/set?option=' + encodeURIComponent(this.value), true);
|
||||
xhr.open("POST", '/'+domain[0]+'/' + id + '/set?'+domain[1]+'=' + encodeURIComponent(this.value), true);
|
||||
xhr.send();
|
||||
});
|
||||
})(row.id);
|
||||
}
|
||||
if (row.classList.contains("number")) {
|
||||
(function(id) {
|
||||
row.children[2].children[0].addEventListener('change', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/number/' + id.substr(7) + '/set?value=' + encodeURIComponent(this.value), true);
|
||||
xhr.send();
|
||||
});
|
||||
})(row.id);
|
||||
}
|
||||
if (row.classList.contains("button")) {
|
||||
(function(id) {
|
||||
row.children[2].children[0].addEventListener('click', function () {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/button/' + id.substr(7) + '/press', true);
|
||||
xhr.send();
|
||||
});
|
||||
})(row.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
const source=new EventSource("/events");source.addEventListener("log",(function(t){const n=document.getElementById("log");let e="";t.data.startsWith("[1;31m")?e="e":t.data.startsWith("[0;33m")?e="w":t.data.startsWith("[0;32m")?e="i":t.data.startsWith("[0;35m")?e="c":t.data.startsWith("[0;36m")?e="d":t.data.startsWith("[0;37m")?e="v":n.innerHTML+=t.data+"\n",n.innerHTML+='<span class="'+e+'">'+t.data.substr(7,t.data.length-10)+"</span>\n"})),source.addEventListener("state",(function(t){const n=JSON.parse(t.data);document.getElementById(n.id).children[1].innerText=n.state}));const states=document.getElementById("states");let row,i=0;for(;row=states.rows[i];i++)row.children[2].children.length&&(row.classList.contains("switch")&&function(t){row.children[2].children[0].addEventListener("click",(function(){const n=new XMLHttpRequest;n.open("POST","/switch/"+t.substr(7)+"/toggle",!0),n.send()}))}(row.id),row.classList.contains("fan")&&function(t){row.children[2].children[0].addEventListener("click",(function(){const n=new XMLHttpRequest;n.open("POST","/fan/"+t.substr(4)+"/toggle",!0),n.send()}))}(row.id),row.classList.contains("light")&&function(t){row.children[2].children[0].addEventListener("click",(function(){const n=new XMLHttpRequest;n.open("POST","/light/"+t.substr(6)+"/toggle",!0),n.send()}))}(row.id),row.classList.contains("cover")&&function(t){row.children[2].children[0].addEventListener("click",(function(){const n=new XMLHttpRequest;n.open("POST","/cover/"+t.substr(6)+"/open",!0),n.send()})),row.children[2].children[1].addEventListener("click",(function(){const n=new XMLHttpRequest;n.open("POST","/cover/"+t.substr(6)+"/close",!0),n.send()}))}(row.id),row.classList.contains("select")&&function(t){row.children[2].children[0].addEventListener("change",(function(){const n=new XMLHttpRequest;n.open("POST","/select/"+t.substr(7)+"/set?option="+encodeURIComponent(this.value),!0),n.send()}))}(row.id),row.classList.contains("number")&&function(t){row.children[2].children[0].addEventListener("change",(function(){const n=new XMLHttpRequest;n.open("POST","/number/"+t.substr(7)+"/set?value="+encodeURIComponent(this.value),!0),n.send()}))}(row.id),row.classList.contains("button")&&function(t){row.children[2].children[0].addEventListener("click",(function(){const n=new XMLHttpRequest;n.open("POST","/button/"+t.substr(7)+"/press",!0),n.send()}))}(row.id));
|
||||
const source=new EventSource("/events");source.addEventListener("log",function(t){const e=document.getElementById("log");let n=[["[1;31m","e"],["[0;33m","w"],["[0;32m","i"],["[0;35m","c"],["[0;36m","d"],["[0;37m","v"]],o="";for(const e of n)t.data.startsWith(e[0])&&(o=e[1]);""==o&&(e.innerHTML+=t.data+"\n"),e.innerHTML+='<span class="'+o+'">'+t.data.substr(7,t.data.length-11)+"</span>\n"}),actions=[["switch",["toggle"]],["light",["toggle"]],["fan",["toggle"]],["cover",["open","close"]],["button",["press"]],["lock",["lock","unlock","open"]]],multi_actions=[["select","option"],["number","value"]],source.addEventListener("state",function(t){const e=JSON.parse(t.data);document.getElementById(e.id).children[1].innerText=e.state});const states=document.getElementById("states");let row,i=0;for(;row=states.rows[i];i++)if(row.children[2].children.length){for(const t of actions)if(row.classList.contains(t[0])){let e=row.id.substr(t[0].length+1);for(let n=0;n<row.children[2].children.length&&n<t[1].length;n++)row.children[2].children[n].addEventListener("click",function(){const o=new XMLHttpRequest;o.open("POST","/"+t[0]+"/"+e+"/"+t[1][n],!0),o.send()})}for(const t of multi_actions)if(row.classList.contains(t[0])){let e=row.id.substr(t[0].length+1);row.children[2].children[0].addEventListener("change",function(){const n=new XMLHttpRequest;n.open("POST","/"+t[0]+"/"+e+"/set?"+t[1]+"="+encodeURIComponent(this.value),!0),n.send()})}}
|
|
@ -19,4 +19,5 @@ Components
|
|||
text_sensor/index
|
||||
stepper/index
|
||||
touchscreen/index
|
||||
lock/index
|
||||
*
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,166 @@
|
|||
Lock Component
|
||||
================
|
||||
|
||||
.. seo::
|
||||
:description: Instructions for setting up generic locks in ESPHome.
|
||||
:image: folder-open.svg
|
||||
|
||||
The ``lock`` domain includes all platforms that should function like a lock
|
||||
with lock/unlock actions.
|
||||
|
||||
.. note::
|
||||
|
||||
ESPHome lock components requires Home Assistant 2022.3 or newer
|
||||
|
||||
.. _config-lock:
|
||||
|
||||
Base Lock Configuration
|
||||
-------------------------
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
lock:
|
||||
- platform: ...
|
||||
name: "Lock Name"
|
||||
|
||||
Configuration variables:
|
||||
|
||||
- **name** (**Required**, string): The name of the lock.
|
||||
- **icon** (*Optional*, icon): Manually set the icon to use for the
|
||||
lock in the frontend.
|
||||
- **internal** (*Optional*, boolean): Mark this component as internal. Internal components will
|
||||
not be exposed to the frontend (like Home Assistant). Only specifying an ``id`` without
|
||||
a ``name`` will implicitly set this to true.
|
||||
- **on_lock** (*Optional*, :ref:`Action <config-action>`): An automation to perform
|
||||
when the lock is locked. See :ref:`lock-on_lock_unlock_trigger`.
|
||||
- **on_unlock** (*Optional*, :ref:`Action <config-action>`): An automation to perform
|
||||
when the lock is unlocked. See :ref:`lock-on_lock_unlock_trigger`..
|
||||
- **disabled_by_default** (*Optional*, boolean): If true, then this entity should not be added to any client's frontend,
|
||||
(usually Home Assistant) without the user manually enabling it (via the Home Assistant UI).
|
||||
Defaults to ``false``.
|
||||
- **entity_category** (*Optional*, string): The category of the entity.
|
||||
See https://developers.home-assistant.io/docs/core/entity/#generic-properties
|
||||
for a list of available options. Set to ``""`` to remove the default entity category.
|
||||
- If MQTT enabled, All other options from :ref:`MQTT Component <config-mqtt-component>`.
|
||||
|
||||
.. _lock-lock_action:
|
||||
|
||||
``lock.lock`` Action
|
||||
*************************
|
||||
|
||||
This action locks a lock with the given ID on when executed.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
on_...:
|
||||
then:
|
||||
- lock.lock: deadbolt_1
|
||||
|
||||
.. _lock-unlock_action:
|
||||
|
||||
``lock.unlock`` Action
|
||||
**************************
|
||||
|
||||
This action unlocks a lock with the given ID off when executed.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
on_...:
|
||||
then:
|
||||
- lock.unlock: deadbolt_1
|
||||
|
||||
.. _lock-open_action:
|
||||
|
||||
``lock.open`` Action
|
||||
************************
|
||||
|
||||
This action opens (e.g. unlatch) a lock with the given ID off when executed.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
on_...:
|
||||
then:
|
||||
- lock.open: doorlock_1
|
||||
|
||||
.. _lock-is_locked_condition:
|
||||
.. _lock-is_unlocked_condition:
|
||||
|
||||
``lock.is_locked`` / ``lock.is_unlocked`` Condition
|
||||
***************************************************
|
||||
|
||||
This :ref:`Condition <config-condition>` checks if the given lock is LOCKED (or UNLOCKED).
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# In some trigger:
|
||||
on_...:
|
||||
if:
|
||||
condition:
|
||||
# Same syntax for is_unlocked
|
||||
lock.is_locked: my_lock
|
||||
|
||||
.. _lock-lambda_calls:
|
||||
|
||||
lambda calls
|
||||
************
|
||||
|
||||
From :ref:`lambdas <config-lambda>`, you can call several methods on all locks to do some
|
||||
advanced stuff (see the full API Reference for more info).
|
||||
|
||||
- ``publish_state()``: Manually cause the lock to publish a new state and store it internally.
|
||||
If it's different from the last internal state, it's additionally published to the frontend.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
// Within lambda, make the lock report a specific state
|
||||
id(my_lock).publish_state(LOCK_STATE_LOCKED);
|
||||
id(my_lock).publish_state(LOCK_STATE_UNLOCKED);
|
||||
|
||||
- ``state``: Retrieve the current state of the lock.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
// Within lambda, get the lock state and conditionally do something
|
||||
if (id(my_lock).state == LOCK_STATE_LOCKED) {
|
||||
// Lock is LOCKED, do something here
|
||||
}
|
||||
|
||||
- ``unlock()``/``lock()``/``open()``: Manually lock/unlock/open a lock from code.
|
||||
Similar to the ``lock.lock``, ``lock.unlock``, and ``lock.open`` actions,
|
||||
but can be used in complex lambda expressions.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
id(my_lock).unlock();
|
||||
id(my_lock).lock();
|
||||
id(my_lock).open();
|
||||
|
||||
.. _lock-on_lock_unlock_trigger:
|
||||
|
||||
``lock.on_lock`` / ``lock.on_unlock`` Trigger
|
||||
****************************************************************
|
||||
|
||||
This trigger is activated each time the lock is locked/unlocked. It becomes active
|
||||
right after the lock component has acknowledged the state (e.g. after it LOCKED/UNLOCKED itself).
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
lock:
|
||||
- platform: template # or any other platform
|
||||
# ...
|
||||
on_lock:
|
||||
- logger.log: "Door Locked!"
|
||||
on_unlock:
|
||||
- logger.log: "Door Unlocked!"
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
- :apiref:`lock/lock.h`
|
||||
- :ghedit:`Edit`
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
*
|
|
@ -0,0 +1,39 @@
|
|||
Generic Output Lock
|
||||
=====================
|
||||
|
||||
.. seo::
|
||||
:description: Instructions for setting up generic output locks in ESPHome that control an output component.
|
||||
:image: upload.svg
|
||||
|
||||
The ``output`` lock platform allows you to use any output component as a lock.
|
||||
|
||||
.. figure:: images/output-ui.png
|
||||
:align: center
|
||||
:width: 80.0%
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# Example configuration entry
|
||||
output:
|
||||
- platform: gpio
|
||||
pin: 25
|
||||
id: 'generic_out'
|
||||
lock:
|
||||
- platform: output
|
||||
name: "Generic Output"
|
||||
output: 'generic_out'
|
||||
|
||||
Configuration variables:
|
||||
------------------------
|
||||
|
||||
- **output** (**Required**, :ref:`config-id`): The ID of the output component to use.
|
||||
- **name** (**Required**, string): The name for the lock.
|
||||
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
|
||||
- All other options from :ref:`Lock <config-lock>`.
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
- :doc:`/components/output/index`
|
||||
- :apiref:`output/lock/output_lock.h`
|
||||
- :ghedit:`Edit`
|
|
@ -0,0 +1,115 @@
|
|||
Template Lock
|
||||
===============
|
||||
|
||||
.. seo::
|
||||
:description: Instructions for setting up template locks that can execute arbitrary actions when locked, unlocked, or opened
|
||||
:image: description.svg
|
||||
|
||||
The ``template`` lock platform allows you to create simple locks out of just actions and
|
||||
an optional value lambda. Once defined, it will automatically appear in Home Assistant
|
||||
as a lock and can be controlled through the frontend.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# Example configuration entry
|
||||
lock:
|
||||
- platform: template
|
||||
name: "Template Lock"
|
||||
lambda: |-
|
||||
if (id(some_binary_sensor).state) {
|
||||
return LOCK_STATE_LOCKED;
|
||||
} else {
|
||||
return LOCK_STATE_UNLOCKED;
|
||||
}
|
||||
lock_action:
|
||||
- switch.turn_on: switch1
|
||||
unlock_action:
|
||||
- switch.turn_off: switch1
|
||||
open_action:
|
||||
- button.press: button1
|
||||
|
||||
|
||||
Possible return values for the optional lambda:
|
||||
|
||||
- ``return LOCK_STATE_LOCKED;`` if the lock should be reported as LOCKED.
|
||||
- ``return LOCK_STATE_UNLOCKED;`` if the lock should be reported as UNLOCKED.
|
||||
- ``return LOCK_STATE_JAMMED;`` if the lock should be reported as JAMMED.
|
||||
- ``return LOCK_STATE_LOCKING;`` if the lock should be reported as LOCKING.
|
||||
- ``return LOCK_STATE_UNLOCKING;`` if the lock should be reported as UNLOCKING.
|
||||
- ``return {};`` if the last state should be repeated.
|
||||
|
||||
.. note::
|
||||
|
||||
Only ``LOCK_STATE_LOCKED`` and ``LOCK_STATE_UNLOCKED`` are supported by the MQTT component in Home Assistant
|
||||
|
||||
Configuration variables:
|
||||
------------------------
|
||||
|
||||
- **name** (**Required**, string): The name of the lock.
|
||||
- **lambda** (*Optional*, :ref:`lambda <config-lambda>`):
|
||||
Lambda to be evaluated repeatedly to get the current state of the lock.
|
||||
- **lock_action** (*Optional*, :ref:`Action <config-action>`): The action that should
|
||||
be performed when the remote (like Home Assistant's frontend) requests the lock to be locked.
|
||||
- **unlock_action** (*Optional*, :ref:`Action <config-action>`): The action that should
|
||||
be performed when the remote (like Home Assistant's frontend) requests the lock to be unlocked.
|
||||
- **restore_state** (*Optional*, boolean): Sets whether ESPHome should attempt to restore the
|
||||
state on boot-up and call the lock/unlock actions with the recovered values. Defaults to ``no``.
|
||||
- **optimistic** (*Optional*, boolean): Whether to operate in optimistic mode - when in this mode,
|
||||
any command sent to the template lock will immediately update the reported state.
|
||||
Defaults to ``false``.
|
||||
- **assumed_state** (*Optional*, boolean): Whether the true state of the lock is not known.
|
||||
This will make the Home Assistant frontend show buttons for both LOCK and UNLOCK actions, instead
|
||||
of hiding one of them when the lock is LOCKED/UNLOCKED. Defaults to ``false``.
|
||||
- **id** (*Optional*, :ref:`config-id`): Manually specify the ID used for code generation.
|
||||
- All other options from :ref:`Lock <config-lock>`.
|
||||
|
||||
.. _lock-template-publish_action:
|
||||
|
||||
``lock.template.publish`` Action
|
||||
----------------------------------
|
||||
|
||||
You can also publish a state to a template lock from elsewhere in your YAML file
|
||||
with the ``lock.template.publish`` action.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# Example configuration entry
|
||||
lock:
|
||||
- platform: template
|
||||
name: "Template Lock"
|
||||
id: template_lock1
|
||||
|
||||
# in some trigger
|
||||
on_...:
|
||||
- lock.template.publish:
|
||||
id: template_lock1
|
||||
state: LOCK_STATE_LOCKED
|
||||
|
||||
# Templated
|
||||
- lock.template.publish:
|
||||
id: template_lock1
|
||||
state: !lambda 'return LOCK_STATE_LOCKED;'
|
||||
|
||||
Configuration options:
|
||||
|
||||
- **id** (**Required**, :ref:`config-id`): The ID of the template lock.
|
||||
- **state** (**Required**, boolean, :ref:`templatable <config-templatable>`):
|
||||
The state to publish.
|
||||
|
||||
.. note::
|
||||
|
||||
This action can also be written in lambdas, the parameter of the `publish_state` method denotes the state the
|
||||
lock should become:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
id(template_lock1).publish_state(lock::LOCK_STATE_LOCKED);
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
- :doc:`/guides/automations`
|
||||
- :doc:`/components/lock/index`
|
||||
- :doc:`/components/binary_sensor/index`
|
||||
- :apiref:`template/lock/template_lock.h`
|
||||
- :ghedit:`Edit`
|
|
@ -598,6 +598,15 @@ Select Components
|
|||
Select Core, components/select/index, folder-open.svg
|
||||
Template Select, components/select/template, description.svg
|
||||
|
||||
Lock Components
|
||||
-----------------
|
||||
|
||||
.. imgtable::
|
||||
|
||||
Lock Core, components/lock/index, folder-open.svg
|
||||
Generic Output Lock, components/lock/output, upload.svg
|
||||
Template Lock, components/lock/template, description.svg
|
||||
|
||||
Misc Components
|
||||
---------------
|
||||
|
||||
|
|
Loading…
Reference in New Issue