neilimixamo / Home-Assistant-Quick-Look-Mobile

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add Washing Machine Card

Michelone86 opened this issue Β· comments

Hi super @neilimixamo, I would like to use the EQUIPMENT tab to view the controls for my Washing Machine and Dryer (both Electrolux). Any ideas about it?

ps. I don't have other devices to insert in the EQUIPMENT tab, I just need to be able to control them. Thank you!

Hi @Michelone86, since I don't have connected washing and dryer machines myself, could you tell me more about the brands/products you use?

Could you also detail the functions you would like to integrate into the cards (e.g., start a wash, stop it, check the status or its power consumption, etc.) as well as the style you prefer?

If you only want to display two cards in your equipment view, we could quadruple the card size, which would allow for more controls or information to be displayed.

There is also a nice existing 'Custom-card Washer' proposed by UI-Minimalist, what do you think about it ? We could draw inspiration from it and create a QLM card that integrates seamlessly with the rest of its theme.

image

Feel free to provide sketches or drawings of what you envision for greater clarity."

Hello @neilimixamo, the card you proposed is really very interesting and would be great for me!
The controls I would like to see are quite simple: Current State (on/off), Wash Phase, Pause, Resume, Remaining Time. I currently use this Electrolux integration https://github.com/albaintor/homeassistant_electrolux_status.

Thank you so much for the time you dedicate to this great work, you are really good πŸ™πŸ˜Š

Hi @neilimixamo, unfortunately I don't have that much programming skills and I can't figure out how to make this card work with my Electrolux appliances and your qlm. Could you help me? πŸ™

If you only need to see the washing phase, remaining time, and control start/pause actions for the machine, we can maintain a minimalist approach by using the basic QLM card model as shown in this example.

image

The progress state will be represented by the appropriate icon for each cycle phase and a slider, while the remaining time will be displayed as a label when the machine is running and will show "paused" when the machine is paused. Does this card meet your requirements?

Since I don't have access to such a device, I need the following information:

  • Entity name of your washing machine
  • Screenshot of the "dev tool" window for this entity showing its attributes
  • Entity name for starting/pausing your washing machine
  • Entity name indicating the washing cycle
  • Entity name indicating the remaining time

Hi @neilimixamo , I'm so happy to hear that you can view and control my washing machine directly with your QLM! The example you showed me is really what I need!

Here is the data you requested:

Entity name: electrolux_lavatrice
Entity name for starting: button.electrolux_lavatrice_executecommand_start
Entity name for pausing: button.electrolux_lavatrice_executecommand_pause
Entity name for resume: button.electrolux_lavatrice_executecommand_resume
Entity name for washing cycle: sensor.electrolux_lavatrice_cyclephase and sensor.electrolux_lavatrice_cyclesubphase
Entity name for remaining time: sensor.electrolux_lavatrice_timetoend

I also attach all the screenshots from the developers section.
ps. I will also need to insert my dryer (electrolux), but I think I will be able to adapt your work without giving you further trouble ;)
Lavatrice_1
Lavatrice_2
Lavatrice_3
Lavatrice_4
Lavatrice_5
Lavatrice_6
Lavatrice_7
Lavatrice_8
Lavatrice_9

Ok, add first this new "appliance.yaml" beta template,

