diff --git a/_static/webserver-v1.js b/_static/webserver-v1.js
index 1f66a04a9..f82011ae5 100644
--- a/_static/webserver-v1.js
+++ b/_static/webserver-v1.js
@@ -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 += '' + e.data.substr(7, e.data.length - 10) + "\n";
+ log.innerHTML += '' + e.data.substr(7, e.data.length - 11) + "\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'+t.data.substr(7,t.data.length-10)+"\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+=''+t.data.substr(7,t.data.length-11)+"\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`): An automation to perform
+ when the lock is locked. See :ref:`lock-on_lock_unlock_trigger`.
+- **on_unlock** (*Optional*, :ref:`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 `.
+
+.. _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 ` 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 `, 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:
+
+ *
diff --git a/components/lock/output.rst b/components/lock/output.rst
new file mode 100644
index 000000000..282763eb7
--- /dev/null
+++ b/components/lock/output.rst
@@ -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 `.
+
+See Also
+--------
+
+- :doc:`/components/output/index`
+- :apiref:`output/lock/output_lock.h`
+- :ghedit:`Edit`
diff --git a/components/lock/template.rst b/components/lock/template.rst
new file mode 100644
index 000000000..da81965c1
--- /dev/null
+++ b/components/lock/template.rst
@@ -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 `):
+ Lambda to be evaluated repeatedly to get the current state of the lock.
+- **lock_action** (*Optional*, :ref:`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 `): 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 `.
+
+.. _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 `):
+ 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`
diff --git a/index.rst b/index.rst
index 2a2848dd3..7d3ced8d9 100644
--- a/index.rst
+++ b/index.rst
@@ -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
---------------