HomeAutomation

Sleep automation with Mi Band and Home Assistant

Have you ever thought about using your Mi Band to help you automate stuff? Read this article and you will know how you can use your Mi Band sleep detection to trigger automations in your Home Assistant!

You will need:

  • Tasker
  • Notify & Fitness for Mi Band
  • A Mi Band
  • An automation which creates a webhook and then publishes your sleep status on a MQTT topic
  • A sensor based on the MQTT topic previously defined (it can be avoided for the purpose of this automation but I prefer to have it to keep track of the sleep status)
  • Tasker profiles which react to your sleep status

Create a sleep_status.yaml file in your packages directory with the following content.

Replace [YOUR_NAME] and [YOUR_WEBHOOK_ID].

homeassistant:

automation:
  - alias: [YOUR_NAME] Sleep Status
    initial_state: true
    trigger:
      - platform: webhook
      webhook_id: [YOUR_WEBHOOK_ID]
      condition: []
    action:
      - service: mqtt.publish
        data_template: 
          payload: "{\"sleepStatus\": \"{{trigger.json.sleepStatus}}\"}"
          topic: "sleepstatus/[YOUR_NAME]"

sensor:
  - platform: mqtt
    name: "[YOUR_NAME] Sleep Status"
    state_topic: "sleepstatus/[YOUR_NAME]"
    value_template: '{{ value_json["sleepStatus"] }}'

Take now your phone, open Tasker and setup the following profiles which will send your sleep status events (when you wake up and fall asleep) to your Home Assistant.

Replace [YOUR_HOME_ASSISTANT_DOMAIN_NAME] and [YOUR_WEBHOOK_ID]

<TaskerData sr="" dvi="1" tv="5.9.rc">
<Profile sr="prof61" ve="2">
<cdate>1574199690392</cdate>
<clp>true</clp>
<edate>1574529186304</edate>
<flags>8</flags>
<id>61</id>
<mid0>63</mid0>
<nme>MiBand Fell Asleep</nme>
<Event sr="con0" ve="2">
<code>599</code>
<Str sr="arg0" ve="3">com.mc.miband.tasker.fellAsleep</Str>
<Int sr="arg1" val="0"/>
<Int sr="arg2" val="0"/>
<Str sr="arg3" ve="3"/>
<Str sr="arg4" ve="3"/>
</Event>
</Profile>
<Task sr="task63">
<cdate>1574199712492</cdate>
<edate>1574529096550</edate>
<id>63</id>
<nme>Send Asleep Payload</nme>
<pri>6</pri>
<Action sr="act0" ve="7">
<code>339</code>
<Bundle sr="arg0">
<Vals sr="val">
<net.dinglisch.android.tasker.RELEVANT_VARIABLES><StringArray sr=""><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0>%http_data
Data
Data that the server responded from the HTTP request.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1>%http_file_output
File Output
Will always contain the file's full path even if you specified a directory as the File to save.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2>%http_response_code
Response Code
The HTTP Code the server responded</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3>%http_headers()
Response Headers
The HTTP Headers the server sent in the response. Each header is in the 'key:value' format</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4>%http_response_length
Response Length
The size of the response in bytes</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4></StringArray></net.dinglisch.android.tasker.RELEVANT_VARIABLES>
<net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
</Vals>
</Bundle>
<Int sr="arg1" val="1"/>
<Str sr="arg2" ve="3">https://[YOUR_HOME_ASSISTANT_DOMAIN_NAME]/api/webhook/[YOUR_WEBHOOK_ID]y</Str>
<Str sr="arg3" ve="3">Content-Type:application/json</Str>
<Str sr="arg4" ve="3"/>
<Str sr="arg5" ve="3">{ "sleepStatus" : "Asleep" }</Str>
<Str sr="arg6" ve="3"/>
<Str sr="arg7" ve="3"/>
<Int sr="arg8" val="30"/>
<Int sr="arg9" val="0"/>
</Action>
</Task>
</TaskerData>
<TaskerData sr="" dvi="1" tv="5.9.rc">
<Profile sr="prof66" ve="2">
<cdate>1574200024388</cdate>
<edate>1574529166065</edate>
<flags>8</flags>
<id>66</id>
<mid0>65</mid0>
<nme>MiBand WokeUp</nme>
<Event sr="con0" ve="2">
<code>599</code>
<Str sr="arg0" ve="3">com.mc.miband.tasker.wokeUp</Str>
<Int sr="arg1" val="0"/>
<Int sr="arg2" val="0"/>
<Str sr="arg3" ve="3"/>
<Str sr="arg4" ve="3"/>
</Event>
</Profile>
<Task sr="task65">
<cdate>1574199886906</cdate>
<edate>1574529090100</edate>
<id>65</id>
<nme>Send Awake Payload</nme>
<pri>6</pri>
<Action sr="act0" ve="7">
<code>339</code>
<Bundle sr="arg0">
<Vals sr="val">
<net.dinglisch.android.tasker.RELEVANT_VARIABLES><StringArray sr=""><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0>%http_data
Data
Data that the server responded from the HTTP request.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1>%http_file_output
File Output
Will always contain the file's full path even if you specified a directory as the File to save.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2>%http_response_code
Response Code
The HTTP Code the server responded</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3>%http_headers()
Response Headers
The HTTP Headers the server sent in the response. Each header is in the 'key:value' format</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4>%http_response_length
Response Length
The size of the response in bytes</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4></StringArray></net.dinglisch.android.tasker.RELEVANT_VARIABLES>
<net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
</Vals>
</Bundle>
<Int sr="arg1" val="1"/>
<Str sr="arg2" ve="3">https://[YOUR_HOME_ASSISTANT_DOMAIN_NAME]/api/webhook/[YOUR_WEBHOOK_ID]</Str>
<Str sr="arg3" ve="3">Content-Type:application/json</Str>
<Str sr="arg4" ve="3"/>
<Str sr="arg5" ve="3">{ "sleepStatus" : "Awake" }</Str>
<Str sr="arg6" ve="3"/>
<Str sr="arg7" ve="3"/>
<Int sr="arg8" val="30"/>
<Int sr="arg9" val="0"/>
</Action>
</Task>
</TaskerData>