appliance (beta).yaml
appliance:
  template: 
    - basic_card
    - badge_battery
  variables:
    entity: 
    states: ["starting", "washing", "rinsing", "drying"] # add/adapt terms if needed
    label_on: 'Active'
    label_off: 'Inactive'
  styles:
    card:
      - background-color: |
          [[[
            if (variables.entity) {
              const state = states[variables.entity].state;
              if (state !== 'off' && state !== 'paused') {
                return 'var(--fan-background-active)';
              } else {
                return 'var(--fan-background-inactive)';
              }
            } else {
              return 'var(--fan-background-inactive)';
            }
          ]]]
  custom_fields:
    slider_visible:
      card:
        type: custom:my-slider-v2
        entity: |
            [[[
              if (variables.entity) {
                return variables.entity;
              } else {
                return '';
              }
            ]]]
        intermediate: true
        styles:
          card:
            - display: block
            - position: absolute
            - height: 100%
            - width: 100%
            - background-color: transparent
            - border-radius: 27px
            - box-shadow: none
            - bottom: 0vh
            - left: 0vw
          container:
            - width: 100%
            - height: 100%
            - position: absolute
            - bottom: 0vh
            - right: 0vw
            - overflow: hidden
            - border-radius: 27px
          track:
            - width: 100%
            - height: 100%
            - position: absolute
            - bottom: 0vh
            - right: 0vw
            - border-radius: 27px
            - background-color:  |
                [[[
                  if (variables.entity) {
                    var state = states[variables.entity].state.toLowerCase();
                    var percentage = states[variables.entity].attributes.percentage;

                    if (state === 'on') {
                      return 'var(--fan-slider-track-active)';
                    } 
                  }
                  return 'var(--fan-slider-track-inactive)';
                ]]]
          progress:
            - height: 100%
            - background:  |
                [[[
                  if (variables.entity) {
                    var state = states[variables.entity].state.toLowerCase();
                    var percentage = states[variables.entity].attributes.percentage;

                    if (variables.states.some(s => state.includes(s.toLowerCase()))) {
                      return 'var(--fan-background-active)';
                    } 
                  }
                  return 'var(--fan-background-inactive)';
                ]]]
            - border-radius: 27px 0px 0px 27px
          thumb:
            - width: 0px
    icon:
      card:
        icon: |
          [[[
            if (variables.entity) {
              const state = states[variables.entity].state.toLowerCase();
              if (state.includes('starting')) {
                return 'mdi:power';
              } else if (state.includes('washing')) {
                return 'mdi:water';
              } else if (state.includes('rinsing')) {
                return 'mdi:water-pump';
              } else if (state.includes('drying')) {
                return 'mdi:weather-sunny';
              } else if (state.includes('paused')) {
                return 'mdi:pause';
              } else {
                return 'mdi:washing-machine';
              }
            } else {
              return 'mdi:help';
            }
          ]]]
        styles:
          card:
            - overflow: visible
            - background-color:  |
                [[[
                  if (variables.entity) {
                    const state = states[variables.entity].state;
                    if (state !== 'off' && state !== 'paused') {
                      return 'var(--fan-icon-background-active)';
                    } else {
                      return 'var(--fan-icon-background-inactive)';
                    }
                  } else {
                    return 'var(--fan-icon-background-inactive)';
                  }
                ]]]
          icon:
            - color: 'var(--fan-icon-active)'
        tap_action:
          action: toggle
        hold_action: 
          action: none
        custom_fields:
          badge:
            type: custom:button-card # calls for the 'badge_battery' template
    name:
      card:
        label:  |
          [[[
            if (variables.entity) {
              if (states[variables.entity].state === "unavailable") {
                return 'Unavailable';
              }else if (states[variables.entity].state === "paused") {
                return 'In Pausa';
              } else {
                var state = states[variables.entity].state;
                if (state != 'off') {
                  return states[variables.label_on].state;
                } else {
                  return variables.label_off;
                }
              }
            } else if (variables.label) {
              return variables.label;
            } else {
              return 'Label';
            }
          ]]]

then create the following card and give me feedback.

- type: custom:button-card
  template: appliance
  variables:
    entity: sensor.electrolux_lavatrice_appliancestate
    name: Lavatrice
    label_on: sensor.electrolux_lavatrice_timetoend
    label_off: 'Disattivata' 
    icon_tap_action:
      action: call-service
      service: button.press
      service_data:
        entity_id: >
          [[[
            if (states[variables.entity].state.toLowerCase() === 'off') {
              return 'button.electrolux_lavatrice_executecommand_start';
            } else if (states[variables.entity].state.toLowerCase() === 'paused') {
              return 'button.electrolux_lavatrice_executecommand_resume';
            } else {
              return 'button.electrolux_lavatrice_executecommand_pause';
            }
          ]]]

Can you also list the possible states of the entities sensor.electrolux_lavatrice_appliancestate, sensor.electrolux_lavatrice_cyclephase and sensor.electrolux_lavatrice_cyclesubphasecycle ?

Which entity will indicate "washing", "rinsing", "drying", "paused" ?

Hi @neilimixamo, I created the board!
The minutes remaining for the end of the cycle are correct. If I click on the label, the sensor.electrolux_lavatrice_appliancestate entity screen opens. If I click on the icon instead, I get the error "Service sensor.turn_off not found".

123054_VRecorder.mp4

These are the states I was able to read since the last wash:

sensor.electrolux_lavatrice_appliancestate : off, running, end of cycle.
sensor.electrolux_lavatrice_cyclephase : unavailable, wash, rinse, spin.
sensor.electrolux_lavatrice_cyclesubphasecycle : not available, add garment, wash, rinse.

I await your instructions, thank you!

This new code should fix the action bug and should adapt the icon and label according to the progress state of the washing machine, while displaying the remaining time in minutes.

Can you make sure that the entity 'sensor.electrolux_lavatrice_appliancestate' also indicates a 'paused' state?

appliance (beta 2).yaml
appliance:
  template: 
    - basic_card
    - badge_battery
  variables:
    entity: 
    label_on: 'Attivata'
    label_off: 'Disattivata'
  styles:
    card:
      - background-color: |
          [[[
            if (variables.entity) {
              const state = states[variables.entity].state;
              if (state !== 'off' && state !== 'paused') {
                return 'var(--fan-background-active)';
              } else {
                return 'var(--fan-background-inactive)';
              }
            } else {
              return 'var(--fan-background-inactive)';
            }
          ]]]
  custom_fields:
    icon:
      card:
        icon:  |
          [[[
            const state = states[variables.entity].state.toLowerCase();
            const phase = states['sensor.electrolux_lavatrice_cyclephase'].state.toLowerCase();

            if (state === 'off') {
              return 'mdi:washing-machine-off';
            } else if (state === 'running') {
              if (phase === 'wash') {
                return 'mdi:water';
              } else if (phase === 'rinse') {
                return 'mdi:water-pump';
              } else if (phase === 'spin') {
                return 'mdi:turbine';
              } else {
                return 'mdi:washing-machine';
              }
            } else if (state === 'end of cycle') {
              return 'mdi:check';
            } else if (state === 'paused') {
              return 'mdi:pause';
            } else {
              return 'mdi:help';
            }
          ]]]
        styles:
          card:
            - overflow: visible
            - background-color: |
                [[[
                  if (variables.entity) {
                    const state = states[variables.entity].state;
                    if (state !== 'off' && state !== 'paused') {
                      return 'var(--fan-icon-background-active)';
                    } else {
                      return 'var(--fan-icon-background-inactive)';
                    }
                  } else {
                    return 'var(--fan-icon-background-inactive)';
                  }
                ]]]
          icon:
            - color: 'var(--fan-icon-active)'
        custom_fields:
          badge:
            type: custom:button-card
    name:
      card:
        label: |
          [[[
            const state = states[variables.entity].state.toLowerCase();
            const phase = states['sensor.electrolux_lavatrice_cyclephase'].state.toLowerCase();
            const timetoend = states['sensor.electrolux_lavatrice_timetoend'].state;

            if (state === 'off') {
              return variables.label_off;
            } else if (state === 'running') {
              if (phase === 'wash') {
                return 'Lavaggio - ' + timetoend + 'min';
              } else if (phase === 'rinse') {
                return 'Risciacquo - ' + timetoend + 'min';
              } else if (phase === 'spin') {
                return 'Asciugatura - ' + timetoend + 'min';
              } else {
                return variables.label_on + ' - ' + timetoend + 'min';
              }
            } else if (state === 'end of cycle') {
              return 'Finito';
            } else {
              return 'Label';
            }
          ]]]
- type: custom:button-card 
  template: appliance
  variables:
    entity: sensor.electrolux_lavatrice_appliancestate
    name: Lavatrice
    icon_tap_action:
      action: call-service
      service: button.press
      service_data:
        entity_id: >
          [[[
            const state = states[variables.entity].state.toLowerCase();
            const button = 'button.electrolux_lavatrice_executecommand_';

            if (state === 'off') {
              return button + 'start';
            } else if (state === 'paused') {
              return button + 'resume';
            } else {
              return button + 'pause';
            }
          ]]]

This template is currently not applicable to other devices but I'll try to make it more generic once we have finalized this card and as mentioned earlier, I cannot test this code myself, so I have to rely on your observations for the following improvements ;)

Hi @neilimixamo , the code works perfectly!
Is it possible to move the remaining minutes under the wash cycle name? Thank you!
Screenshot_20240519_172151
Screenshot_20240519_172157
Screenshot_20240519_172235

May I propose you one of the following alternatives instead of adding a third text line ?

image

Ok the last solution seems the best to me, it would be perfect! (Replace state by name)

