A self-contained, phone-optimised widget for LG washing machines exposed through the LG ThinQ binding (lgthinq). Pure SVG, dark navy theme; everything animates while a cycle runs — a rotating progress ring, a live drum with three paddles, water droplets, and a 5-stage progress bar (Weigh → Wash → Rinse → Spin → Done). A single f7-card, no external images or fonts. Built for a phone in a single column (portrait viewBox 720×1060, scales to container width).
What it shows
- Header status pill (top-right): ON / STANDBY / OFF, current phase, time remaining
- Central ring: rotates while running, amber on pause, full green when finished; large remaining-time readout (H:MM) in the middle
- Drawn washer with a spinning drum (3 paddles), display LED and water droplets — animated only while a cycle is active
- Estimated finish time (“done at ~HH:MM”, via
dayjs) - Stage bar: Weigh → Wash → Rinse → Spin → Done; completed steps turn green, the active one pulses
- Six info tiles: Program/Course, Temperature, Spin speed, Rinse, Door lock, Remote start
Raw ThinQ enum values (e.g. TURBO59, TEMP_40, SPIN_1200, DOOR_LOCK_ON) are mapped to friendly labels inside the widget.
Requirements
- openHAB 5.x (Main UI), tested on 5.2. Likely works on 4.x but untested — if you confirm it, the title range can be widened to
[4.0.0.0;.... - LG ThinQ binding with a
washer-201(or compatible) thing online. - Channels linked to items (all props optional — leave one blank to hide that piece):
| Prop | Channel (typical) | Item type |
|---|---|---|
itemPower |
dashboard#power |
Switch |
itemStandby |
dashboard#stand-by |
Switch |
itemState |
dashboard#state |
String |
itemProcess |
dashboard#process-state |
String |
itemCourse |
dashboard#course |
String |
itemTemp |
dashboard#temperature-level |
String |
itemSpin |
dashboard#spin |
String |
itemRinse |
dashboard#rinse |
String |
itemRemaining |
dashboard#remain-time |
String (H:MM) |
itemDoor |
dashboard#door-lock |
String (DOOR_LOCK_ON/OFF or 1/0) |
itemRemoteStart |
dashboard#rs-flag |
Switch |
stateis the coarse washer state;process-stateis the detailed phase. The widget prefersprocess-statefor the ring / stage bar / drum and falls back tostatewhenprocess-stateisn’t reporting a known phase — link both for the best result.
Adapting value maps to your model: the friendly-text maps cover common LG course/temperature/spin/rinse enums. If your model reports a course that isn’t mapped, the tile shows —; add your value to the object in the PROGRAM text element (same pattern for temperature/spin/rinse). Find your model’s exact values via the item’s State Description in the Developer Sidebar, or the event log while you change settings on the machine.
Screenshots
Changelog
Version 0.1
- initial release: animated ring + drawn washer with spinning drum, 5-stage bar, six info tiles, friendly value maps,
process-state→statefallback, door lock accepts bothDOOR_LOCK_ON/OFFand1/0
Resources
uid: washer_lg_card_mobile
tags:
- washer
- mobile
- LG
props:
parameters:
- context: item
description: Switch item for washer power on/off
label: Power (Switch)
name: itemPower
required: false
type: TEXT
- context: item
description: Switch item for standby mode
label: Standby mode (Switch)
name: itemStandby
required: false
type: TEXT
- context: item
description: 'String item: overall washer state (RUNNING, END, PAUSE, ...)'
label: Washer State (String)
name: itemState
required: false
type: TEXT
- context: item
description: 'String item: detailed cycle phase (DETECTING, RINSING, SPINNING, ...). Drives the ring, stage bar and drum.'
label: Process State / phase (String)
name: itemProcess
required: false
type: TEXT
- context: item
description: 'String item: selected program / course'
label: Course / program (String)
name: itemCourse
required: false
type: TEXT
- context: item
description: 'String item: temperature level (TEMP_40, TEMP_COLD, ...)'
label: Temperature level (String)
name: itemTemp
required: false
type: TEXT
- context: item
description: 'String item: spin speed (SPIN_1200, NO_SPIN, ...)'
label: Spin (String)
name: itemSpin
required: false
type: TEXT
- context: item
description: 'String item: rinse option (RINSE_NORMAL, RINSE_PLUS, ...)'
label: Rinse (String)
name: itemRinse
required: false
type: TEXT
- context: item
description: 'String item: remaining time, formatted H:MM'
label: Remaining time (String)
name: itemRemaining
required: false
type: TEXT
- context: item
description: 'String item: door lock (accepts DOOR_LOCK_ON/OFF or 1/0)'
label: Door lock (String)
name: itemDoor
required: false
type: TEXT
- context: item
description: 'Switch item: remote start enabled'
label: Remote start (Switch)
name: itemRemoteStart
required: false
type: TEXT
component: f7-card
config:
style:
background: '#0b111a'
border-radius: 28px
box-shadow: 0 6px 24px rgba(0,0,0,0.45)
margin: 8px
overflow: hidden
padding: '0'
position: relative
stylesheet: |-
.wm-pulse { animation: wm-pulse 2s ease-in-out infinite; }
@keyframes wm-pulse { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }
.wm-ring { transform-box: fill-box; transform-origin: center; animation: wm-spin 2.4s linear infinite; }
.wm-drum { transform-box: fill-box; transform-origin: center; animation: wm-spin 1.7s linear infinite; }
@keyframes wm-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
slots:
default:
- component: svg
config:
style:
display: block
viewBox: 0 0 720 1060
width: 100%
xmlns: http://www.w3.org/2000/svg
slots:
default:
- component: rect
config:
fill: '#0b111a'
height: '1060'
width: '720'
x: '0'
y: '0'
- component: circle
config:
cx: '360'
cy: '400'
fill: none
r: '225'
stroke: '#16222f'
stroke-width: '1.5'
- component: circle
config:
cx: '360'
cy: '400'
fill: none
r: '250'
stroke: '#14202c'
stroke-dasharray: 4 10
stroke-width: '1.5'
- component: rect
config:
fill: rgba(255,255,255,0.04)
height: '44'
rx: '22'
stroke: rgba(255,255,255,0.1)
width: '290'
x: '40'
y: '28'
- component: circle
config:
class: wm-pulse
cx: '64'
cy: '50'
fill: "=(items[props.itemPower].state === 'ON') ? '#37d67a' : ((items[props.itemStandby].state === 'ON') ? '#ffd54f' : '#8a97a3')"
r: '5'
- component: text
config:
content: "=(items[props.itemPower].state === 'ON') ? 'WASHER ON' : ((items[props.itemStandby].state === 'ON') ? 'STANDBY' : 'WASHER OFF')"
fill: "=(items[props.itemPower].state === 'ON') ? '#37d67a' : ((items[props.itemStandby].state === 'ON') ? '#ffd54f' : '#9fb0bd')"
font-size: '18'
font-weight: '600'
letter-spacing: '1'
x: '80'
y: '57'
- component: text
config:
content: =dayjs().format('DD.MM.YYYY')
fill: '#dbe3ea'
font-size: '18'
font-weight: '600'
text-anchor: end
x: '680'
y: '57'
- component: text
config:
content: LG WASHER
fill: '#ffffff'
font-size: '34'
font-weight: '800'
letter-spacing: '2'
x: '40'
y: '116'
- component: text
config:
content: WASHING MACHINE · LG THINQ
fill: '#9fb0bd'
font-size: '17'
letter-spacing: '3'
x: '40'
y: '144'
- component: rect
config:
fill: rgba(255,255,255,0.03)
height: '64'
rx: '18'
stroke: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? 'rgba(53,194,245,0.5)' : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'PAUSE' || [(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'RINSEHOLD') ? 'rgba(255,213,79,0.5)' : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'END') ? 'rgba(55,214,122,0.5)' : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'ERROR') ? 'rgba(255,112,67,0.5)' : 'rgba(159,176,189,0.3)')))"
width: '268'
x: '412'
y: '84'
- component: text
config:
content: "=({'DETECTING':'⚖️','WASHING':'🌀','RUNNING':'🌀','RINSING':'💧','SPINNING':'🌀','DRYING':'🔥','STEAMSOFTENING':'♨️','WASH_REFRESHING':'✨','COOLDOWN':'❄️','PAUSE':'⏸','RINSEHOLD':'⏸','END':'✅','ERROR':'⚠️','RESERVED':'🕐','INITIAL':'🟢'})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state] || '🌙'"
font-size: '20'
x: '434'
y: '124'
- component: text
config:
content: "=({'DETECTING':'WEIGHING','WASHING':'WASHING','RUNNING':'WASHING','RINSING':'RINSING','SPINNING':'SPINNING','DRYING':'DRYING','STEAMSOFTENING':'STEAM','WASH_REFRESHING':'REFRESHING','COOLDOWN':'COOLDOWN','PAUSE':'PAUSED','RINSEHOLD':'RINSE HOLD','END':'CYCLE DONE','ERROR':'ERROR','RESERVED':'SCHEDULED','INITIAL':'READY'})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state] || 'OFF'"
fill: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? '#35c2f5' : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'PAUSE' || [(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'RINSEHOLD') ? '#ffd54f' : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'END') ? '#37d67a' : '#8a97a3'))"
font-size: '19'
font-weight: '800'
x: '470'
y: '114'
- component: text
config:
content: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0 && items[props.itemRemaining].state && items[props.itemRemaining].state !== 'NULL') ? ('⏱ ' + items[props.itemRemaining].state + ' left') : 'no active cycle'"
fill: '#9fb0bd'
font-size: '14'
x: '470'
y: '138'
- component: circle
config:
cx: '360'
cy: '400'
fill: none
r: '190'
stroke: '#16222f'
stroke-width: '18'
- component: g
config:
transform: rotate(-90 360 400)
slots:
default:
- component: circle
config:
class: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? 'wm-ring' : ''"
cx: '360'
cy: '400'
fill: none
opacity: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? '1' : '0.25'"
r: '190'
stroke: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? '#35c2f5' : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'PAUSE' || [(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'RINSEHOLD') ? '#ffd54f' : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'END') ? '#37d67a' : '#2a3644'))"
stroke-dasharray: "=([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'END') ? '1194 1' : '420 774'"
stroke-linecap: round
stroke-width: '18'
- component: text
config:
content: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? 'TIME LEFT' : 'STATUS'"
fill: '#9fb0bd'
font-size: '16'
letter-spacing: '2'
text-anchor: middle
x: '360'
y: '272'
- component: text
config:
content: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? ((items[props.itemRemaining].state && items[props.itemRemaining].state !== 'NULL' && items[props.itemRemaining].state !== 'UNDEF') ? items[props.itemRemaining].state : '--:--') : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'END') ? '✔' : '—')"
fill: '#ffffff'
font-size: '48'
font-weight: '800'
text-anchor: middle
x: '360'
y: '320'
- component: ellipse
config:
cx: '360'
cy: '564'
fill: '#000000'
opacity: '0.45'
rx: '80'
ry: '11'
- component: rect
config:
fill: '#30363e'
height: '220'
rx: '14'
stroke: '#454c55'
stroke-width: '2.5'
width: '164'
x: '278'
y: '336'
- component: line
config:
stroke: '#454c55'
stroke-width: '2.5'
x1: '280'
x2: '440'
y1: '378'
y2: '378'
- component: rect
config:
fill: '#3a414a'
height: '18'
rx: '3'
width: '32'
x: '288'
y: '350'
- component: circle
config:
cx: '350'
cy: '359'
fill: '#4a525c'
r: '9'
stroke: '#59626d'
stroke-width: '1.5'
- component: rect
config:
fill: '#35c2f5'
height: '15'
opacity: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? '0.9' : '0.25'"
rx: '3'
width: '38'
x: '390'
y: '352'
- component: circle
config:
cx: '360'
cy: '478'
fill: '#1c2127'
r: '58'
stroke: '#4a525c'
stroke-width: '5'
- component: circle
config:
cx: '360'
cy: '478'
fill: none
r: '46'
stroke: '#343b44'
stroke-width: '2.5'
- component: circle
config:
cx: '360'
cy: '478'
fill: '#0d1520'
r: '38'
- component: g
config:
transform: translate(360,478)
slots:
default:
- component: g
config:
class: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? 'wm-drum' : ''"
slots:
default:
- component: circle
config:
cx: '0'
cy: '0'
fill: none
opacity: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? '0.5' : '0.15'"
r: '27'
stroke: '#2f9fe0'
stroke-dasharray: 54 116
stroke-linecap: round
stroke-width: '4'
- component: rect
config:
fill: '#3f4750'
height: '22'
rx: '4'
width: '8'
x: '-4'
y: '-32'
- component: rect
config:
fill: '#3f4750'
height: '22'
rx: '4'
transform: rotate(120)
width: '8'
x: '-4'
y: '-32'
- component: rect
config:
fill: '#3f4750'
height: '22'
rx: '4'
transform: rotate(240)
width: '8'
x: '-4'
y: '-32'
- component: circle
config:
cx: '0'
cy: '0'
fill: '#333a43'
r: '6'
- component: rect
config:
fill: '#454c55'
height: '9'
rx: '2'
width: '16'
x: '292'
y: '556'
- component: rect
config:
fill: '#454c55'
height: '9'
rx: '2'
width: '16'
x: '412'
y: '556'
- component: circle
config:
class: wm-pulse
cx: '240'
cy: '428'
fill: '#2f9fe0'
opacity: '0.35'
r: '7'
- component: circle
config:
class: wm-pulse
cx: '255'
cy: '468'
fill: '#2f9fe0'
opacity: '0.3'
r: '5'
- component: circle
config:
class: wm-pulse
cx: '230'
cy: '500'
fill: '#2f9fe0'
opacity: '0.3'
r: '4'
- component: circle
config:
class: wm-pulse
cx: '480'
cy: '440'
fill: '#2f9fe0'
opacity: '0.3'
r: '6'
- component: circle
config:
class: wm-pulse
cx: '494'
cy: '482'
fill: '#2f9fe0'
opacity: '0.25'
r: '4'
- component: text
config:
content: "=(({'DETECTING':'Weighing load','WASHING':'Washing','RUNNING':'Washing','RINSING':'Rinsing','SPINNING':'Spinning','DRYING':'Drying','STEAMSOFTENING':'Steam softening','WASH_REFRESHING':'Refreshing','COOLDOWN':'Cooling down','PAUSE':'Paused','RINSEHOLD':'Rinse hold','END':'Finished','ERROR':'Error','RESERVED':'Scheduled start','INITIAL':'Ready to start'})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state] || 'Off') + ((['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0 && items[props.itemRemaining].state && items[props.itemRemaining].state.indexOf(':') > 0) ? (' · done at ~' + dayjs().add(Number.parseInt(items[props.itemRemaining].state.split(':')[0]) || 0, 'hour').add(Number.parseInt(items[props.itemRemaining].state.split(':')[1]) || 0, 'minute').format('HH:mm')) : '')"
fill: "=(['DETECTING','WASHING','RUNNING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN'].indexOf((['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state) >= 0) ? '#35c2f5' : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'END') ? '#37d67a' : '#9fb0bd')"
font-size: '20'
font-weight: '700'
text-anchor: middle
x: '360'
y: '632'
- component: line
config:
stroke: '#22303f'
stroke-width: '4'
x1: '110'
x2: '610'
y1: '680'
y2: '680'
- component: line
config:
stroke: '#37d67a'
stroke-width: '4'
x1: '110'
x2: "=110 + 125 * Math.max(0, ({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state] || 0)"
y1: '680'
y2: '680'
- component: circle
config:
class: "=([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'DETECTING') ? 'wm-pulse' : ''"
cx: '110'
cy: '680'
fill: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) > 0 ? '#37d67a' : (([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'DETECTING') ? '#35c2f5' : '#1b2531')"
r: '12'
stroke: rgba(255,255,255,0.12)
stroke-width: '2'
- component: circle
config:
class: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) === 1 ? 'wm-pulse' : ''"
cx: '235'
cy: '680'
fill: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) > 1 ? '#37d67a' : ((({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) === 1 ? '#35c2f5' : '#1b2531')"
r: '12'
stroke: rgba(255,255,255,0.12)
stroke-width: '2'
- component: circle
config:
class: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) === 2 ? 'wm-pulse' : ''"
cx: '360'
cy: '680'
fill: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) > 2 ? '#37d67a' : ((({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) === 2 ? '#35c2f5' : '#1b2531')"
r: '12'
stroke: rgba(255,255,255,0.12)
stroke-width: '2'
- component: circle
config:
class: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) === 3 ? 'wm-pulse' : ''"
cx: '485'
cy: '680'
fill: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) > 3 ? '#37d67a' : ((({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) === 3 ? '#35c2f5' : '#1b2531')"
r: '12'
stroke: rgba(255,255,255,0.12)
stroke-width: '2'
- component: circle
config:
class: "=([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'END') ? 'wm-pulse' : ''"
cx: '610'
cy: '680'
fill: "=([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'END') ? '#37d67a' : '#1b2531'"
r: '12'
stroke: rgba(255,255,255,0.12)
stroke-width: '2'
- component: text
config:
content: WEIGH
fill: "=([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'DETECTING') ? '#e6ecf1' : '#74848f'"
font-size: '15'
text-anchor: middle
x: '110'
y: '722'
- component: text
config:
content: WASH
fill: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) === 1 ? '#e6ecf1' : '#74848f'"
font-size: '15'
text-anchor: middle
x: '235'
y: '722'
- component: text
config:
content: RINSE
fill: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) === 2 ? '#e6ecf1' : '#74848f'"
font-size: '15'
text-anchor: middle
x: '360'
y: '722'
- component: text
config:
content: SPIN
fill: "=(({'DETECTING':0,'WASHING':1,'RUNNING':1,'STEAMSOFTENING':1,'WASH_REFRESHING':1,'RINSING':2,'RINSEHOLD':2,'SPINNING':3,'DRYING':3,'COOLDOWN':3,'END':4})[(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state]) === 3 ? '#e6ecf1' : '#74848f'"
font-size: '15'
text-anchor: middle
x: '485'
y: '722'
- component: text
config:
content: DONE
fill: "=([(['DETECTING','WASHING','RINSING','SPINNING','DRYING','STEAMSOFTENING','WASH_REFRESHING','COOLDOWN','PAUSE','RINSEHOLD','END'].indexOf(items[props.itemProcess].state) >= 0) ? items[props.itemProcess].state : items[props.itemState].state][0] === 'END') ? '#e6ecf1' : '#74848f'"
font-size: '15'
text-anchor: middle
x: '610'
y: '722'
- component: rect
config:
fill: rgba(255,255,255,0.035)
height: '84'
rx: '18'
stroke: rgba(255,255,255,0.09)
width: '312'
x: '40'
y: '752'
- component: text
config:
content: 🧺
font-size: '22'
x: '60'
y: '804'
- component: text
config:
content: PROGRAM
fill: '#9fb0bd'
font-size: '17'
font-weight: '600'
x: '100'
y: '788'
- component: text
config:
content: =({'COTTON':'Cotton','COTTONPLUS':'Eco 40-60','MIXEDFABRIC':'Mixed fabric','EASYCARE':'Easy care','TURBO59':'Turbo Wash 59','SPEED14':'Speed 14','SMALLLOAD':'Small load','SINGLEGARMENT':'Single garment','LIGHTLYSOILEDITEMS':'Lightly soiled','DUVET':'Duvet','BLANKET':'Blanket','WOOL':'Wool','DELICATE':'Delicate','LINGERIE':'Lingerie','JEANS':'Jeans','SPORTSWEAR':'Sportswear','GYMCLOTHES':'Gym clothes','SWIMMINGWEAR':'Swimwear','KIDSWEAR':'Kids wear','BABYCARE':'Baby care','BABYSTEAMCARE':'Baby steam','ALLERGYSPASTEAM':'Anti-allergy','HYGIENE':'Hygiene','SKINCARE':'Skin care','COLORPROTECTION':'Color protection','COLDWASH':'Cold wash','NOISEMINIMIZE':'Quiet','RAINYSEASON':'Rainy season','SWEATSTAIN':'Sweat stains','JUICEANDFOODSTAINS':'Food stains','SLEEVEHEMSANDCOLLARS':'Cuffs & collars','MINIMIZEDETERGENT':'Less detergent','RINSESPIN':'Rinse + spin','SPIN':'Spin','DRAIN':'Drain','TUB_CLEAN':'Tub clean','QUICKTUBCLEAN':'Quick tub clean','NOT_SELECTED':'—'})[items[props.itemCourse].state] || '—'
fill: '#ffffff'
font-size: '19'
font-weight: '800'
x: '100'
y: '822'
- component: rect
config:
fill: rgba(255,255,255,0.035)
height: '84'
rx: '18'
stroke: rgba(255,255,255,0.09)
width: '312'
x: '368'
y: '752'
- component: text
config:
content: 🌡
font-size: '22'
x: '388'
y: '804'
- component: text
config:
content: TEMPERATURE
fill: '#9fb0bd'
font-size: '17'
font-weight: '600'
x: '428'
y: '788'
- component: text
config:
content: =({'TEMP_COLD':'Cold','TEMP_20':'20 °C','TEMP_30':'30 °C','TEMP_40':'40 °C','TEMP_50':'50 °C','TEMP_60':'60 °C','TEMP_95':'95 °C','NO_TEMP':'—'})[items[props.itemTemp].state] || '—'
fill: '#ffffff'
font-size: '22'
font-weight: '800'
x: '428'
y: '822'
- component: rect
config:
fill: rgba(255,255,255,0.035)
height: '84'
rx: '18'
stroke: rgba(255,255,255,0.09)
width: '312'
x: '40'
y: '848'
- component: text
config:
content: 🌀
font-size: '22'
x: '60'
y: '900'
- component: text
config:
content: SPIN
fill: '#9fb0bd'
font-size: '17'
font-weight: '600'
x: '100'
y: '884'
- component: text
config:
content: "=(items[props.itemSpin].state === 'NO_SPIN') ? 'No spin' : ((items[props.itemSpin].state === 'SPIN_Max') ? 'Max' : ((items[props.itemSpin].state && items[props.itemSpin].state.indexOf('SPIN_') === 0) ? (items[props.itemSpin].state.substring(5) + ' rpm') : '—'))"
fill: '#ffffff'
font-size: '22'
font-weight: '800'
x: '100'
y: '918'
- component: rect
config:
fill: rgba(255,255,255,0.035)
height: '84'
rx: '18'
stroke: rgba(255,255,255,0.09)
width: '312'
x: '368'
y: '848'
- component: text
config:
content: 💧
font-size: '22'
x: '388'
y: '900'
- component: text
config:
content: RINSE
fill: '#9fb0bd'
font-size: '17'
font-weight: '600'
x: '428'
y: '884'
- component: text
config:
content: =({'RINSE_NORMAL':'Normal','RINSE_PLUS':'Plus','RINSE_PLUSPLUS':'Plus ++','RINSE_NORMAL_HOLD':'Normal · hold','RINSE_PLUS_HOLD':'Plus · hold','NO_RINSE':'—'})[items[props.itemRinse].state] || '—'
fill: '#ffffff'
font-size: '22'
font-weight: '800'
x: '428'
y: '918'
- component: rect
config:
fill: rgba(255,255,255,0.035)
height: '84'
rx: '18'
stroke: rgba(255,255,255,0.09)
width: '312'
x: '40'
y: '944'
- component: text
config:
content: "=(items[props.itemDoor].state === '1' || items[props.itemDoor].state === 'DOOR_LOCK_ON') ? '🔒' : '🔓'"
font-size: '22'
x: '60'
y: '996'
- component: text
config:
content: DOOR
fill: '#9fb0bd'
font-size: '17'
font-weight: '600'
x: '100'
y: '980'
- component: text
config:
content: "=(items[props.itemDoor].state === '1' || items[props.itemDoor].state === 'DOOR_LOCK_ON') ? 'LOCKED' : ((items[props.itemDoor].state === '0' || items[props.itemDoor].state === 'DOOR_LOCK_OFF') ? 'UNLOCKED' : '—')"
fill: "=(items[props.itemDoor].state === '1' || items[props.itemDoor].state === 'DOOR_LOCK_ON') ? '#35c2f5' : '#9fb0bd'"
font-size: '22'
font-weight: '800'
x: '100'
y: '1014'
- component: rect
config:
fill: rgba(255,255,255,0.035)
height: '84'
rx: '18'
stroke: rgba(255,255,255,0.09)
width: '312'
x: '368'
y: '944'
- component: text
config:
content: 📡
font-size: '22'
x: '388'
y: '996'
- component: text
config:
content: REMOTE START
fill: '#9fb0bd'
font-size: '17'
font-weight: '600'
x: '428'
y: '980'
- component: text
config:
content: "=(items[props.itemRemoteStart].state === 'ON') ? 'ENABLED' : 'DISABLED'"
fill: "=(items[props.itemRemoteStart].state === 'ON') ? '#37d67a' : '#9fb0bd'"
font-size: '22'
font-weight: '800'
x: '428'
y: '1014'