Now, you can create a Node-RED flow which reacts to sensor.[YOUR_NAME]_sleep_status with Awake/Asleep value and executes your preferred stuff (e.g. turn off all the lights as soon as you fall asleep).

I have used my Mi Band 2 and it detects my sleep activity usually within 5 minutes so don’t use it for real-time automations.

Edit: it works on my Mi Band 4, too!

Let me know what automations you are going to create!

HomeAutomation

Light control using an Aqara Wireless Remote Switch with Home Assistant and Node-RED

Home automation is nice, sending commands with your voice is very cool, but sometimes, especially at night, you might want to just use traditional methods like a wall switch. You can do it very easily without compromising your cool voice controls, and you can also customize the way it works to suit your needs!

smart_light

Let’s start, this is what you need:

The Aqara Wireless Remote Switch is a switch you can place anywhere because it doesn’t need any wire, it works with a small battery which will last a very long time. To make the following configuration you need to use the double rocker one: it has a lot of different click interactions you can customize.

This is the configuration you can have using the Node-RED flow shown in this post:

  • Left click: turn on/off the lights (toggle)
  • Right click: switch between a predefined set of colors
  • Left long click: decrease brightness
  • Right long click: increase brightness

This Node-RED flow should be pretty straightforward, anyway, let’s have some more details.

The first node is an event state node which reacts to the changes of the Aqara Switch: it can have different values like left, right, left_long, right_long, etc. Home Assistant detects it as a sensor.

The second node is a switch used to take different actions based on the type of click done on the Aqara switch.

Left Click scenario

A service call to toggle the lights, very easy!

Right Click scenario

This is the most interesting part, the state machine node! It is used to create a finite state machine of the colors and transitions you want the lights to have. Unfortunately, I couldn’t find a way to make the state machine move to the next state based only on the current one, without taking care of the input value, so I managed to achieve it with a workaround!

After the state machine node there is a switch node which checks if the state machine already did its transition checking a value which is put into the payload object in the following function node. This function node also put the current state machine value as payload of the message, so the state machine can correctly move. In doing so, the second time the flow will go to another function node which generates the light settings based on the value the state machine now has. Then, the last node simply sets this configuration to the lights.

Left long click scenario

A current state node which gets the current brightness of the light followed by a function node which calculates the increased brightness value. In the function node you can customize the brightness interval and the minimum brightness you would like to reach. The last node is a service call which sets the new brightness to the light.

Right long click scenario

Same as the left long click scenario but this time the light brightness is increased, so the function node calculates the increased value the light will have. Customization for brightness interval and maximum brightness are available here as well.

Enjoy your wireless switch!