Ok, let's try this then :

appliance (beta 3).yaml
appliance:
  template: 
    - basic_card
    - badge_battery
  variables:
    entity: 
    label_on: 'Attivata'
    label_off: 'Disattivata'
  styles:
    card:
      - background-color: |
          [[[
            if (variables.entity) {
              const state = states[variables.entity].state;
              if (state !== 'off' && state !== 'paused') {
                return 'var(--fan-background-active)';
              } else {
                return 'var(--fan-background-inactive)';
              }
            } else {
              return 'var(--fan-background-inactive)';
            }
          ]]]
  custom_fields:
    icon:
      card:
        icon:  |
          [[[
            const state = states[variables.entity].state.toLowerCase();
            const phase = states['sensor.electrolux_lavatrice_cyclephase'].state.toLowerCase();

            if (state === 'off') {
              return 'mdi:washing-machine-off';
            } else if (state === 'running') {
              if (phase === 'wash') {
                return 'mdi:hand-water';
              } else if (phase === 'rinse') {
                return 'mdi:water';
              } else if (phase === 'spin') {
                return 'mdi:turbine';
              } else {
                return 'mdi:washing-machine';
              }
            } else if (state === 'end of cycle') {
              return 'mdi:check';
            } else if (state === 'paused') {
              return 'mdi:pause';
            } else {
              return 'mdi:help';
            }
          ]]]
        styles:
          card:
            - overflow: visible
            - background-color: |
                [[[
                  if (variables.entity) {
                    const state = states[variables.entity].state;
                    if (state !== 'off' && state !== 'paused') {
                      return 'var(--fan-icon-background-active)';
                    } else {
                      return 'var(--fan-icon-background-inactive)';
                    }
                  } else {
                    return 'var(--fan-icon-background-inactive)';
                  }
                ]]]
          icon:
            - color: 'var(--fan-icon-active)'
        custom_fields:
          badge:
            type: custom:button-card
    name:
      card:
        name:  |
          [[[
            const state = states[variables.entity].state.toLowerCase();
            const phase = states['sensor.electrolux_lavatrice_cyclephase'].state.toLowerCase();

            if (state === 'off') {
              return variables.name;
            } else if (state === 'paused') {
              return 'In Pausa';
            } else if (state === 'running') {
              if (phase === 'wash') {
                return 'Lavaggio' ;
              } else if (phase === 'rinse') {
                return 'Risciacquo';
              } else if (phase === 'spin') {
                return 'Asciugatura';
              } else {
                return variables.name;
              }
            } else if (state === 'end of cycle') {
              return 'Finito';
            } else {
              return 'Name';
            }
          ]]]
        label: |
          [[[
            const state = states[variables.entity].state.toLowerCase();
            const timetoend = states['sensor.electrolux_lavatrice_timetoend'].state;

            if (state === 'off') {
              return variables.label_off;
            } else if (state === 'running') {
              return timetoend + ' min';
            } else if (state === 'end of cycle') {
              return 'Finito';
            } else {
              return variables.label_on ;
            }
          ]]]

Ok, I did a quick test and it seems to work well! In the next few days I'll do some complete tests and update you, I'll wait to close the topic.
Thanks Super @neilimixamo πŸš€πŸš€πŸš€

Hi @neilimixamo , I've been testing your card for a few days, everything works great and I even managed to get the dryer to work by adapting its entities based on your code!

Thank you so much, your QLM Dashboard is really perfect πŸ˜ŽπŸš€

Screenshot_20240521_122727

Hi @Michelone86, I'm glad to hear that you achieved the dashboard you wanted. However, I notice that the cards are blue even though their status is "Spenta", they should be greyded instead, no?

Hi @neilimixamo , yes the cards are Blue even if they are turned off... πŸ€”

Screenshot_20240521_133549

Is the state of your inactive washing machine "off" or "end of cycle" ? Cause every other states than "off" or "paused" will return a blue card

The state of the switched off washing machine is OFF.

Can you try to change 'OFF' to 'off' in the dev tools to see if it fixes the problem ?

If it does, replace lines 15 and 59 of the appliance template with

if (state.toLowerCase() !== 'off' && state.toLowerCase() !== 'paused') {

Hi @neilimixamo, I made the change you told me to do, now everything works correctly! Thank you ✌🏻
Screenshot_20240521_224257