smart switch node red flow
[
    {
        "id": "63038ca5.043d84",
        "type": "server-state-changed",
        "z": "ebd2b060.28c27",
        "name": "SmartSwitch1",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "entityidfilter": "sensor.smartswitch1_click",
        "entityidfiltertype": "exact",
        "outputinitially": false,
        "state_type": "str",
        "haltifstate": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "outputs": 1,
        "output_only_on_state_change": true,
        "x": 110,
        "y": 160,
        "wires": [
            [
                "e4f033f6.02139"
            ]
        ]
    },
    {
        "id": "e4f033f6.02139",
        "type": "switch",
        "z": "ebd2b060.28c27",
        "name": "click is",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "left",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "right",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "left_long",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "right_long",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 4,
        "x": 290,
        "y": 160,
        "wires": [
            [
                "c92d98e8.8de858"
            ],
            [
                "bbcdb78.3712348"
            ],
            [
                "752f2116.e5b66"
            ],
            [
                "a10c6932.4ae3a8"
            ]
        ]
    },
    {
        "id": "c92d98e8.8de858",
        "type": "api-call-service",
        "z": "ebd2b060.28c27",
        "name": "toggle living room lights",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "service_domain": "light",
        "service": "toggle",
        "entityId": "group.living_room_lights",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 530,
        "y": 60,
        "wires": [
            []
        ]
    },
    {
        "id": "515a6b84.e21c54",
        "type": "api-call-service",
        "z": "ebd2b060.28c27",
        "name": "set brightness to living room lights",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "service_domain": "light",
        "service": "turn_on",
        "entityId": "group.living_room_lights",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 1200,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "752f2116.e5b66",
        "type": "api-current-state",
        "z": "ebd2b060.28c27",
        "name": "",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "override_topic": false,
        "entity_id": "light.left_living_room",
        "state_type": "str",
        "state_location": "data",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "blockInputOverrides": false,
        "x": 560,
        "y": 200,
        "wires": [
            [
                "91a6b4b5.c19558"
            ]
        ]
    },
    {
        "id": "ba574398.cc6a",
        "type": "function",
        "z": "ebd2b060.28c27",
        "name": "calculate increased brightness",
        "func": "const BRIGHTNESS_INTERVAL = 100;\nconst BRIGHTNESS_MAX = 255;\n\nlet brightness = msg.data.attributes.brightness;\nlet entityId = msg.data.entity_id;\nbrightness = brightness + BRIGHTNESS_INTERVAL\nif (brightness > BRIGHTNESS_MAX) {\n    brightness = BRIGHTNESS_MAX\n}\nmsg.payload = {\n    \"data\": {\n        \"brightness\":brightness\n        \n    }\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 870,
        "y": 260,
        "wires": [
            [
                "515a6b84.e21c54"
            ]
        ]
    },
    {
        "id": "91a6b4b5.c19558",
        "type": "function",
        "z": "ebd2b060.28c27",
        "name": "calculate decreased brightness",
        "func": "const BRIGHTNESS_INTERVAL = 100;\nconst BRIGHTNESS_MIN = 1;\n\nlet brightness = msg.data.attributes.brightness;\nlet entityId = msg.data.entity_id;\nbrightness = brightness - BRIGHTNESS_INTERVAL\nif (brightness < BRIGHTNESS_MIN) {\n    brightness = BRIGHTNESS_MIN\n}\nmsg.payload = {\n    \"data\": {\n        \"brightness\":brightness\n        \n    }\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 870,
        "y": 200,
        "wires": [
            [
                "515a6b84.e21c54"
            ]
        ]
    },
    {
        "id": "a10c6932.4ae3a8",
        "type": "api-current-state",
        "z": "ebd2b060.28c27",
        "name": "",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "override_topic": false,
        "entity_id": "light.left_living_room",
        "state_type": "str",
        "state_location": "data",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "blockInputOverrides": false,
        "x": 560,
        "y": 260,
        "wires": [
            [
                "ba574398.cc6a"
            ]
        ]
    },
    {
        "id": "bbcdb78.3712348",
        "type": "state-machine",
        "z": "ebd2b060.28c27",
        "name": "bulb color state",
        "triggerProperty": "payload",
        "triggerPropertyType": "msg",
        "stateProperty": "topic",
        "statePropertyType": "msg",
        "outputStateChangeOnly": false,
        "throwException": false,
        "states": [
            "white",
            "yellow"
        ],
        "transitions": [
            {
                "name": "white",
                "from": "white",
                "to": "yellow"
            },
            {
                "name": "yellow",
                "from": "yellow",
                "to": "white"
            }
        ],
        "x": 540,
        "y": 140,
        "wires": [
            [
                "10c6a2fd.57b7dd"
            ]
        ]
    },
    {
        "id": "f1efc70d.5de9e8",
        "type": "api-call-service",
        "z": "ebd2b060.28c27",
        "name": "set color to living room lights",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "service_domain": "light",
        "service": "turn_on",
        "entityId": "group.living_room_lights",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 1340,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "10c6a2fd.57b7dd",
        "type": "switch",
        "z": "ebd2b060.28c27",
        "name": "has state ben processed",
        "property": "data.attributes.state_machine_processed",
        "propertyType": "msg",
        "rules": [
            {
                "t": "true"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 770,
        "y": 140,
        "wires": [
            [
                "41cecb0c.bbfbe4"
            ],
            [
                "f4044d2d.8f0a6"
            ]
        ]
    },
    {
        "id": "f4044d2d.8f0a6",
        "type": "function",
        "z": "ebd2b060.28c27",
        "name": "set topic as payload",
        "func": "msg.payload = msg.topic\nmsg.data = {\n    \"attributes\": {\n        \"state_machine_processed\" : true\n    }\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 1040,
        "y": 140,
        "wires": [
            [
                "bbcdb78.3712348"
            ]
        ]
    },
    {
        "id": "41cecb0c.bbfbe4",
        "type": "function",
        "z": "ebd2b060.28c27",
        "name": "create color data as payload",
        "func": "whitePayload = {\n    \"data\": {\n        \"kelvin\": 6000\n    }\n}\n\nyellowPayload = {\n    \"data\": {\n        \"kelvin\": 3000\n    }\n}\n\nlet payloadToUse;\nswitch(msg.topic) {\n    case \"white\": \n        payloadToUse = whitePayload;\n        break;\n    case \"yellow\":\n        payloadToUse = yellowPayload;\n        break;\n    default:\n    \n        break;\n}\n\nmsg.payload = payloadToUse;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 1060,
        "y": 100,
        "wires": [
            [
                "f1efc70d.5de9e8"
            ]
        ]
    },
    {
        "id": "b0807fb3.bda6d",
        "type": "server",
        "z": "",
        "name": "Home Assistant"
    }
]
HomeAutomation

Make your washing machine smarter with Home Assistant

washing machines

Do you own an old washing machine and do you ever think about how to make your washing machine smarter? Would you have better information about the current washing cycle?

Well, you can do it today, you just need a couple of things:

  • a smart plug with energy monitoring feature
  • an Home Assistant instance running somewhere (e.g. on your raspberry)

SMART PLUG INTEGRATION

Each smart plug is different from another so you have to figure out how to integrate your into Home Assistant.

In my setup I used a TP-Link HS-110 and I integrated it into my Home Assistant as shown here.

After the successful integration you should have a new sensor which reports the current consumption, and this is the one you are going to use to monitor your washing machine!

WASHING MACHINE INTEGRATION

The following snippet of code is an Home Assistant package, you can just copy-paste it into a whatever_you_want.yaml file into your packages directory; the only thing you need to do is to replace PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE with your current consumption sensor name!

homeassistant:
  customize_glob:
    "*washing_machine*":
      icon: mdi:washing-machine

input_text:
  washing_machine_enriched_status:
    name: 'Washing Machine Enriched Status'
    initial: Off
    
sensor:
  - platform: template
    sensors:
      washing_machine_status:
        friendly_name: 'Washing Machine Status'
        value_template:  >
          {% if states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float > 3.0 %}
            {{ "Running" }}
          {% elif states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float > 1 %}
            {{ "On" }}
          {% else %}
            {{ "Off" }}
          {% endif %}
      washing_machine_info:
        friendly_name: 'Washing Machine Info'
        entity_id: sensor.time
        value_template:  >
          {%- macro as_formatted_elapsed_time(now, other_date_time) %}
          {% set duration = as_timestamp(now) - as_timestamp(other_date_time) %}
          {% set seconds = (duration % 60) | int %}
          {% set minutes = ((duration / 60) | int) % 60 %}
          {% set hours = (duration / 3600) | int %}
          {{ [hours, "hours", minutes, "minutes", seconds, "seconds"] | join(' ') }}
          {%- endmacro %}
          {% if states.input_text.washing_machine_enriched_status.state == "Running" %}
            Washing machine running for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == "On_After_Running" %}
            Clothes left in the washing machine for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == "On_After_Off" %}
            {{ "Washing machine ready to start" }}
          {% else %}
            {{ "Washing machine is off" }}
          {% endif %}

automation:
  - id: '1556314846684'
    alias: Refresh Washing Machine Info
    initial_state: true
    trigger:
      platform: state
      entity_id: sensor.washing_machine_status
    action:
      - service: input_text.set_value
        data_template:
          entity_id: input_text.washing_machine_enriched_status
          value:  >
            {% if trigger.to_state.state == "Running" %}
              {{ "Running" }}
            {% elif trigger.to_state.state == "On" and trigger.from_state.state == "Running" %}
              {{ "On_After_Running" }}
            {% elif trigger.to_state.state == "On" and trigger.from_state.state == "Off" %}
              {{ "On_After_Off" }}
            {% else %}
              {{ "Off" }}
            {% endif %}

DETAILED EXPLANATION

Let me explain a bit about what this package does:

Here we are just telling Home Assistant to use a washing machine icon for all the sensors containing the string washing_machine

homeassistant:
  customize_glob:
    "*washing_machine*":
      icon: mdi:washing-machine

Here we declare a sensor, washing_machine_status, to keep track of the status of the washing machine. There are three possible status: Running, On and Off. You would need to tune the watts value according to your washing machine to detect when it’s running or just turned on.

washing_machine_status:
        friendly_name: 'Washing Machine Status'
        value_template:  >
          {% if states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float > 3.0 %}
            {{ "Running" }}
          {% elif states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float > 1 %}
            {{ "On" }}
          {% else %}
            {{ "Off" }}
          {% endif %}

Here we setup an automation to store in an input_text (couldn’t find a better way) the enriched status of our washing machine: the enriched status is a status which keeps track of the previous one. So for example, if the washing machine was off and you turn it on, the status would be On, but the enriched status would be On_After_Off

automation:
  - id: '1556314846684'
    alias: Refresh Washing Machine Info
    initial_state: true
    trigger:
      platform: state
      entity_id: sensor.washing_machine_status
    action:
      - service: input_text.set_value
        data_template:
          entity_id: input_text.washing_machine_enriched_status
          value:  >
            {% if trigger.to_state.state == "Running" %}
              {{ "Running" }}
            {% elif trigger.to_state.state == "On" and trigger.from_state.state == "Running" %}
              {{ "On_After_Running" }}
            {% elif trigger.to_state.state == "On" and trigger.from_state.state == "Off" %}
              {{ "On_After_Off" }}
            {% else %}
              {{ "Off" }}
            {% endif %}

This is just the simple declaration of the variable holding the enriched status of the washing machine.

input_text:
  washing_machine_enriched_status:
    name: 'Washing Machine Enriched Status'
    initial: Off

Here we declare another sensor, washing_machine_info, which will keep us informed of the washing machine’s current status and will also tell us more information (e.g. for how long the washing machine is running). The sensor will be updated every minute since it has a dependency on sensor.time: you can customize this behaviour adding another automation to refresh it faster or slower, but I think every minute is enough. as_formatted_elapsed_time is a macro used to display the elapsed time between two moments.

     washing_machine_info:
        friendly_name: 'Washing Machine Info'
        entity_id: sensor.time
        value_template:  >
          {%- macro as_formatted_elapsed_time(now, other_date_time) %}
          {% set duration = as_timestamp(now) - as_timestamp(other_date_time) %}
          {% set seconds = (duration % 60) | int %}
          {% set minutes = ((duration / 60) | int) % 60 %}
          {% set hours = (duration / 3600) | int %}
          {{ [hours, "hours", minutes, "minutes", seconds, "seconds"] | join(' ') }}
          {%- endmacro %}
          {% if states.input_text.washing_machine_enriched_status.state == "Running" %}
            Washing machine running for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == "On_After_Running" %}
            Clothes left in the washing machine for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == "On_After_Off" %}
            {{ "Washing machine ready to start" }}
          {% else %}
            {{ "Washing machine is off" }}
          {% endif %}

Finally, you can just put your new sensor washing_machine_info into your UI and you will get very useful information about your washing machine, making it actually smarter.

Furthermore, you can use the statuses we collect here, and their time-based changes, to make other automations like send you a Telegram notification when the washing machine cycle ends, but at the moment that’s left to your imagination!