matterbridge-dyson-care 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.7.0] - 2026-11-01
8
+
9
+ ### Changed
10
+
11
+ - [devices]: **Breaking:** every device changes its identity in Matter once more at the first start, and the Matter apps take it for a new device: put it back in its room once. The identity is now made without the product name, so a cloud that names a machine later, or names it differently, changes nothing in Matter. The machine list of the account updates the product name again, without reconnecting the machine, and the setup page shows the name the cloud gives the machine now.
12
+
13
+ ### Fixed
14
+
15
+ - [setup]: Redraw the home screen alone, and only when the state changed, while a machine connects: a redraw of another screen for nothing could land between a press and its click, and lose the click.
16
+ - [setup]: Sign out and remove a machine through a POST, so that the page shows a sign out or a removal that failed: Matterbridge answers a DELETE with 204 and no body, whatever the plugin returns, which showed every one as a success.
17
+
7
18
  ## [0.6.0] - 2026-10-25
8
19
 
9
20
  ### Added
package/README.md CHANGED
@@ -424,7 +424,7 @@ Outside Matterbridge, [homebridge-dyson-pure-cool](https://github.com/lukasroegn
424
424
 
425
425
  The plugin knows each machine by its **serial number**, which is also the serial number it has in Matter. A thermostat, a switch, a mode select or a sensor that is a device of its own has that serial number with a dash and a letter after it, such as `NK6-EU-MHA0000A-N` for the night mode switch and `NK6-EU-MHA0000A-T` for the thermostat:
426
426
 
427
- - **The firmware is kept up to date.** The plugin reads the machine list of your account when it starts, when you sign in, every 15 minutes and when you open the setup page. It takes the firmware, the variant, the model, the room name and the password of its broker on the network each machine has now, and starts again a machine that could not start. The product name stays as it was when the machine was added, because Matterbridge makes the identity of the device in Matter from it too. A machine the cloud now reports with another type, or on a Dyson cloud service dyson-control cannot connect to, keeps what was stored, and a warning is logged.
427
+ - **The firmware is kept up to date.** The plugin reads the machine list of your account when it starts, when you sign in, every 15 minutes and when you open the setup page. It takes the firmware, the variant, the model, the product name, the room name and the password of its broker on the network each machine has now, and starts again a machine that could not start. The identity of the device in Matter is made without the product name, so a cloud that names a machine differently later changes nothing there. A machine the cloud now reports with another type, or on a Dyson cloud service dyson-control cannot connect to, keeps what was stored, and a warning is logged.
428
428
  - **Each device has the name you give it in the setup page**, of up to 32 characters, which starts as the room name of the MyDyson app, followed by a number when another device has that name already, such as `Bedroom 2`. A machine without a room name starts with none, for you to type. A new room name in the MyDyson app does not change it: the options of the device show the new one beside its product name and serial number, as soon as you open the setup page again. No two devices share a name, because the name is what the Matter apps show and what its switches and sensors are named after, shortened where the whole would pass 32 characters.
429
429
  - **A new name changes nothing else in Matter.** The device keeps its identity: in Matterbridge's own list of devices it is named after the serial number, such as `Dyson NK6-EU-MHA0000A`, and the name you give it is the label the Matter apps show, which a new name replaces in place. The apps keep its room and its automations, and one that let you rename it there keeps the name you chose.
430
430
  - **What each machine can do** is read from its type, its model, its variant and its firmware capabilities, as the cloud reports them. When a firmware update gives a machine other controls, a setting it no longer takes is reset, and the machine is built again in Matter at the next restart of Matterbridge, which the setup page and Matterbridge ask for.
@@ -1,10 +1,10 @@
1
- async function y(e,t,n){try{let[s,r]=e.split(" "),a={method:s};t!==void 0&&(a.headers={"Content-Type":"application/json"},a.body=JSON.stringify(t));let l=await fetch(new URL(`api/${r}${n===void 0?"":`?${n}`}`,document.baseURI),a);if(l.status===204)return{ok:!0};if(l.status===503)return{ok:!1,error:"The plugin is not running. Start it in Matterbridge, then reload this page."};let c=await l.json();return"ok"in c?c:{ok:!1,error:c.error??`The plugin answered with status ${l.status}`}}catch{return{ok:!1,error:"Matterbridge did not answer. Check that it is running, then try again."}}}var i={screen:"loading",setup:void 0,countries:[],signIn:{email:"",country:""},machines:[],selected:new Set,queue:[],saved:[],handled:[],isBusy:!1,busyText:"",error:"",info:"",confirmingRemoval:"",confirmingSignOut:!1};function b(){return i.queue[0]}function $(e){return e.name===""?e.machine.serialNumber:e.name}function G(e){return e.productName??`Dyson machine of type ${e.type}`}function j(e){let t=`${G(e)} ${e.serialNumber}`;return e.name===void 0?t:`${t}, named "${e.name}" in the MyDyson app`}var Y=["hasChildLockSwitch","hasUvLightSwitch","hasIoniserSwitch","hasJetFocusSwitch","hasIndirectAirflowSwitch","hasAirQualityTargetSelect","hasWickDryingSwitch","hasWickDryingSpeedSelect","hasWaterHardnessSelect"];function L(e){let{deviceType:t,fanControl:n,fanAutoControl:s,oscillationControl:r,nightModeControl:a,airflowDirectionControl:l,heatControl:c,humidificationControl:d,findAndFollowControl:v,hasChildLockSwitch:C,hasUvLightSwitch:T,hasIoniserSwitch:P,hasJetFocusSwitch:m,hasIndirectAirflowSwitch:A,hasAirQualityTargetSelect:V,hasWickDryingSwitch:re,hasWickDryingSpeedSelect:Ee,hasWaterHardnessSelect:He,hasContinuousMonitoringSwitch:Ne,hasAirQualitySensor:Le,hasTemperatureSensor:Fe,hasHumiditySensor:Oe,isComposed:Re}=e;return{deviceType:t,fanControl:n,fanAutoControl:s,oscillationControl:r,nightModeControl:a,airflowDirectionControl:l,heatControl:c,humidificationControl:d,findAndFollowControl:v,hasChildLockSwitch:C,hasUvLightSwitch:T,hasIoniserSwitch:P,hasJetFocusSwitch:m,hasIndirectAirflowSwitch:A,hasAirQualityTargetSelect:V,hasWickDryingSwitch:re,hasWickDryingSpeedSelect:Ee,hasWaterHardnessSelect:He,hasContinuousMonitoringSwitch:Ne,hasAirQualitySensor:Le,hasTemperatureSensor:Fe,hasHumiditySensor:Oe,isComposed:Re}}function Z(e,t){let n=L(e);t.hasAutoMode||(n.fanAutoControl="native");let s=t.oscillationAngles.length===0&&t.tiltAngles.length===0;t.hasOscillation&&n.oscillationControl==="select"&&s&&(n.oscillationControl="switch"),t.hasOscillation&&n.oscillationControl==="fan"&&t.oscillationFanAngles.length===0&&(n.oscillationControl="switch"),t.hasHeat||(n.heatControl="hidden"),t.hasHumidification||(n.humidificationControl="hidden"),t.hasFindAndFollow||(n.findAndFollowControl="hidden");for(let r of Y)n[r]&&=t.settingControls.includes(r);return n.hasContinuousMonitoringSwitch&&=t.hasContinuousMonitoring,n.hasAirQualitySensor&&=t.hasAirQualitySensor,n}var Pe={deviceType:"airPurifier",fanControl:"steps",fanAutoControl:"native",oscillationControl:"native",nightModeControl:"native",airflowDirectionControl:"native",heatControl:"thermostat",humidificationControl:"select",findAndFollowControl:"hidden",hasChildLockSwitch:!1,hasUvLightSwitch:!1,hasIoniserSwitch:!1,hasJetFocusSwitch:!1,hasIndirectAirflowSwitch:!1,hasAirQualityTargetSelect:!1,hasWickDryingSwitch:!1,hasWickDryingSpeedSelect:!1,hasWaterHardnessSelect:!1,hasContinuousMonitoringSwitch:!1,hasAirQualitySensor:!0,hasTemperatureSensor:!0,hasHumiditySensor:!0,isComposed:!0},je=32;function qe(e,t){if(e===""||!t.has(e))return e;for(let n=2;;n++){let s=` ${n}`,r=`${e.slice(0,je-s.length).trimEnd()}${s}`;if(!t.has(r))return r}}function ae(e){let t=new Set((i.setup?.devices??[]).map(n=>n.name));return e.map(n=>{let s=qe(n.name??"",t);return t.add(s),{machine:n,name:s,host:"",isCloudOnly:!1,isNew:!0,...L(Pe)}})}function le(e){let{capabilities:t,controls:n,reportedSensors:s,name:r,roomName:a,host:l,isCloudOnly:c,serialNumber:d,type:v,variant:C,mqttRootTopicLevel:T,model:P,productName:m,firmwareVersion:A,firmwareCapabilities:V}=e;return{machine:{serialNumber:d,type:v,category:"environmentalCare",name:a,variant:C,mqttRootTopicLevel:T,model:P,productName:m,firmwareVersion:A,firmwareCapabilities:V},name:r,host:l??"",isCloudOnly:c??!1,isNew:!1,capabilities:t,controls:n,reportedSensors:s,...L(e)}}var ce={temperature:!1,humidity:!1,airQuality:!1,pm25:!1,pm10:!1,volatileOrganicCompounds:!1,nitrogenDioxide:!1,formaldehyde:!1,carbonDioxide:!1},he={hasAutoMode:!1,hasOscillation:!1,oscillationAngles:[],tiltAngles:[],oscillationFanAngles:[],hasFindAndFollow:!1,hasAirflowDirection:!1,hasHeat:!1,hasHumidification:!1,hasAutoHumidification:!1,settingControls:[],hasFilterLife:!1,hasContinuousMonitoring:!1,hasAirQualitySensor:!1};var q={apple:{title:"Apple Home",text:"Fan speed as a percentage with its auto mode, a switch for the oscillation, the night mode, the find and follow, the jet focus, the UV light and the ioniser, with a sensor for the air quality, the temperature and the humidity, each a device of its own."},google:{title:"Google Home",text:"Fan speed as steps, a switch for the auto mode, the oscillation, the night mode, the find and follow, the jet focus, the UV light and the ioniser, with a sensor for the air quality, the temperature and the humidity, each a device of its own."},matter:{title:"Full Matter support",text:"For apps that show every Matter control: the auto mode, the oscillation, the night mode and the airflow direction on the fan control itself, the target humidity, the find and follow and the settings of the machine as mode selects and switches, and every sensor, all as one device."}};function F(e,t){let n=e==="matter",s=n?"native":"switch";return Z({deviceType:t.hasAirQualitySensor?"airPurifier":"fan",fanControl:e==="apple"?"percent":"steps",fanAutoControl:e==="google"?"switch":"native",oscillationControl:s,nightModeControl:s,airflowDirectionControl:"native",heatControl:"thermostat",humidificationControl:n?"select":"switches",findAndFollowControl:n?"select":"switch",hasChildLockSwitch:n,hasUvLightSwitch:!0,hasIoniserSwitch:!0,hasJetFocusSwitch:!0,hasIndirectAirflowSwitch:!0,hasAirQualityTargetSelect:n,hasWickDryingSwitch:n,hasWickDryingSpeedSelect:n,hasWaterHardnessSelect:n,hasContinuousMonitoringSwitch:n,hasAirQualitySensor:!0,hasTemperatureSensor:!0,hasHumiditySensor:!0,isComposed:n},t)}function ue(e,t,n){return JSON.stringify(Z(e,n))===JSON.stringify(F(t,n))}var X;function pe(e){X=e}function h(){let e=document.getElementById("app");if(e===null||X===void 0)return;let t=`${i.screen}:${b()?.machine.serialNumber??""}`,n=e.dataset.form===t,s=document.activeElement,r=s instanceof HTMLElement?s.id:"",a=s instanceof HTMLInputElement&&(s.type==="text"||s.type==="password")?{start:s.selectionStart,end:s.selectionEnd}:void 0,l=new Map;for(let d of e.querySelectorAll("input[type=text], input[type=email], input[type=password]"))d.id!==""&&l.set(d.id,d.value);if(e.replaceChildren(...X[i.screen]().filter(d=>d!==!1&&d!==null&&d!==void 0)),e.dataset.form=t,!n)return;for(let[d,v]of l){let C=document.getElementById(d);C instanceof HTMLInputElement&&(C.value=v)}if(r==="")return;let c=document.getElementById(r);c?.focus(),a!==void 0&&c instanceof HTMLInputElement&&c.setSelectionRange(a.start,a.end)}async function k(e,t){i.isBusy=!0,i.busyText=e,i.error="",i.info="",h();try{return await t()}finally{i.isBusy=!1,h()}}async function W(){let e=await y("GET state");if(!e.ok){i.error=e.error,i.screen="home",h();return}if(O(e.state),e.state.account===void 0){await I();return}if(e.state.devices.length===0&&!e.state.account.needsSignIn){await E();return}i.screen="home",h(),We()}async function We(){let e=await y("POST sync");!e.ok||JSON.stringify(e.state)===JSON.stringify(i.setup)||(O(e.state),h())}var Qe=2e3,_;function O(e){i.setup=e,!(_!==void 0||!e.devices.some(t=>t.status==="connecting"))&&(_=window.setTimeout(()=>{_=void 0,x().then(h)},Qe))}async function x(){let e=await y("GET state");e.ok&&O(e.state)}function S(){i.queue=[],i.saved=[],i.handled=[],i.error="",i.info="",i.screen="home",x().then(h),h()}async function I(){let e=i.setup?.account;if(i.signIn={email:e?.email??"",country:e?.country??""},i.error="",i.info="",i.screen="signIn",h(),i.countries.length>0)return;let t=await y("GET countries");t.ok&&(i.countries=t.countries),h()}async function ee(e,t){i.signIn={email:e,country:t};let n=await k(`Asking Dyson how ${e} signs in\u2026`,async()=>y("POST login-code",{email:e,country:t}));if(!n.ok){i.error=n.error,h();return}i.signIn={email:e,country:t,challengeId:n.challengeId},h()}async function te(e,t){let{email:n,country:s,challengeId:r}=i.signIn,a=typeof r=="string"?{challengeId:r,otpCode:t}:{},l=await k("Signing in\u2026",async()=>y("POST login",{email:n,country:s,password:e,...a}));if(!l.ok){i.error=l.error,h();return}O(l.state),i.signIn={email:n,country:s},l.state.devices.length===0?await E():S()}async function de(){let e=await k("Signing out\u2026",async()=>y("DELETE account"));i.confirmingSignOut=!1,e.ok||(i.error=e.error),await x(),h()}async function E(){i.screen="find";let e=await k("Reading the machines of your Dyson account\u2026",async()=>y("POST machines"));if(!e.ok){i.error=`The machines could not be read: ${e.error}`,h();return}await x(),i.machines=e.machines;for(let t of e.machines)!t.isConfigured&&t.support==="supported"&&i.selected.add(t.serialNumber);h()}function Q(e){i.queue=e,i.saved=[],i.handled=[],ne()}async function ne(){let e=b();if(e===void 0){i.screen=i.saved.length>0?"done":"home",await x(),h();return}i.screen="connect",i.error="",i.info="",e.probe=void 0,h(),await oe(e)}async function oe(e){let t=await k(`Connecting to ${$(e)} through the Dyson cloud, and looking for it on your network\u2026`,async()=>y("POST probe",{machine:e.machine}));if(!t.ok){i.error=t.error,h();return}if(e.probe=t.result,t.result.status==="signInRequired"&&await x(),t.result.status==="ready"){let n=e.controls===void 0;e.capabilities=t.result.capabilities,e.controls=t.result.controls,e.reportedSensors=t.result.reportedSensors,e.isNew&&n&&e.controls!==void 0&&Object.assign(e,F("apple",e.controls)),e.isNew&&n&&t.result.isFoundOnLan===!1&&(e.isCloudOnly=!0),i.screen="options"}h()}function fe(){let e=i.queue.shift();e!==void 0&&i.handled.push({label:$(e),isSaved:!1}),ne()}async function me(e){let t=e.name.trim(),n=Ue(e,t);if(n!==""){i.error=n,h();return}let{machine:s}=e,r=e.isCloudOnly||e.host===""?void 0:e.host,a={...s,name:t,roomName:s.name,host:r,isCloudOnly:e.isCloudOnly||void 0,...L(e)},l=await k(`Saving ${t}\u2026`,async()=>y("POST device",{device:a}));if(!l.ok){i.error=l.error,h();return}O(l.state),i.saved.push(t),i.handled.push({label:t,isSaved:!0}),i.queue.shift(),await ne()}function Ue(e,t){return t===""?"Give the machine a name: it is the name it gets in Matter.":(i.setup?.devices??[]).some(s=>s.name===t&&s.serialNumber!==e.machine.serialNumber)?`"${t}" is already the name of another machine. Pick a different one.`:""}var ie="Matterbridge's own list of devices shows the change once you reload its page.",ge="Restart Matterbridge to apply the new options in Matter: until then the machine keeps its current controls.";async function ye(e){let t=await k(`Removing ${e.name}\u2026`,async()=>y("DELETE device",void 0,`serialNumber=${encodeURIComponent(e.serialNumber)}`));i.confirmingRemoval="",t.ok?i.info=`${e.name} is removed. ${ie}`:i.error=t.error,await x(),h()}function o(e,t={},...n){let s=document.createElement(e);for(let[r,a]of Object.entries(t))a===void 0||a===!1||(r==="class"?s.className=String(a):r==="html"?s.innerHTML=String(a):r.startsWith("on")&&typeof a=="function"?s.addEventListener(r.slice(2),a):r in s?s[r]=a:s.setAttribute(r,String(a)));for(let r of n)r!==!1&&r!==null&&r!==void 0&&s.append(r);return s}function u(e,t,n="quiet",s=!1){return o("button",{type:"button",class:`button ${n}`,onclick:t,disabled:s||i.isBusy},e)}function w(){return i.isBusy?o("p",{class:"notice working",role:"status"},o("span",{class:"spinner"}),i.busyText):i.error!==""?o("p",{class:"notice problem",role:"alert"},i.error):i.info!==""?o("p",{class:"notice",role:"status"},i.info):null}function U(){let e=b();if(e===void 0||!e.isNew||i.handled.length+i.queue.length<2)return null;let t=i.screen==="connect"?"Connecting":"Choosing options";return o("ol",{class:"units","aria-label":"Machines in this setup"},...i.handled.map(n=>o("li",{class:n.isSaved?"done":"skipped"},o("strong",{},n.label),o("span",{class:"meta"},n.isSaved?"Added":"Skipped"))),...i.queue.map((n,s)=>o("li",{class:s===0?"current":"ahead","aria-current":s===0?"true":void 0},o("strong",{},$(n)),o("span",{class:"meta"},s===0?t:"Waiting"))))}function B(e,t){return o("header",{class:"unit-header"},o("h1",{},$(e)),o("p",{class:"meta"},t))}function H(){if(i.queue.length>0&&!i.queue.some(s=>s.isNew))return null;let e=["Find","Connect","Options"],n=["find","connect","options"].indexOf(i.screen);return o("ol",{class:"steps"},...e.map((s,r)=>{let a="ahead";return r<n?a="done":r===n&&(a="current"),o("li",{class:a,"aria-current":r===n?"step":void 0},s)}))}function Be(e){let t=e.probe?.status,n=e.name===""?"The machine":e.name;return t==="signInRequired"?o("div",{class:"field"},o("p",{class:"notice problem",role:"alert"},"The Dyson cloud no longer accepts the sign-in of this plugin."),u("Sign in again",()=>{I()},"primary")):t==="unreachable"?o("p",{class:"notice problem",role:"alert"},`${n} did not answer. Check that it is plugged in and shows as online in the MyDyson app, then try again.`):t==="unsupported"?o("p",{class:"notice problem",role:"alert"},`The plugin cannot control ${n}: ${e.probe?.message??"unknown"}`):t==="failed"?o("p",{class:"notice problem",role:"alert"},`${n} answered with an error: ${e.probe?.message??"unknown"}`):null}function we(){let e=b();if(e===void 0)return[w()];let t=e.probe?.status==="unreachable"||e.probe?.status==="failed"||i.error!=="",n=e.isNew&&i.queue.length>1;return[H(),U(),B(e,`Connecting to the ${j(e.machine)} through the Dyson cloud.`),w(),i.isBusy?null:Be(e),o("div",{class:"footer"},t?u("Try again",()=>{oe(e)}):null,u(n?"Skip this one":"Cancel",n?fe:S))]}function Se(){let e=i.saved,t=(i.setup?.devices??[]).some(n=>e.includes(n.name)&&n.needsRestart);return[o("header",{},o("h1",{},e.length===1?`${e[0]} is set up`:`${e.length} machines are set up`)),o("p",{class:"lead"},"They now appear in the Matter apps paired with Matterbridge. If Matterbridge is not paired yet, pair it from its home page."),t?o("p",{class:"help"},o("strong",{},ge)):null,o("p",{class:"help"},ie),o("div",{class:"footer"},u("Done",S,"primary"))]}function ve(e){return!e.isConfigured&&e.support==="supported"}function Ke(e){let t=[];return e.isConfigured&&t.push("Already set up."),e.support==="otherCategory"&&t.push("Not a purifier, fan, heater or humidifier: the plugin does not control it."),e.support==="unknownType"&&t.push(`The plugin does not know machines of type ${e.type} yet.`),e.support==="otherBroker"&&t.push("It is reached through a Dyson cloud service the plugin cannot connect to yet."),t}function ze(e){let t=ve(e),n=`machine-${e.serialNumber}`,s=o("input",{type:"checkbox",id:n,checked:t&&i.selected.has(e.serialNumber),disabled:!t,onchange:()=>{s.checked?i.selected.add(e.serialNumber):i.selected.delete(e.serialNumber),h()}});return o("li",{class:t?"found":"found unavailable"},o("label",{for:n},s,o("span",{},o("strong",{},e.name??"No room name"),o("span",{class:"meta"},` ${G(e)}, ${e.serialNumber}`),...Ke(e).map(r=>o("span",{class:"meta note"},r)))))}function Ce(){let e=i.machines.filter(n=>i.selected.has(n.serialNumber)&&ve(n)),t=e.length===1?"Set up 1 machine":`Set up ${e.length} machines`;return[H(),o("header",{},o("h1",{},"Your Dyson machines")),w(),i.machines.length>0?o("ul",{class:"found-list"},...i.machines.map(ze)):null,!i.isBusy&&i.machines.length===0&&i.error===""?o("p",{class:"empty"},"Your Dyson account has no machine yet. Add your machines in the MyDyson app, then read the list again."):null,o("div",{class:"footer"},u("Read the list again",()=>{E()}),u("Cancel",S),u(t,()=>Q(ae(e)),"primary",e.length===0))]}var Je={hasChildLockSwitch:"child lock",hasUvLightSwitch:"UV light",hasIoniserSwitch:"ioniser",hasJetFocusSwitch:"jet focus",hasIndirectAirflowSwitch:"indirect airflow",hasAirQualityTargetSelect:"air quality target",hasWickDryingSwitch:"wick drying",hasWickDryingSpeedSelect:"wick drying speed",hasWaterHardnessSelect:"water hardness"},R={native:"on the fan",switch:"as a switch",select:"as a mode select",fan:"as a fan of its own",hidden:""};function Ve(e){let t=e.controls,n=m=>t===void 0||t[m],s=t!==void 0&&t.tiltAngles.length>0,r=e.deviceType==="airPurifier"?"an air purifier":"a fan",a={native:"",switch:"",firstStep:" with auto first",lastStep:" with auto last"}[e.fanAutoControl],l=[`${r} with speed ${e.fanControl==="steps"?"steps":"as a percentage"}${n("hasAutoMode")?a:""}`],c=new Map,d=(m,A)=>{m!==""&&c.set(m,[...c.get(m)??[],A])};n("hasAutoMode")&&e.fanAutoControl==="switch"&&d(R.switch,"the auto mode"),n("hasOscillation")&&d(R[e.oscillationControl],s?"the tilt":"the oscillation"),n("hasFindAndFollow")&&d(R[e.findAndFollowControl],"the find and follow"),d(R[e.nightModeControl],"the night mode"),n("hasAirflowDirection")&&d(R[e.airflowDirectionControl],"the airflow direction");for(let[m,A]of c)l.push(`${A.join(", ")} ${m}`);n("hasHeat")&&e.heatControl!=="hidden"&&l.push(e.heatControl==="thermostat"?"a thermostat":"a heat switch"),n("hasHumidification")&&e.humidificationControl!=="hidden"&&l.push(e.humidificationControl==="select"?"a target humidity select":"humidify switches");let v=Y.filter(m=>e[m]&&(t===void 0||t.settingControls.includes(m))).map(m=>Je[m]);v.length>0&&l.push(`a control for the ${v.join(", ")}`),n("hasFilterLife")&&e.deviceType==="airPurifier"&&l.push("the filter life");let{reportedSensors:C}=e,T=[n("hasAirQualitySensor")&&e.hasAirQualitySensor&&C.airQuality?"air quality":"",e.hasTemperatureSensor&&C.temperature?"temperature":"",e.hasHumiditySensor&&C.humidity?"humidity":""].filter(m=>m!=="");return T.length>0&&l.push(`a sensor for the ${T.join(", ")}`),`${l.length===1?l[0]:`${l.slice(0,-1).join(", ")} and ${l[l.length-1]}`}${e.isComposed?", all as one device":""}`}var Ge={filter:"Filter",outerFilter:"HEPA filter",innerFilter:"Carbon filter"};function Ye(e){let t=e.remainingTimeOfActiveInMinutes;if(e.state==="active")return t===null?"Cleaning":`Cleaning, ${t} min left`;if(e.state==="paused")return"Clean cycle paused";if(e.state==="incomplete")return"Clean cycle not finished";if(e.state==="setup")return"Clean cycle being set up";let n=e.remainingTimeToNextInHours;return n===null?"":n<=0?"Clean cycle due":n<48?`Next clean cycle in ${n} hours`:`Next clean cycle in ${Math.round(n/24)} days`}function Ze(e){let t=e.filters.map(({filter:a,lifeInPercent:l})=>`${Ge[a]} ${l} %`).join(", "),n=e.cleanCycle===void 0?"":Ye(e.cleanCycle),s=e.wickDrying?.remainingTimeInMinutes,r="";return e.wickDrying!==void 0&&(r=s==null?"Drying its wick":`Drying its wick, ${s} min left`),[t,n,r].filter(a=>a!=="").map(a=>`${a}.`).join(" ")}function Xe(e){return e.replaceAll(/([a-z])([A-Z])/g,"$1 $2").toLowerCase()}function _e(e){if(e===void 0)return[];let t=Ze(e),n=e.faults??[];return[t===""?null:o("p",{class:"meta"},t),n.length===0?null:o("p",{class:"meta problem-text"},`The machine reports: ${n.map(Xe).join(", ")}.`)]}var et={local:" on your network",remote:" through the Dyson cloud"};function tt(e){return e.status!=="online"||e.hostType!=="remote"||!e.isTriedOnLan?null:e.host!==void 0?o("p",{class:"meta"},`It did not answer on your network at ${e.host}, so the plugin reaches it through the Dyson cloud. Check the host in its options.`):o("p",{class:"meta"},"It was not found on your network, so the plugin reaches it through the Dyson cloud. Where the search does not reach it, such as in a Docker container, enter its IP address in its options.")}function nt(e){let n={online:`Online${e.hostType===void 0?"":et[e.hostType]}`,connecting:"Connecting\u2026",offline:"Not answering",stopped:"Not started"}[e.status],s=i.confirmingRemoval===e.serialNumber,r=e.productName??`type ${e.type}`;return o("li",{class:"device"},o("div",{class:"device-main"},o("h3",{},e.name),o("p",{class:"meta"},o("span",{class:`status ${e.status}`},n),` ${r}, ${e.serialNumber}. In Matter: ${Ve(e)}.`),tt(e),..._e(e.report),e.status!=="online"&&e.error?o("p",{class:"meta problem-text"},e.error):null,e.needsRestart?o("p",{class:"meta"},o("strong",{},"Restart Matterbridge")," to apply the new options in Matter. Until then it keeps its current controls."):null),s?o("div",{class:"actions"},o("span",{class:"meta"},"Remove it from Matter too?"),u("Remove",()=>{ye(e)},"danger"),u("Keep",()=>{i.confirmingRemoval="",h()})):o("div",{class:"actions"},u(e.status==="online"?"Options":"Reconnect",()=>Q([le(e)]),"quiet",i.setup?.account===void 0),u("Remove",()=>{i.confirmingRemoval=e.serialNumber,h()})))}function ot(){let e=i.setup?.account;if(i.setup===void 0)return null;if(e===void 0)return o("section",{class:"account"},o("p",{class:"notice problem",role:"alert"},"No Dyson account is signed in, so the machines below are not connected. They stay in Matter until you sign in again."),u("Sign in",()=>{I()},"primary"));let t=i.confirmingSignOut?[o("span",{class:"meta"},"The machines stay in Matter, unreachable until you sign in again."),u("Sign out",()=>{de()},"danger"),u("Stay signed in",()=>{i.confirmingSignOut=!1,h()})]:[u(e.needsSignIn?"Sign in again":"Switch account",()=>{I()},e.needsSignIn?"primary":"quiet"),u("Sign out",()=>{i.confirmingSignOut=!0,h()})];return o("section",{class:"account"},o("p",{class:"meta"},`Signed in to Dyson as ${e.email}.`),e.needsSignIn?o("p",{class:"notice problem",role:"alert"},"The Dyson cloud no longer accepts this sign-in. Sign in again to keep the machines connected."):null,o("div",{class:"actions"},...t))}function be(){let e=i.setup?.devices??[],t=i.setup?.account!==void 0&&!i.setup.account.needsSignIn;return[o("header",{},o("h1",{},"Dyson machines"),t?u("Add machines",()=>{E()},"primary"):null),w(),ot(),e.length===0&&i.setup?o("p",{class:"empty"},"No machine is set up yet. Add one to control it from Matter."):o("ul",{class:"devices"},...e.map(nt)),i.setup?null:u("Try again",()=>{W()},"primary")]}var K={steps:'<svg viewBox="0 0 48 28" aria-hidden="true"><rect x="2" y="20" width="8" height="6"/><rect x="14" y="14" width="8" height="12"/><rect x="26" y="8" width="8" height="18"/><rect x="38" y="2" width="8" height="24"/></svg>',percent:'<svg viewBox="0 0 48 28" aria-hidden="true"><path d="M2 26 L46 2 L46 26 Z"/></svg>',purifier:'<svg viewBox="0 0 48 28" aria-hidden="true"><rect class="stroke" x="16" y="2" width="16" height="24" rx="3"/><path class="stroke" d="M20 8 H28 M20 13 H28 M20 18 H28"/></svg>',fan:'<svg viewBox="0 0 48 28" aria-hidden="true"><circle class="stroke" cx="24" cy="12" r="9"/><path class="stroke" d="M24 21 V26 M18 26 H30"/></svg>'},N="Few apps show a mode select: Home Assistant does, Apple Home does not.",J="Not every app shows it there: Apple Home and Google Home are not known to.";function f(e,t,n){return e.isComposed?n?`It appears as a part of "${e.name}", tagged "${t}"`:`It appears as a part of "${e.name}"`:`It appears as "${e.name} ${t}"`}function g(e,t){Object.assign(e,t),h()}function z(e,t,n,s,r,a,l){let c=`${e}-${t}`,d=o("input",{type:"radio",name:e,id:c,checked:t===n,onchange:()=>l(t)});return o("label",{class:"choice pictured",for:c},d,o("span",{class:"glyph",html:a}),o("span",{},o("strong",{},s),o("span",{class:"meta"},r)))}function p(e,t,n,s,r,a){let l=`${e}-${t}`,c=o("input",{type:"radio",name:e,id:l,checked:t===n,onchange:()=>a(t)});return o("label",{class:"choice",for:l},c,o("span",{},o("strong",{},s),o("span",{class:"meta"},r)))}function M(e,t,n,s,r){let a=o("input",{type:"checkbox",id:e,checked:t,onchange:()=>r(a.checked)});return o("label",{class:"choice",for:e},a,o("span",{},o("strong",{},n),o("span",{class:"meta"},s)))}function D(e,t,...n){let s=`${e.toLowerCase().replaceAll(" ","-")}-title`;return o("section",{},o("h2",{id:s},e),t===""?null:o("p",{class:"help"},t),o("div",{class:"choices",role:"radiogroup","aria-labelledby":s},...n))}function it(e){let t=o("input",{type:"text",id:"device-name",value:e.name,maxLength:32,autocomplete:"off",spellcheck:!1});t.addEventListener("input",r=>{e.name=t.value.trim(),r.isComposing||h()});let n=e.machine.name,s=n===void 0?"It has no room name in the MyDyson app.":`Its room name in the MyDyson app is "${n}", which other machines in that room share.`;return o("section",{},o("h2",{},o("label",{for:"device-name"},"Name in Matter")),o("p",{class:"help"},`${s} The name here is what your Matter apps show, up to 32 characters, and no two machines of this plugin share one. A new room name in the MyDyson app does not change it.`),o("div",{class:"inline-form"},t))}function st(e,t){return o("section",{class:"presets"},o("h2",{},"Start from a preset"),o("div",{class:"preset-list"},...Object.keys(q).map(n=>o("button",{type:"button",class:"preset","aria-pressed":String(ue(e,n,t)),onclick:()=>g(e,F(n,t))},o("strong",{},q[n].title),o("span",{class:"meta"},q[n].text)))),o("p",{class:"help"},"A preset fills in the choices below for what this machine can do. Change any of them afterwards."))}function rt(e,t){let n=r=>g(e,{deviceType:r}),s=t.hasFilterLife?" and the life left in its filters, which a Matter app can reset after a new one is fitted":"";return o("section",{},o("h2",{},"Appears as"),o("div",{class:"choices"},z("device-type","airPurifier",e.deviceType,"An air purifier",`The Matter device type of a purifier, with its fan speed${s}. Apple Home, Google Home and Home Assistant show it.`,K.purifier,n),z("device-type","fan",e.deviceType,"A fan",`The Matter device type every app shows, with the same fan speed${t.hasFilterLife?", but no filters":""}. For apps that show no air purifier.`,K.fan,n)))}function at(e,t){let n=c=>g(e,{fanControl:c}),s=c=>g(e,{fanAutoControl:c}),r=t.hasAutoMode?e.fanAutoControl:"native",l=r==="firstStep"||r==="lastStep"?"Eleven steps: the ten speeds and auto.":"Ten steps, one for each speed.";return o("section",{},o("h2",{},"Fan speed"),o("div",{class:"choices"},z("fan","steps",e.fanControl,"Steps",`${l} A slider shows them as a percentage too.`,K.steps,n),z("fan","percent",e.fanControl,"Percentage","A slider from 0 to 100, which the plugin rounds to the nearest speed: 10 % is speed 1 and 100 % speed 10.",K.percent,n)),t.hasAutoMode?o("div",{class:"field"},o("h3",{id:"fan-auto-title"},"Auto mode"),o("p",{class:"help"},"In its auto mode the machine picks the fan speed from the air it measures. Matter has it as the fan mode Auto, which not every app shows."),o("div",{class:"choices",role:"radiogroup","aria-labelledby":"fan-auto-title"},p("fan-auto","native",r,"Native fan mode only","Auto is a mode of the fan speed, beside its speeds, as Matter has it.",s),p("fan-auto","switch",r,"A switch",`${f(e,"Auto Mode",!0)}. On turns the auto mode on, off goes back to the speed the fan last ran at. Auto stays a fan mode as well.`,s),p("fan-auto","firstStep",r,"The first step","Auto comes below speed 1: step 1, or the bottom of the slider.",s),p("fan-auto","lastStep",r,"The last step","Auto comes above speed 10: the top step, or 100 %.",s))):null)}function se(e){let t=e.map(n=>n==="breeze"?"breeze":`${n}\xB0`);return t.length===1?t[0]:`${t.slice(0,-1).join(", ")} and ${t[t.length-1]}`}function xe(e,t,n,s,r){let a=s?", and its fan mode Auto breezes":"";return p("oscillation","fan",t,"A fan",`${f(e,"Oscillation",!0)}, with ${n}${a}. It is on and off with the machine, and its own on/off does nothing. It takes the place of the rocking of the fan control, which Apple Home mishandles.`,r)}function lt(e,t){let n=a=>g(e,{oscillationControl:a}),s=t.oscillationFanAngles.length>0,r=e.oscillationControl==="fan"&&!s?"switch":e.oscillationControl;return D("Tilt","The machine tilts to 0\xB0, 25\xB0 or 50\xB0, or breezes: it tilts up and down. Tilting turns a machine that is off on, as in the MyDyson app.",p("oscillation","native",r,"Native rocking",`The up and down rocking of the fan control, as Matter has it: on breezes, off goes back to the angle it was tilted to. ${J}`,n),p("oscillation","switch",r,"A switch",`${f(e,"Oscillation",!0)}. On breezes, off goes back to the angle it was tilted to. It takes the place of the rocking of the fan control, which Apple Home mishandles.`,n),p("oscillation","select",r,"A mode select",`${f(e,"Tilt",!0)}, and picks 0\xB0, 25\xB0, 50\xB0 or breeze. It takes the place of the rocking of the fan control, which Apple Home mishandles. ${N}`,n),s?xe(e,r,`${se(t.oscillationFanAngles)} its speeds`,!0,n):null)}function ct(e,t){if(!t.hasOscillation)return null;if(t.tiltAngles.length>0)return lt(e,t);let n=c=>g(e,{oscillationControl:c}),s=t.oscillationAngles.length>0,r=t.oscillationFanAngles.length>0,l=e.oscillationControl==="select"&&!s||e.oscillationControl==="fan"&&!r?"switch":e.oscillationControl;return D("Oscillation","",p("oscillation","native",l,"Native rocking",`The left and right rocking of the fan control, as Matter has it. ${J}`,n),p("oscillation","switch",l,"A switch",`${f(e,"Oscillation",!0)}. On sweeps the angle set in the MyDyson app. It takes the place of the rocking of the fan control, which Apple Home mishandles.`,n),s?p("oscillation","select",l,"A mode select",`${f(e,"Oscillation",!0)}, and picks off or one of ${se(t.oscillationAngles)}. It takes the place of the rocking of the fan control, which Apple Home mishandles. ${N}`,n):null,r?xe(e,l,`the first speed standing it still and ${se(t.oscillationFanAngles)} the speeds above it`,t.oscillationAngles.includes("breeze"),n):null)}function ht(e){let t=n=>g(e,{nightModeControl:n});return D("Night mode","In its night mode the machine runs quieter and dims its display.",p("night-mode","native",e.nightModeControl,"Native sleep wind only",`The sleep wind of the fan control, as Matter has it. ${J}`,t),p("night-mode","switch",e.nightModeControl,"A switch",`${f(e,"Night Mode",!0)}. The night mode stays the sleep wind of the fan control as well.`,t))}function ut(e,t){if(!t.hasAirflowDirection)return null;let n=s=>g(e,{airflowDirectionControl:s});return D("Airflow direction","The machine blows its air out of the front, or out of the back to circulate the air without cooling you.",p("airflow","native",e.airflowDirectionControl,"Native airflow direction only",`The airflow direction of the fan control, as Matter has it: forward is the front. ${J}`,n),p("airflow","switch",e.airflowDirectionControl,"A switch",`${f(e,"Front Airflow",!0)}. On blows the air out of the front, off out of the back. The airflow direction of the fan control stays as well.`,n))}function pt(e,t){if(!t.hasFindAndFollow)return null;let n=s=>g(e,{findAndFollowControl:s});return D("Find and follow","The machine finds the people in the room and turns to follow them. It runs only while the machine is on, so turning it on turns the machine on.",p("find-and-follow","switch",e.findAndFollowControl,"A switch",`${f(e,"Find and Follow",!0)}. On follows the people the machine finds.`,n),p("find-and-follow","select",e.findAndFollowControl,"A mode select",`${f(e,"Find and Follow",!0)}, and picks off, follow, or scan to look for people. ${N}`,n),p("find-and-follow","hidden",e.findAndFollowControl,"Not shown in Matter","Find and follow stays as the MyDyson app sets it.",n))}function dt(e,t){if(!t.hasHeat)return null;let n=s=>g(e,{heatControl:s});return D("Heating","The machine heats only while it is on, so turning the heating on turns the machine on.",p("heat","thermostat",e.heatControl,"A thermostat",`${f(e,"Heater",!1)}: it heats or is off, with a setpoint from 1 to 37 \xB0C. Off leaves the fan running.`,n),p("heat","switch",e.heatControl,"A switch",`${f(e,"Heat",!0)}. It heats to the setpoint set in the MyDyson app.`,n),p("heat","hidden",e.heatControl,"Not shown in Matter","The heating stays as the MyDyson app sets it.",n))}function ft(e,t){if(!t.hasHumidification)return null;let n=a=>g(e,{humidificationControl:a}),s=t.hasAutoHumidification?' With a second switch, "Auto Humidify", for the machine to pick the target itself.':"",r=t.hasAutoHumidification?"off, auto, or a target from 30 to 70 %":"off, or a target from 30 to 70 %";return D("Humidifying","Matter has no humidifier, so the humidifying takes a switch or a mode select. It runs only while the machine is on, so turning it on turns the machine on.",p("humidify","switches",e.humidificationControl,"Switches",`${f(e,"Humidify",!0)}, and humidifies to the target set in the MyDyson app.${s}`,n),p("humidify","select",e.humidificationControl,"A mode select",`${f(e,"Target Humidity",!0)}, and picks ${r}. ${N}`,n),p("humidify","hidden",e.humidificationControl,"Not shown in Matter","The humidifying stays as the MyDyson app sets it.",n))}var Ae={hasChildLockSwitch:{label:"Child Lock",text:"On locks the buttons of the machine.",isSelect:!1},hasUvLightSwitch:{label:"UV Light",text:"On turns the UV light of the machine on.",isSelect:!1},hasIoniserSwitch:{label:"Ioniser",text:"On turns the ioniser of the machine on.",isSelect:!1},hasJetFocusSwitch:{label:"Jet Focus",text:"On focuses the air into one stream, and off diffuses it.",isSelect:!1},hasIndirectAirflowSwitch:{label:"Indirect Airflow",text:"On tilts the nozzle to 45\xB0, and off points it straight.",isSelect:!1},hasAirQualityTargetSelect:{label:"Air Quality Target",text:"picks the air quality the auto mode aims for: good, sensitive or very sensitive.",isSelect:!0},hasWickDryingSwitch:{label:"Wick Drying",text:"On dries the wick of the humidifier.",isSelect:!1},hasWickDryingSpeedSelect:{label:"Wick Drying Speed",text:"picks how fast the wick dries: slow, normal or fast, in 60, 45 or 30 minutes.",isSelect:!0}};function mt(e,t){let n=Object.keys(Ae).filter(s=>t.settingControls.includes(s));return n.length===0?null:o("section",{},o("h2",{},"More controls"),o("p",{class:"help"},"Controls of this machine that Matter has no place for. As in the MyDyson app, most of them turn a machine that is off on: the air quality target and the wick drying do not."),o("div",{class:"choices"},...n.map(s=>{let{label:r,text:a,isSelect:l}=Ae[s],c=`Add a ${l?"mode select":"switch"} for the ${r.toLowerCase().replace("uv","UV")}`,d=l?`${f(e,r,!0)}, and ${a} ${N}`:`${f(e,r,!0)}. ${a}`;return M(s,e[s],c,d,v=>g(e,{[s]:v}))})))}function gt(e,t){return t.settingControls.includes("hasWaterHardnessSelect")?o("section",{},o("h2",{},"Clean cycle"),o("p",{class:"help"},"The machine asks for a clean cycle as often as the hardness of its water says: the harder the water, the sooner. The setup page shows when the next one is due."),o("div",{class:"choices"},M("water-hardness",e.hasWaterHardnessSelect,"Add a mode select for the water hardness",`${f(e,"Water Hardness",!0)}, and picks soft, medium or hard. ${N}`,n=>g(e,{hasWaterHardnessSelect:n})))):null}var yt=[["pm25","PM2.5"],["pm10","PM10"],["volatileOrganicCompounds","VOC"],["nitrogenDioxide","NO\u2082"],["formaldehyde","formaldehyde"],["carbonDioxide","CO\u2082"]];function ke(e,t="and"){return e.length===1?e[0]:`${e.slice(0,-1).join(", ")} ${t} ${e[e.length-1]}`}function wt(e,t){let n=e.reportedSensors??ce,s=yt.filter(([c])=>n[c]).map(([,c])=>c),r=s.length===0?"each pollutant the machine measures, once it gives it":`the ${ke(s)} the machine measures`,a=[n.temperature?"":"room temperature",n.humidity?"":"humidity"].filter(c=>c!==""),l=a.length===0?"Each sensor shows what the machine measures.":`This machine has given no ${ke(a,"or")} reading, so there is no sensor for ${a.length===1?"it":"them"}. If it gives one later, open its options again.`;return o("section",{},o("h2",{},"Readings"),o("p",{class:"help"},l),o("div",{class:"choices"},t.hasAirQualitySensor?M("air-quality",e.hasAirQualitySensor,"Add an air quality sensor",`${f(e,"Air Quality",!1)}, with the air quality of its worst pollutant and ${r}.`,c=>g(e,{hasAirQualitySensor:c})):null,n.temperature?M("temperature",e.hasTemperatureSensor,"Add a temperature sensor",`${f(e,"Temperature",!1)}, with the room temperature the machine measures.`,c=>g(e,{hasTemperatureSensor:c})):null,n.humidity?M("humidity",e.hasHumiditySensor,"Add a humidity sensor",`${f(e,"Humidity",!1)}, with the room humidity the machine measures.`,c=>g(e,{hasHumiditySensor:c})):null,t.hasContinuousMonitoring?M("continuous-monitoring",e.hasContinuousMonitoringSwitch,"Add a switch for the continuous monitoring",`${f(e,"Continuous Monitoring",!0)}. On keeps the machine measuring the air while it is off, so that the readings stay fresh.`,c=>g(e,{hasContinuousMonitoringSwitch:c})):null))}function St(e){let t=e.probe?.isFoundOnLan;return t===void 0?"":t?" It answered on your network just now.":" It was not found on your network just now."}function vt(e){let t=o("input",{type:"text",id:"device-host",value:e.host,placeholder:"Found on your network",maxLength:253,autocomplete:"off",spellcheck:!1});return t.addEventListener("input",()=>{e.host=t.value.trim()}),o("div",{class:"field"},o("label",{for:"device-host"},"Host"),o("p",{class:"help"},"Where the search does not reach the machine, such as in a Docker container without host networking, enter its IP address, ideally one your router keeps for it. Leave it empty to search."),o("div",{class:"inline-form"},t))}function Ct(e){let t=s=>{e.isCloudOnly=s==="cloud",h()},n=e.isCloudOnly?"cloud":"lan";return o("section",{},o("h2",{id:"connection-title"},"Connection"),o("p",{class:"help"},`How the plugin reaches the machine.${St(e)}`),o("div",{class:"choices",role:"radiogroup","aria-labelledby":"connection-title"},p("connection","lan",n,"Your network, then the Dyson cloud","The plugin finds the machine on your network by the service it announces there, and reaches it through the Dyson cloud when it does not answer. It searches again on each connection, so it follows the machine to a new IP address.",t),p("connection","cloud",n,"The Dyson cloud only","For a machine that is not on the network of Matterbridge: each connection skips the search of up to 3 seconds.",t)),e.isCloudOnly?null:vt(e))}function bt(e){let t=e.deviceType==="airPurifier"?"the air purifier":"the fan";return o("section",{},o("h2",{},"One device or several"),o("p",{class:"help"},`The thermostat, the switches, the mode selects and the sensors can be parts of ${t} in Matter, or devices of their own beside it.`),o("div",{class:"choices"},M("composed",e.isComposed,`Make them parts of ${t}`,`Your Matter app has one device, "${e.name}", and they move between rooms with it. A part has no name of its own in Matter, so an app may show the switches as numbered outlets, and an app that shows no parts shows ${t} alone. Turned off, each is a device of its own with its own name, which your app puts in a room by itself.`,n=>g(e,{isComposed:n}))))}function Me(){let e=b();if(e===void 0)return[w()];let t=e.controls??he;return[H(),U(),B(e,`Options for the ${j(e.machine)}. Power and fan speed always appear in Matter: choose how the rest appears.`),it(e),st(e,t),rt(e,t),at(e,t),ct(e,t),ht(e),ut(e,t),pt(e,t),dt(e,t),ft(e,t),gt(e,t),mt(e,t),wt(e,t),bt(e),Ct(e),e.isNew?null:o("p",{class:"help"},"Changing any of these but the name and the connection takes effect at the next restart of Matterbridge, which this page and Matterbridge ask for. Until then the machine keeps its current controls."),w(),o("div",{class:"footer"},u("Cancel",S),u(e.isNew?`Add ${e.name}`:"Save options",()=>{me(e)},"primary"))]}var At="An account in mainland China is on a Dyson cloud of its own: pick China for it.";function kt(){let e=i.signIn.country===""?"US":i.signIn.country,t=i.countries.length===0?o("input",{type:"text",id:"country",value:e,maxLength:2,autocomplete:"country",spellcheck:!1}):o("select",{id:"country"},...i.countries.map(n=>o("option",{value:n,selected:n===e},xt(n))));return o("div",{class:"field"},o("label",{for:"country"},"Country of the account"),o("p",{class:"help"},At),t)}function xt(e){try{return new Intl.DisplayNames(void 0,{type:"region"}).of(e)??e}catch{return e}}function Mt(){let e=o("input",{type:"email",id:"email",value:i.signIn.email,autocomplete:"username",spellcheck:!1}),t=()=>{let n=document.getElementById("country"),s=n instanceof HTMLInputElement||n instanceof HTMLSelectElement?n.value.trim().toUpperCase():"";e.value.trim()===""||s===""||ee(e.value.trim(),s)};return[o("div",{class:"field"},o("label",{for:"email"},"Email address"),o("p",{class:"help"},"The address you sign in to the MyDyson app with. Dyson emails it a code, unless the account signs in with its password alone."),e),kt(),o("div",{class:"footer"},u("Cancel",S),u("Continue",t,"primary"))]}function De(e){return o("div",{class:"field"},o("label",{for:"password"},"Password of the account"),o("p",{class:"help"},"It is used once to sign in and is not stored."),e)}function Te(){return u("Use another address",()=>{i.signIn={email:i.signIn.email,country:i.signIn.country},i.error="",h()})}function Dt(){let e=o("input",{type:"text",id:"otp-code",inputMode:"numeric",autocomplete:"one-time-code",spellcheck:!1,maxLength:16}),t=o("input",{type:"password",id:"password",autocomplete:"current-password",maxLength:256}),n=()=>{e.value.trim()===""||t.value===""||te(t.value,e.value.trim())};return t.addEventListener("keydown",s=>{s.key==="Enter"&&n()}),[o("p",{class:"lead"},`Dyson emailed a code to ${i.signIn.email}. It can take a minute to arrive.`),o("div",{class:"field"},o("label",{for:"otp-code"},"Code from the email"),e),De(t),o("div",{class:"footer"},Te(),u("Send a new code",()=>{ee(i.signIn.email,i.signIn.country)}),u("Sign in",n,"primary"))]}function Tt(){let e=o("input",{type:"password",id:"password",autocomplete:"current-password",maxLength:256}),t=()=>{e.value!==""&&te(e.value)};return e.addEventListener("keydown",n=>{n.key==="Enter"&&t()}),[o("p",{class:"lead"},`${i.signIn.email} signs in to Dyson with its password alone.`),De(e),o("div",{class:"footer"},Te(),u("Sign in",t,"primary"))]}function $e(){let e=i.setup?.account!==void 0;return[o("header",{},o("h1",{},e?"Sign in to Dyson again":"Sign in to Dyson")),o("p",{class:"lead"},"Dyson machines are reached through the Dyson cloud, with your Dyson account. The plugin keeps the sign-in Dyson gives it in its configuration, and never your password."),w(),...i.signIn.challengeId===void 0?Mt():i.signIn.challengeId===null?Tt():Dt()]}function Ie(){try{if(window.parent===window)return;let e=getComputedStyle(window.parent.document.body);for(let t of["--primary-color","--div-bg-color","--div-text-color","--main-bg-color","--main-label-color","--div-border-color","--main-light-color"]){let n=e.getPropertyValue(t).trim();n!==""&&document.documentElement.style.setProperty(`--host${t.slice(1)}`,n)}document.documentElement.style.setProperty("--host-font",e.fontFamily)}catch{}}pe({loading:()=>[o("p",{class:"notice working",role:"status"},o("span",{class:"spinner"}),"Loading\u2026")],home:be,signIn:$e,find:Ce,connect:we,options:Me,done:Se});Ie();h();W();
1
+ async function y(e,t){try{let[n,s]=e.split(" "),r={method:n};t!==void 0&&(r.headers={"Content-Type":"application/json"},r.body=JSON.stringify(t));let a=await fetch(new URL(`api/${s}`,document.baseURI),r);if(a.status===503)return{ok:!1,error:"The plugin is not running. Start it in Matterbridge, then reload this page."};let l=await a.json();return"ok"in l?l:{ok:!1,error:l.error??`The plugin answered with status ${a.status}`}}catch{return{ok:!1,error:"Matterbridge did not answer. Check that it is running, then try again."}}}var i={screen:"loading",setup:void 0,countries:[],signIn:{email:"",country:""},machines:[],selected:new Set,queue:[],saved:[],handled:[],isBusy:!1,busyText:"",error:"",info:"",confirmingRemoval:"",confirmingSignOut:!1};function b(){return i.queue[0]}function T(e){return e.name===""?e.machine.serialNumber:e.name}function G(e){return e.productName??`Dyson machine of type ${e.type}`}function j(e){let t=`${G(e)} ${e.serialNumber}`;return e.name===void 0?t:`${t}, named "${e.name}" in the MyDyson app`}var Y=["hasChildLockSwitch","hasUvLightSwitch","hasIoniserSwitch","hasJetFocusSwitch","hasIndirectAirflowSwitch","hasAirQualityTargetSelect","hasWickDryingSwitch","hasWickDryingSpeedSelect","hasWaterHardnessSelect"];function F(e){let{deviceType:t,fanControl:n,fanAutoControl:s,oscillationControl:r,nightModeControl:a,airflowDirectionControl:l,heatControl:h,humidificationControl:d,findAndFollowControl:v,hasChildLockSwitch:C,hasUvLightSwitch:D,hasIoniserSwitch:P,hasJetFocusSwitch:m,hasIndirectAirflowSwitch:A,hasAirQualityTargetSelect:V,hasWickDryingSwitch:re,hasWickDryingSpeedSelect:He,hasWaterHardnessSelect:Ne,hasContinuousMonitoringSwitch:Ee,hasAirQualitySensor:Le,hasTemperatureSensor:Fe,hasHumiditySensor:Oe,isComposed:Re}=e;return{deviceType:t,fanControl:n,fanAutoControl:s,oscillationControl:r,nightModeControl:a,airflowDirectionControl:l,heatControl:h,humidificationControl:d,findAndFollowControl:v,hasChildLockSwitch:C,hasUvLightSwitch:D,hasIoniserSwitch:P,hasJetFocusSwitch:m,hasIndirectAirflowSwitch:A,hasAirQualityTargetSelect:V,hasWickDryingSwitch:re,hasWickDryingSpeedSelect:He,hasWaterHardnessSelect:Ne,hasContinuousMonitoringSwitch:Ee,hasAirQualitySensor:Le,hasTemperatureSensor:Fe,hasHumiditySensor:Oe,isComposed:Re}}function Z(e,t){let n=F(e);t.hasAutoMode||(n.fanAutoControl="native");let s=t.oscillationAngles.length===0&&t.tiltAngles.length===0;t.hasOscillation&&n.oscillationControl==="select"&&s&&(n.oscillationControl="switch"),t.hasOscillation&&n.oscillationControl==="fan"&&t.oscillationFanAngles.length===0&&(n.oscillationControl="switch"),t.hasHeat||(n.heatControl="hidden"),t.hasHumidification||(n.humidificationControl="hidden"),t.hasFindAndFollow||(n.findAndFollowControl="hidden");for(let r of Y)n[r]&&=t.settingControls.includes(r);return n.hasContinuousMonitoringSwitch&&=t.hasContinuousMonitoring,n.hasAirQualitySensor&&=t.hasAirQualitySensor,n}var Pe={deviceType:"airPurifier",fanControl:"steps",fanAutoControl:"native",oscillationControl:"native",nightModeControl:"native",airflowDirectionControl:"native",heatControl:"thermostat",humidificationControl:"select",findAndFollowControl:"hidden",hasChildLockSwitch:!1,hasUvLightSwitch:!1,hasIoniserSwitch:!1,hasJetFocusSwitch:!1,hasIndirectAirflowSwitch:!1,hasAirQualityTargetSelect:!1,hasWickDryingSwitch:!1,hasWickDryingSpeedSelect:!1,hasWaterHardnessSelect:!1,hasContinuousMonitoringSwitch:!1,hasAirQualitySensor:!0,hasTemperatureSensor:!0,hasHumiditySensor:!0,isComposed:!0},je=32;function qe(e,t){if(e===""||!t.has(e))return e;for(let n=2;;n++){let s=` ${n}`,r=`${e.slice(0,je-s.length).trimEnd()}${s}`;if(!t.has(r))return r}}function ae(e){let t=new Set((i.setup?.devices??[]).map(n=>n.name));return e.map(n=>{let s=qe(n.name??"",t);return t.add(s),{machine:n,name:s,host:"",isCloudOnly:!1,isNew:!0,...F(Pe)}})}function le(e){let{capabilities:t,controls:n,reportedSensors:s,name:r,roomName:a,host:l,isCloudOnly:h,serialNumber:d,type:v,variant:C,mqttRootTopicLevel:D,model:P,productName:m,firmwareVersion:A,firmwareCapabilities:V}=e;return{machine:{serialNumber:d,type:v,category:"environmentalCare",name:a,variant:C,mqttRootTopicLevel:D,model:P,productName:m,firmwareVersion:A,firmwareCapabilities:V},name:r,host:l??"",isCloudOnly:h??!1,isNew:!1,capabilities:t,controls:n,reportedSensors:s,...F(e)}}var ce={temperature:!1,humidity:!1,airQuality:!1,pm25:!1,pm10:!1,volatileOrganicCompounds:!1,nitrogenDioxide:!1,formaldehyde:!1,carbonDioxide:!1},he={hasAutoMode:!1,hasOscillation:!1,oscillationAngles:[],tiltAngles:[],oscillationFanAngles:[],hasFindAndFollow:!1,hasAirflowDirection:!1,hasHeat:!1,hasHumidification:!1,hasAutoHumidification:!1,settingControls:[],hasFilterLife:!1,hasContinuousMonitoring:!1,hasAirQualitySensor:!1};var q={apple:{title:"Apple Home",text:"Fan speed as a percentage with its auto mode, a switch for the oscillation, the night mode, the find and follow, the jet focus, the UV light and the ioniser, with a sensor for the air quality, the temperature and the humidity, each a device of its own."},google:{title:"Google Home",text:"Fan speed as steps, a switch for the auto mode, the oscillation, the night mode, the find and follow, the jet focus, the UV light and the ioniser, with a sensor for the air quality, the temperature and the humidity, each a device of its own."},matter:{title:"Full Matter support",text:"For apps that show every Matter control: the auto mode, the oscillation, the night mode and the airflow direction on the fan control itself, the target humidity, the find and follow and the settings of the machine as mode selects and switches, and every sensor, all as one device."}};function O(e,t){let n=e==="matter",s=n?"native":"switch";return Z({deviceType:t.hasAirQualitySensor?"airPurifier":"fan",fanControl:e==="apple"?"percent":"steps",fanAutoControl:e==="google"?"switch":"native",oscillationControl:s,nightModeControl:s,airflowDirectionControl:"native",heatControl:"thermostat",humidificationControl:n?"select":"switches",findAndFollowControl:n?"select":"switch",hasChildLockSwitch:n,hasUvLightSwitch:!0,hasIoniserSwitch:!0,hasJetFocusSwitch:!0,hasIndirectAirflowSwitch:!0,hasAirQualityTargetSelect:n,hasWickDryingSwitch:n,hasWickDryingSpeedSelect:n,hasWaterHardnessSelect:n,hasContinuousMonitoringSwitch:n,hasAirQualitySensor:!0,hasTemperatureSensor:!0,hasHumiditySensor:!0,isComposed:n},t)}function ue(e,t,n){return JSON.stringify(Z(e,n))===JSON.stringify(O(t,n))}var X;function pe(e){X=e}function c(){let e=document.getElementById("app");if(e===null||X===void 0)return;let t=`${i.screen}:${b()?.machine.serialNumber??""}`,n=e.dataset.form===t,s=document.activeElement,r=s instanceof HTMLElement?s.id:"",a=s instanceof HTMLInputElement&&(s.type==="text"||s.type==="password")?{start:s.selectionStart,end:s.selectionEnd}:void 0,l=new Map;for(let d of e.querySelectorAll("input[type=text], input[type=email], input[type=password]"))d.id!==""&&l.set(d.id,d.value);if(e.replaceChildren(...X[i.screen]().filter(d=>d!==!1&&d!==null&&d!==void 0)),e.dataset.form=t,!n)return;for(let[d,v]of l){let C=document.getElementById(d);C instanceof HTMLInputElement&&(C.value=v)}if(r==="")return;let h=document.getElementById(r);h?.focus(),a!==void 0&&h instanceof HTMLInputElement&&h.setSelectionRange(a.start,a.end)}async function k(e,t){i.isBusy=!0,i.busyText=e,i.error="",i.info="",c();try{return await t()}finally{i.isBusy=!1,c()}}async function W(){let e=await y("GET state");if(!e.ok){i.error=e.error,i.screen="home",c();return}if($(e.state),e.state.account===void 0){await H();return}if(e.state.devices.length===0&&!e.state.account.needsSignIn){await N();return}i.screen="home",c(),We()}async function We(){let e=await y("POST sync");!e.ok||JSON.stringify(e.state)===JSON.stringify(i.setup)||($(e.state),c())}var Qe=2e3,_;function $(e){i.setup=e,!(_!==void 0||!e.devices.some(t=>t.status==="connecting"))&&(_=window.setTimeout(()=>{_=void 0,Be()},Qe))}async function Be(){let e=await y("GET state");if(!e.ok)return;let t=JSON.stringify(e.state)!==JSON.stringify(i.setup);$(e.state),t&&i.screen==="home"&&c()}async function I(){let e=await y("GET state");e.ok&&$(e.state)}function S(){i.queue=[],i.saved=[],i.handled=[],i.error="",i.info="",i.screen="home",I().then(c),c()}async function H(){let e=i.setup?.account;if(i.signIn={email:e?.email??"",country:e?.country??""},i.error="",i.info="",i.screen="signIn",c(),i.countries.length>0)return;let t=await y("GET countries");t.ok&&(i.countries=t.countries),c()}async function ee(e,t){i.signIn={email:e,country:t};let n=await k(`Asking Dyson how ${e} signs in\u2026`,async()=>y("POST login-code",{email:e,country:t}));if(!n.ok){i.error=n.error,c();return}i.signIn={email:e,country:t,challengeId:n.challengeId},c()}async function te(e,t){let{email:n,country:s,challengeId:r}=i.signIn,a=typeof r=="string"?{challengeId:r,otpCode:t}:{},l=await k("Signing in\u2026",async()=>y("POST login",{email:n,country:s,password:e,...a}));if(!l.ok){i.error=l.error,c();return}$(l.state),i.signIn={email:n,country:s},l.state.devices.length===0?await N():S()}async function de(){let e=await k("Signing out\u2026",async()=>y("POST sign-out"));i.confirmingSignOut=!1,e.ok||(i.error=e.error),await I(),c()}async function N(){i.screen="find";let e=await k("Reading the machines of your Dyson account\u2026",async()=>y("POST machines"));if(!e.ok){i.error=`The machines could not be read: ${e.error}`,c();return}await I(),i.machines=e.machines;for(let t of e.machines)!t.isConfigured&&t.support==="supported"&&i.selected.add(t.serialNumber);c()}function Q(e){i.queue=e,i.saved=[],i.handled=[],ne()}async function ne(){let e=b();if(e===void 0){i.screen=i.saved.length>0?"done":"home",await I(),c();return}i.screen="connect",i.error="",i.info="",e.probe=void 0,c(),await oe(e)}async function oe(e){let t=await k(`Connecting to ${T(e)} through the Dyson cloud, and looking for it on your network\u2026`,async()=>y("POST probe",{machine:e.machine}));if(!t.ok){i.error=t.error,c();return}if(e.probe=t.result,t.result.status==="signInRequired"&&await I(),t.result.status==="ready"){let n=e.controls===void 0;e.capabilities=t.result.capabilities,e.controls=t.result.controls,e.reportedSensors=t.result.reportedSensors,e.isNew&&n&&e.controls!==void 0&&Object.assign(e,O("apple",e.controls)),e.isNew&&n&&t.result.isFoundOnLan===!1&&(e.isCloudOnly=!0),i.screen="options"}c()}function fe(){let e=i.queue.shift();e!==void 0&&i.handled.push({label:T(e),isSaved:!1}),ne()}async function me(e){let t=e.name.trim(),n=Ue(e,t);if(n!==""){i.error=n,c();return}let{machine:s}=e,r=e.isCloudOnly||e.host===""?void 0:e.host,a={...s,name:t,roomName:s.name,host:r,isCloudOnly:e.isCloudOnly||void 0,...F(e)},l=await k(`Saving ${t}\u2026`,async()=>y("POST device",{device:a}));if(!l.ok){i.error=l.error,c();return}$(l.state),i.saved.push(t),i.handled.push({label:t,isSaved:!0}),i.queue.shift(),await ne()}function Ue(e,t){return t===""?"Give the machine a name: it is the name it gets in Matter.":(i.setup?.devices??[]).some(s=>s.name===t&&s.serialNumber!==e.machine.serialNumber)?`"${t}" is already the name of another machine. Pick a different one.`:""}var ie="Matterbridge's own list of devices shows the change once you reload its page.",ge="Restart Matterbridge to apply the new options in Matter: until then the machine keeps its current controls.";async function ye(e){let t=await k(`Removing ${e.name}\u2026`,async()=>y("POST remove-device",{serialNumber:e.serialNumber}));i.confirmingRemoval="",t.ok?i.info=`${e.name} is removed. ${ie}`:i.error=t.error,await I(),c()}function o(e,t={},...n){let s=document.createElement(e);for(let[r,a]of Object.entries(t))a===void 0||a===!1||(r==="class"?s.className=String(a):r==="html"?s.innerHTML=String(a):r.startsWith("on")&&typeof a=="function"?s.addEventListener(r.slice(2),a):r in s?s[r]=a:s.setAttribute(r,String(a)));for(let r of n)r!==!1&&r!==null&&r!==void 0&&s.append(r);return s}function u(e,t,n="quiet",s=!1){return o("button",{type:"button",class:`button ${n}`,onclick:t,disabled:s||i.isBusy},e)}function w(){return i.isBusy?o("p",{class:"notice working",role:"status"},o("span",{class:"spinner"}),i.busyText):i.error!==""?o("p",{class:"notice problem",role:"alert"},i.error):i.info!==""?o("p",{class:"notice",role:"status"},i.info):null}function B(){let e=b();if(e===void 0||!e.isNew||i.handled.length+i.queue.length<2)return null;let t=i.screen==="connect"?"Connecting":"Choosing options";return o("ol",{class:"units","aria-label":"Machines in this setup"},...i.handled.map(n=>o("li",{class:n.isSaved?"done":"skipped"},o("strong",{},n.label),o("span",{class:"meta"},n.isSaved?"Added":"Skipped"))),...i.queue.map((n,s)=>o("li",{class:s===0?"current":"ahead","aria-current":s===0?"true":void 0},o("strong",{},T(n)),o("span",{class:"meta"},s===0?t:"Waiting"))))}function U(e,t){return o("header",{class:"unit-header"},o("h1",{},T(e)),o("p",{class:"meta"},t))}function E(){if(i.queue.length>0&&!i.queue.some(s=>s.isNew))return null;let e=["Find","Connect","Options"],n=["find","connect","options"].indexOf(i.screen);return o("ol",{class:"steps"},...e.map((s,r)=>{let a="ahead";return r<n?a="done":r===n&&(a="current"),o("li",{class:a,"aria-current":r===n?"step":void 0},s)}))}function Je(e){let t=e.probe?.status,n=e.name===""?"The machine":e.name;return t==="signInRequired"?o("div",{class:"field"},o("p",{class:"notice problem",role:"alert"},"The Dyson cloud no longer accepts the sign-in of this plugin."),u("Sign in again",()=>{H()},"primary")):t==="unreachable"?o("p",{class:"notice problem",role:"alert"},`${n} did not answer. Check that it is plugged in and shows as online in the MyDyson app, then try again.`):t==="unsupported"?o("p",{class:"notice problem",role:"alert"},`The plugin cannot control ${n}: ${e.probe?.message??"unknown"}`):t==="failed"?o("p",{class:"notice problem",role:"alert"},`${n} answered with an error: ${e.probe?.message??"unknown"}`):null}function we(){let e=b();if(e===void 0)return[w()];let t=e.probe?.status==="unreachable"||e.probe?.status==="failed"||i.error!=="",n=e.isNew&&i.queue.length>1;return[E(),B(),U(e,`Connecting to the ${j(e.machine)} through the Dyson cloud.`),w(),i.isBusy?null:Je(e),o("div",{class:"footer"},t?u("Try again",()=>{oe(e)}):null,u(n?"Skip this one":"Cancel",n?fe:S))]}function Se(){let e=i.saved,t=(i.setup?.devices??[]).some(n=>e.includes(n.name)&&n.needsRestart);return[o("header",{},o("h1",{},e.length===1?`${e[0]} is set up`:`${e.length} machines are set up`)),o("p",{class:"lead"},"They now appear in the Matter apps paired with Matterbridge. If Matterbridge is not paired yet, pair it from its home page."),t?o("p",{class:"help"},o("strong",{},ge)):null,o("p",{class:"help"},ie),o("div",{class:"footer"},u("Done",S,"primary"))]}function ve(e){return!e.isConfigured&&e.support==="supported"}function Ke(e){let t=[];return e.isConfigured&&t.push("Already set up."),e.support==="otherCategory"&&t.push("Not a purifier, fan, heater or humidifier: the plugin does not control it."),e.support==="unknownType"&&t.push(`The plugin does not know machines of type ${e.type} yet.`),e.support==="otherBroker"&&t.push("It is reached through a Dyson cloud service the plugin cannot connect to yet."),t}function ze(e){let t=ve(e),n=`machine-${e.serialNumber}`,s=o("input",{type:"checkbox",id:n,checked:t&&i.selected.has(e.serialNumber),disabled:!t,onchange:()=>{s.checked?i.selected.add(e.serialNumber):i.selected.delete(e.serialNumber),c()}});return o("li",{class:t?"found":"found unavailable"},o("label",{for:n},s,o("span",{},o("strong",{},e.name??"No room name"),o("span",{class:"meta"},` ${G(e)}, ${e.serialNumber}`),...Ke(e).map(r=>o("span",{class:"meta note"},r)))))}function Ce(){let e=i.machines.filter(n=>i.selected.has(n.serialNumber)&&ve(n)),t=e.length===1?"Set up 1 machine":`Set up ${e.length} machines`;return[E(),o("header",{},o("h1",{},"Your Dyson machines")),w(),i.machines.length>0?o("ul",{class:"found-list"},...i.machines.map(ze)):null,!i.isBusy&&i.machines.length===0&&i.error===""?o("p",{class:"empty"},"Your Dyson account has no machine yet. Add your machines in the MyDyson app, then read the list again."):null,o("div",{class:"footer"},u("Read the list again",()=>{N()}),u("Cancel",S),u(t,()=>Q(ae(e)),"primary",e.length===0))]}var Ve={hasChildLockSwitch:"child lock",hasUvLightSwitch:"UV light",hasIoniserSwitch:"ioniser",hasJetFocusSwitch:"jet focus",hasIndirectAirflowSwitch:"indirect airflow",hasAirQualityTargetSelect:"air quality target",hasWickDryingSwitch:"wick drying",hasWickDryingSpeedSelect:"wick drying speed",hasWaterHardnessSelect:"water hardness"},R={native:"on the fan",switch:"as a switch",select:"as a mode select",fan:"as a fan of its own",hidden:""};function Ge(e){let t=e.controls,n=m=>t===void 0||t[m],s=t!==void 0&&t.tiltAngles.length>0,r=e.deviceType==="airPurifier"?"an air purifier":"a fan",a={native:"",switch:"",firstStep:" with auto first",lastStep:" with auto last"}[e.fanAutoControl],l=[`${r} with speed ${e.fanControl==="steps"?"steps":"as a percentage"}${n("hasAutoMode")?a:""}`],h=new Map,d=(m,A)=>{m!==""&&h.set(m,[...h.get(m)??[],A])};n("hasAutoMode")&&e.fanAutoControl==="switch"&&d(R.switch,"the auto mode"),n("hasOscillation")&&d(R[e.oscillationControl],s?"the tilt":"the oscillation"),n("hasFindAndFollow")&&d(R[e.findAndFollowControl],"the find and follow"),d(R[e.nightModeControl],"the night mode"),n("hasAirflowDirection")&&d(R[e.airflowDirectionControl],"the airflow direction");for(let[m,A]of h)l.push(`${A.join(", ")} ${m}`);n("hasHeat")&&e.heatControl!=="hidden"&&l.push(e.heatControl==="thermostat"?"a thermostat":"a heat switch"),n("hasHumidification")&&e.humidificationControl!=="hidden"&&l.push(e.humidificationControl==="select"?"a target humidity select":"humidify switches");let v=Y.filter(m=>e[m]&&(t===void 0||t.settingControls.includes(m))).map(m=>Ve[m]);v.length>0&&l.push(`a control for the ${v.join(", ")}`),n("hasFilterLife")&&e.deviceType==="airPurifier"&&l.push("the filter life");let{reportedSensors:C}=e,D=[n("hasAirQualitySensor")&&e.hasAirQualitySensor&&C.airQuality?"air quality":"",e.hasTemperatureSensor&&C.temperature?"temperature":"",e.hasHumiditySensor&&C.humidity?"humidity":""].filter(m=>m!=="");return D.length>0&&l.push(`a sensor for the ${D.join(", ")}`),`${l.length===1?l[0]:`${l.slice(0,-1).join(", ")} and ${l[l.length-1]}`}${e.isComposed?", all as one device":""}`}var Ye={filter:"Filter",outerFilter:"HEPA filter",innerFilter:"Carbon filter"};function Ze(e){let t=e.remainingTimeOfActiveInMinutes;if(e.state==="active")return t===null?"Cleaning":`Cleaning, ${t} min left`;if(e.state==="paused")return"Clean cycle paused";if(e.state==="incomplete")return"Clean cycle not finished";if(e.state==="setup")return"Clean cycle being set up";let n=e.remainingTimeToNextInHours;return n===null?"":n<=0?"Clean cycle due":n<48?`Next clean cycle in ${n} hours`:`Next clean cycle in ${Math.round(n/24)} days`}function Xe(e){let t=e.filters.map(({filter:a,lifeInPercent:l})=>`${Ye[a]} ${l} %`).join(", "),n=e.cleanCycle===void 0?"":Ze(e.cleanCycle),s=e.wickDrying?.remainingTimeInMinutes,r="";return e.wickDrying!==void 0&&(r=s==null?"Drying its wick":`Drying its wick, ${s} min left`),[t,n,r].filter(a=>a!=="").map(a=>`${a}.`).join(" ")}function _e(e){return e.replaceAll(/([a-z])([A-Z])/g,"$1 $2").toLowerCase()}function et(e){if(e===void 0)return[];let t=Xe(e),n=e.faults??[];return[t===""?null:o("p",{class:"meta"},t),n.length===0?null:o("p",{class:"meta problem-text"},`The machine reports: ${n.map(_e).join(", ")}.`)]}var tt={local:" on your network",remote:" through the Dyson cloud"};function nt(e){return e.status!=="online"||e.hostType!=="remote"||!e.isTriedOnLan?null:e.host!==void 0?o("p",{class:"meta"},`It did not answer on your network at ${e.host}, so the plugin reaches it through the Dyson cloud. Check the host in its options.`):o("p",{class:"meta"},"It was not found on your network, so the plugin reaches it through the Dyson cloud. Where the search does not reach it, such as in a Docker container, enter its IP address in its options.")}function ot(e){let n={online:`Online${e.hostType===void 0?"":tt[e.hostType]}`,connecting:"Connecting\u2026",offline:"Not answering",stopped:"Not started"}[e.status],s=i.confirmingRemoval===e.serialNumber,r=e.productName??`type ${e.type}`;return o("li",{class:"device"},o("div",{class:"device-main"},o("h3",{},e.name),o("p",{class:"meta"},o("span",{class:`status ${e.status}`},n),` ${r}, ${e.serialNumber}. In Matter: ${Ge(e)}.`),nt(e),...et(e.report),e.status!=="online"&&e.error?o("p",{class:"meta problem-text"},e.error):null,e.needsRestart?o("p",{class:"meta"},o("strong",{},"Restart Matterbridge")," to apply the new options in Matter. Until then it keeps its current controls."):null),s?o("div",{class:"actions"},o("span",{class:"meta"},"Remove it from Matter too?"),u("Remove",()=>{ye(e)},"danger"),u("Keep",()=>{i.confirmingRemoval="",c()})):o("div",{class:"actions"},u(e.status==="online"?"Options":"Reconnect",()=>Q([le(e)]),"quiet",i.setup?.account===void 0),u("Remove",()=>{i.confirmingRemoval=e.serialNumber,c()})))}function it(){let e=i.setup?.account;if(i.setup===void 0)return null;if(e===void 0)return o("section",{class:"account"},o("p",{class:"notice problem",role:"alert"},"No Dyson account is signed in, so the machines below are not connected. They stay in Matter until you sign in again."),u("Sign in",()=>{H()},"primary"));let t=i.confirmingSignOut?[o("span",{class:"meta"},"The machines stay in Matter, unreachable until you sign in again."),u("Sign out",()=>{de()},"danger"),u("Stay signed in",()=>{i.confirmingSignOut=!1,c()})]:[u(e.needsSignIn?"Sign in again":"Switch account",()=>{H()},e.needsSignIn?"primary":"quiet"),u("Sign out",()=>{i.confirmingSignOut=!0,c()})];return o("section",{class:"account"},o("p",{class:"meta"},`Signed in to Dyson as ${e.email}.`),e.needsSignIn?o("p",{class:"notice problem",role:"alert"},"The Dyson cloud no longer accepts this sign-in. Sign in again to keep the machines connected."):null,o("div",{class:"actions"},...t))}function be(){let e=i.setup?.devices??[],t=i.setup?.account!==void 0&&!i.setup.account.needsSignIn;return[o("header",{},o("h1",{},"Dyson machines"),t?u("Add machines",()=>{N()},"primary"):null),w(),it(),e.length===0&&i.setup?o("p",{class:"empty"},"No machine is set up yet. Add one to control it from Matter."):o("ul",{class:"devices"},...e.map(ot)),i.setup?null:u("Try again",()=>{W()},"primary")]}var J={steps:'<svg viewBox="0 0 48 28" aria-hidden="true"><rect x="2" y="20" width="8" height="6"/><rect x="14" y="14" width="8" height="12"/><rect x="26" y="8" width="8" height="18"/><rect x="38" y="2" width="8" height="24"/></svg>',percent:'<svg viewBox="0 0 48 28" aria-hidden="true"><path d="M2 26 L46 2 L46 26 Z"/></svg>',purifier:'<svg viewBox="0 0 48 28" aria-hidden="true"><rect class="stroke" x="16" y="2" width="16" height="24" rx="3"/><path class="stroke" d="M20 8 H28 M20 13 H28 M20 18 H28"/></svg>',fan:'<svg viewBox="0 0 48 28" aria-hidden="true"><circle class="stroke" cx="24" cy="12" r="9"/><path class="stroke" d="M24 21 V26 M18 26 H30"/></svg>'},L="Few apps show a mode select: Home Assistant does, Apple Home does not.",z="Not every app shows it there: Apple Home and Google Home are not known to.";function f(e,t,n){return e.isComposed?n?`It appears as a part of "${e.name}", tagged "${t}"`:`It appears as a part of "${e.name}"`:`It appears as "${e.name} ${t}"`}function g(e,t){Object.assign(e,t),c()}function K(e,t,n,s,r,a,l){let h=`${e}-${t}`,d=o("input",{type:"radio",name:e,id:h,checked:t===n,onchange:()=>l(t)});return o("label",{class:"choice pictured",for:h},d,o("span",{class:"glyph",html:a}),o("span",{},o("strong",{},s),o("span",{class:"meta"},r)))}function p(e,t,n,s,r,a){let l=`${e}-${t}`,h=o("input",{type:"radio",name:e,id:l,checked:t===n,onchange:()=>a(t)});return o("label",{class:"choice",for:l},h,o("span",{},o("strong",{},s),o("span",{class:"meta"},r)))}function x(e,t,n,s,r){let a=o("input",{type:"checkbox",id:e,checked:t,onchange:()=>r(a.checked)});return o("label",{class:"choice",for:e},a,o("span",{},o("strong",{},n),o("span",{class:"meta"},s)))}function M(e,t,...n){let s=`${e.toLowerCase().replaceAll(" ","-")}-title`;return o("section",{},o("h2",{id:s},e),t===""?null:o("p",{class:"help"},t),o("div",{class:"choices",role:"radiogroup","aria-labelledby":s},...n))}function st(e){let t=o("input",{type:"text",id:"device-name",value:e.name,maxLength:32,autocomplete:"off",spellcheck:!1});t.addEventListener("input",r=>{e.name=t.value.trim(),r.isComposing||c()});let n=e.machine.name,s=n===void 0?"It has no room name in the MyDyson app.":`Its room name in the MyDyson app is "${n}", which other machines in that room share.`;return o("section",{},o("h2",{},o("label",{for:"device-name"},"Name in Matter")),o("p",{class:"help"},`${s} The name here is what your Matter apps show, up to 32 characters, and no two machines of this plugin share one. A new room name in the MyDyson app does not change it.`),o("div",{class:"inline-form"},t))}function rt(e,t){return o("section",{class:"presets"},o("h2",{},"Start from a preset"),o("div",{class:"preset-list"},...Object.keys(q).map(n=>o("button",{type:"button",class:"preset","aria-pressed":String(ue(e,n,t)),onclick:()=>g(e,O(n,t))},o("strong",{},q[n].title),o("span",{class:"meta"},q[n].text)))),o("p",{class:"help"},"A preset fills in the choices below for what this machine can do. Change any of them afterwards."))}function at(e,t){let n=r=>g(e,{deviceType:r}),s=t.hasFilterLife?" and the life left in its filters, which a Matter app can reset after a new one is fitted":"";return o("section",{},o("h2",{},"Appears as"),o("div",{class:"choices"},K("device-type","airPurifier",e.deviceType,"An air purifier",`The Matter device type of a purifier, with its fan speed${s}. Apple Home, Google Home and Home Assistant show it.`,J.purifier,n),K("device-type","fan",e.deviceType,"A fan",`The Matter device type every app shows, with the same fan speed${t.hasFilterLife?", but no filters":""}. For apps that show no air purifier.`,J.fan,n)))}function lt(e,t){let n=h=>g(e,{fanControl:h}),s=h=>g(e,{fanAutoControl:h}),r=t.hasAutoMode?e.fanAutoControl:"native",l=r==="firstStep"||r==="lastStep"?"Eleven steps: the ten speeds and auto.":"Ten steps, one for each speed.";return o("section",{},o("h2",{},"Fan speed"),o("div",{class:"choices"},K("fan","steps",e.fanControl,"Steps",`${l} A slider shows them as a percentage too.`,J.steps,n),K("fan","percent",e.fanControl,"Percentage","A slider from 0 to 100, which the plugin rounds to the nearest speed: 10 % is speed 1 and 100 % speed 10.",J.percent,n)),t.hasAutoMode?o("div",{class:"field"},o("h3",{id:"fan-auto-title"},"Auto mode"),o("p",{class:"help"},"In its auto mode the machine picks the fan speed from the air it measures. Matter has it as the fan mode Auto, which not every app shows."),o("div",{class:"choices",role:"radiogroup","aria-labelledby":"fan-auto-title"},p("fan-auto","native",r,"Native fan mode only","Auto is a mode of the fan speed, beside its speeds, as Matter has it.",s),p("fan-auto","switch",r,"A switch",`${f(e,"Auto Mode",!0)}. On turns the auto mode on, off goes back to the speed the fan last ran at. Auto stays a fan mode as well.`,s),p("fan-auto","firstStep",r,"The first step","Auto comes below speed 1: step 1, or the bottom of the slider.",s),p("fan-auto","lastStep",r,"The last step","Auto comes above speed 10: the top step, or 100 %.",s))):null)}function se(e){let t=e.map(n=>n==="breeze"?"breeze":`${n}\xB0`);return t.length===1?t[0]:`${t.slice(0,-1).join(", ")} and ${t[t.length-1]}`}function xe(e,t,n,s,r){let a=s?", and its fan mode Auto breezes":"";return p("oscillation","fan",t,"A fan",`${f(e,"Oscillation",!0)}, with ${n}${a}. It is on and off with the machine, and its own on/off does nothing. It takes the place of the rocking of the fan control, which Apple Home mishandles.`,r)}function ct(e,t){let n=a=>g(e,{oscillationControl:a}),s=t.oscillationFanAngles.length>0,r=e.oscillationControl==="fan"&&!s?"switch":e.oscillationControl;return M("Tilt","The machine tilts to 0\xB0, 25\xB0 or 50\xB0, or breezes: it tilts up and down. Tilting turns a machine that is off on, as in the MyDyson app.",p("oscillation","native",r,"Native rocking",`The up and down rocking of the fan control, as Matter has it: on breezes, off goes back to the angle it was tilted to. ${z}`,n),p("oscillation","switch",r,"A switch",`${f(e,"Oscillation",!0)}. On breezes, off goes back to the angle it was tilted to. It takes the place of the rocking of the fan control, which Apple Home mishandles.`,n),p("oscillation","select",r,"A mode select",`${f(e,"Tilt",!0)}, and picks 0\xB0, 25\xB0, 50\xB0 or breeze. It takes the place of the rocking of the fan control, which Apple Home mishandles. ${L}`,n),s?xe(e,r,`${se(t.oscillationFanAngles)} its speeds`,!0,n):null)}function ht(e,t){if(!t.hasOscillation)return null;if(t.tiltAngles.length>0)return ct(e,t);let n=h=>g(e,{oscillationControl:h}),s=t.oscillationAngles.length>0,r=t.oscillationFanAngles.length>0,l=e.oscillationControl==="select"&&!s||e.oscillationControl==="fan"&&!r?"switch":e.oscillationControl;return M("Oscillation","",p("oscillation","native",l,"Native rocking",`The left and right rocking of the fan control, as Matter has it. ${z}`,n),p("oscillation","switch",l,"A switch",`${f(e,"Oscillation",!0)}. On sweeps the angle set in the MyDyson app. It takes the place of the rocking of the fan control, which Apple Home mishandles.`,n),s?p("oscillation","select",l,"A mode select",`${f(e,"Oscillation",!0)}, and picks off or one of ${se(t.oscillationAngles)}. It takes the place of the rocking of the fan control, which Apple Home mishandles. ${L}`,n):null,r?xe(e,l,`the first speed standing it still and ${se(t.oscillationFanAngles)} the speeds above it`,t.oscillationAngles.includes("breeze"),n):null)}function ut(e){let t=n=>g(e,{nightModeControl:n});return M("Night mode","In its night mode the machine runs quieter and dims its display.",p("night-mode","native",e.nightModeControl,"Native sleep wind only",`The sleep wind of the fan control, as Matter has it. ${z}`,t),p("night-mode","switch",e.nightModeControl,"A switch",`${f(e,"Night Mode",!0)}. The night mode stays the sleep wind of the fan control as well.`,t))}function pt(e,t){if(!t.hasAirflowDirection)return null;let n=s=>g(e,{airflowDirectionControl:s});return M("Airflow direction","The machine blows its air out of the front, or out of the back to circulate the air without cooling you.",p("airflow","native",e.airflowDirectionControl,"Native airflow direction only",`The airflow direction of the fan control, as Matter has it: forward is the front. ${z}`,n),p("airflow","switch",e.airflowDirectionControl,"A switch",`${f(e,"Front Airflow",!0)}. On blows the air out of the front, off out of the back. The airflow direction of the fan control stays as well.`,n))}function dt(e,t){if(!t.hasFindAndFollow)return null;let n=s=>g(e,{findAndFollowControl:s});return M("Find and follow","The machine finds the people in the room and turns to follow them. It runs only while the machine is on, so turning it on turns the machine on.",p("find-and-follow","switch",e.findAndFollowControl,"A switch",`${f(e,"Find and Follow",!0)}. On follows the people the machine finds.`,n),p("find-and-follow","select",e.findAndFollowControl,"A mode select",`${f(e,"Find and Follow",!0)}, and picks off, follow, or scan to look for people. ${L}`,n),p("find-and-follow","hidden",e.findAndFollowControl,"Not shown in Matter","Find and follow stays as the MyDyson app sets it.",n))}function ft(e,t){if(!t.hasHeat)return null;let n=s=>g(e,{heatControl:s});return M("Heating","The machine heats only while it is on, so turning the heating on turns the machine on.",p("heat","thermostat",e.heatControl,"A thermostat",`${f(e,"Heater",!1)}: it heats or is off, with a setpoint from 1 to 37 \xB0C. Off leaves the fan running.`,n),p("heat","switch",e.heatControl,"A switch",`${f(e,"Heat",!0)}. It heats to the setpoint set in the MyDyson app.`,n),p("heat","hidden",e.heatControl,"Not shown in Matter","The heating stays as the MyDyson app sets it.",n))}function mt(e,t){if(!t.hasHumidification)return null;let n=a=>g(e,{humidificationControl:a}),s=t.hasAutoHumidification?' With a second switch, "Auto Humidify", for the machine to pick the target itself.':"",r=t.hasAutoHumidification?"off, auto, or a target from 30 to 70 %":"off, or a target from 30 to 70 %";return M("Humidifying","Matter has no humidifier, so the humidifying takes a switch or a mode select. It runs only while the machine is on, so turning it on turns the machine on.",p("humidify","switches",e.humidificationControl,"Switches",`${f(e,"Humidify",!0)}, and humidifies to the target set in the MyDyson app.${s}`,n),p("humidify","select",e.humidificationControl,"A mode select",`${f(e,"Target Humidity",!0)}, and picks ${r}. ${L}`,n),p("humidify","hidden",e.humidificationControl,"Not shown in Matter","The humidifying stays as the MyDyson app sets it.",n))}var Ae={hasChildLockSwitch:{label:"Child Lock",text:"On locks the buttons of the machine.",isSelect:!1},hasUvLightSwitch:{label:"UV Light",text:"On turns the UV light of the machine on.",isSelect:!1},hasIoniserSwitch:{label:"Ioniser",text:"On turns the ioniser of the machine on.",isSelect:!1},hasJetFocusSwitch:{label:"Jet Focus",text:"On focuses the air into one stream, and off diffuses it.",isSelect:!1},hasIndirectAirflowSwitch:{label:"Indirect Airflow",text:"On tilts the nozzle to 45\xB0, and off points it straight.",isSelect:!1},hasAirQualityTargetSelect:{label:"Air Quality Target",text:"picks the air quality the auto mode aims for: good, sensitive or very sensitive.",isSelect:!0},hasWickDryingSwitch:{label:"Wick Drying",text:"On dries the wick of the humidifier.",isSelect:!1},hasWickDryingSpeedSelect:{label:"Wick Drying Speed",text:"picks how fast the wick dries: slow, normal or fast, in 60, 45 or 30 minutes.",isSelect:!0}};function gt(e,t){let n=Object.keys(Ae).filter(s=>t.settingControls.includes(s));return n.length===0?null:o("section",{},o("h2",{},"More controls"),o("p",{class:"help"},"Controls of this machine that Matter has no place for. As in the MyDyson app, most of them turn a machine that is off on: the air quality target and the wick drying do not."),o("div",{class:"choices"},...n.map(s=>{let{label:r,text:a,isSelect:l}=Ae[s],h=`Add a ${l?"mode select":"switch"} for the ${r.toLowerCase().replace("uv","UV")}`,d=l?`${f(e,r,!0)}, and ${a} ${L}`:`${f(e,r,!0)}. ${a}`;return x(s,e[s],h,d,v=>g(e,{[s]:v}))})))}function yt(e,t){return t.settingControls.includes("hasWaterHardnessSelect")?o("section",{},o("h2",{},"Clean cycle"),o("p",{class:"help"},"The machine asks for a clean cycle as often as the hardness of its water says: the harder the water, the sooner. The setup page shows when the next one is due."),o("div",{class:"choices"},x("water-hardness",e.hasWaterHardnessSelect,"Add a mode select for the water hardness",`${f(e,"Water Hardness",!0)}, and picks soft, medium or hard. ${L}`,n=>g(e,{hasWaterHardnessSelect:n})))):null}var wt=[["pm25","PM2.5"],["pm10","PM10"],["volatileOrganicCompounds","VOC"],["nitrogenDioxide","NO\u2082"],["formaldehyde","formaldehyde"],["carbonDioxide","CO\u2082"]];function ke(e,t="and"){return e.length===1?e[0]:`${e.slice(0,-1).join(", ")} ${t} ${e[e.length-1]}`}function St(e,t){let n=e.reportedSensors??ce,s=wt.filter(([h])=>n[h]).map(([,h])=>h),r=s.length===0?"each pollutant the machine measures, once it gives it":`the ${ke(s)} the machine measures`,a=[n.temperature?"":"room temperature",n.humidity?"":"humidity"].filter(h=>h!==""),l=a.length===0?"Each sensor shows what the machine measures.":`This machine has given no ${ke(a,"or")} reading, so there is no sensor for ${a.length===1?"it":"them"}. If it gives one later, open its options again.`;return o("section",{},o("h2",{},"Readings"),o("p",{class:"help"},l),o("div",{class:"choices"},t.hasAirQualitySensor?x("air-quality",e.hasAirQualitySensor,"Add an air quality sensor",`${f(e,"Air Quality",!1)}, with the air quality of its worst pollutant and ${r}.`,h=>g(e,{hasAirQualitySensor:h})):null,n.temperature?x("temperature",e.hasTemperatureSensor,"Add a temperature sensor",`${f(e,"Temperature",!1)}, with the room temperature the machine measures.`,h=>g(e,{hasTemperatureSensor:h})):null,n.humidity?x("humidity",e.hasHumiditySensor,"Add a humidity sensor",`${f(e,"Humidity",!1)}, with the room humidity the machine measures.`,h=>g(e,{hasHumiditySensor:h})):null,t.hasContinuousMonitoring?x("continuous-monitoring",e.hasContinuousMonitoringSwitch,"Add a switch for the continuous monitoring",`${f(e,"Continuous Monitoring",!0)}. On keeps the machine measuring the air while it is off, so that the readings stay fresh.`,h=>g(e,{hasContinuousMonitoringSwitch:h})):null))}function vt(e){let t=e.probe?.isFoundOnLan;return t===void 0?"":t?" It answered on your network just now.":" It was not found on your network just now."}function Ct(e){let t=o("input",{type:"text",id:"device-host",value:e.host,placeholder:"Found on your network",maxLength:253,autocomplete:"off",spellcheck:!1});return t.addEventListener("input",()=>{e.host=t.value.trim()}),o("div",{class:"field"},o("label",{for:"device-host"},"Host"),o("p",{class:"help"},"Where the search does not reach the machine, such as in a Docker container without host networking, enter its IP address, ideally one your router keeps for it. Leave it empty to search."),o("div",{class:"inline-form"},t))}function bt(e){let t=s=>{e.isCloudOnly=s==="cloud",c()},n=e.isCloudOnly?"cloud":"lan";return o("section",{},o("h2",{id:"connection-title"},"Connection"),o("p",{class:"help"},`How the plugin reaches the machine.${vt(e)}`),o("div",{class:"choices",role:"radiogroup","aria-labelledby":"connection-title"},p("connection","lan",n,"Your network, then the Dyson cloud","The plugin finds the machine on your network by the service it announces there, and reaches it through the Dyson cloud when it does not answer. It searches again on each connection, so it follows the machine to a new IP address.",t),p("connection","cloud",n,"The Dyson cloud only","For a machine that is not on the network of Matterbridge: each connection skips the search of up to 3 seconds.",t)),e.isCloudOnly?null:Ct(e))}function At(e){let t=e.deviceType==="airPurifier"?"the air purifier":"the fan";return o("section",{},o("h2",{},"One device or several"),o("p",{class:"help"},`The thermostat, the switches, the mode selects and the sensors can be parts of ${t} in Matter, or devices of their own beside it.`),o("div",{class:"choices"},x("composed",e.isComposed,`Make them parts of ${t}`,`Your Matter app has one device, "${e.name}", and they move between rooms with it. A part has no name of its own in Matter, so an app may show the switches as numbered outlets, and an app that shows no parts shows ${t} alone. Turned off, each is a device of its own with its own name, which your app puts in a room by itself.`,n=>g(e,{isComposed:n}))))}function Me(){let e=b();if(e===void 0)return[w()];let t=e.controls??he;return[E(),B(),U(e,`Options for the ${j(e.machine)}. Power and fan speed always appear in Matter: choose how the rest appears.`),st(e),rt(e,t),at(e,t),lt(e,t),ht(e,t),ut(e),pt(e,t),dt(e,t),ft(e,t),mt(e,t),yt(e,t),gt(e,t),St(e,t),At(e),bt(e),e.isNew?null:o("p",{class:"help"},"Changing any of these but the name and the connection takes effect at the next restart of Matterbridge, which this page and Matterbridge ask for. Until then the machine keeps its current controls."),w(),o("div",{class:"footer"},u("Cancel",S),u(e.isNew?`Add ${e.name}`:"Save options",()=>{me(e)},"primary"))]}var kt="An account in mainland China is on a Dyson cloud of its own: pick China for it.";function xt(){let e=i.signIn.country===""?"US":i.signIn.country,t=i.countries.length===0?o("input",{type:"text",id:"country",value:e,maxLength:2,autocomplete:"country",spellcheck:!1}):o("select",{id:"country"},...i.countries.map(n=>o("option",{value:n,selected:n===e},Mt(n))));return o("div",{class:"field"},o("label",{for:"country"},"Country of the account"),o("p",{class:"help"},kt),t)}function Mt(e){try{return new Intl.DisplayNames(void 0,{type:"region"}).of(e)??e}catch{return e}}function Dt(){let e=o("input",{type:"email",id:"email",value:i.signIn.email,autocomplete:"username",spellcheck:!1}),t=()=>{let n=document.getElementById("country"),s=n instanceof HTMLInputElement||n instanceof HTMLSelectElement?n.value.trim().toUpperCase():"";e.value.trim()===""||s===""||ee(e.value.trim(),s)};return[o("div",{class:"field"},o("label",{for:"email"},"Email address"),o("p",{class:"help"},"The address you sign in to the MyDyson app with. Dyson emails it a code, unless the account signs in with its password alone."),e),xt(),o("div",{class:"footer"},u("Cancel",S),u("Continue",t,"primary"))]}function De(e){return o("div",{class:"field"},o("label",{for:"password"},"Password of the account"),o("p",{class:"help"},"It is used once to sign in and is not stored."),e)}function Te(){return u("Use another address",()=>{i.signIn={email:i.signIn.email,country:i.signIn.country},i.error="",c()})}function Tt(){let e=o("input",{type:"text",id:"otp-code",inputMode:"numeric",autocomplete:"one-time-code",spellcheck:!1,maxLength:16}),t=o("input",{type:"password",id:"password",autocomplete:"current-password",maxLength:256}),n=()=>{e.value.trim()===""||t.value===""||te(t.value,e.value.trim())};return t.addEventListener("keydown",s=>{s.key==="Enter"&&n()}),[o("p",{class:"lead"},`Dyson emailed a code to ${i.signIn.email}. It can take a minute to arrive.`),o("div",{class:"field"},o("label",{for:"otp-code"},"Code from the email"),e),De(t),o("div",{class:"footer"},Te(),u("Send a new code",()=>{ee(i.signIn.email,i.signIn.country)}),u("Sign in",n,"primary"))]}function $t(){let e=o("input",{type:"password",id:"password",autocomplete:"current-password",maxLength:256}),t=()=>{e.value!==""&&te(e.value)};return e.addEventListener("keydown",n=>{n.key==="Enter"&&t()}),[o("p",{class:"lead"},`${i.signIn.email} signs in to Dyson with its password alone.`),De(e),o("div",{class:"footer"},Te(),u("Sign in",t,"primary"))]}function $e(){let e=i.setup?.account!==void 0;return[o("header",{},o("h1",{},e?"Sign in to Dyson again":"Sign in to Dyson")),o("p",{class:"lead"},"Dyson machines are reached through the Dyson cloud, with your Dyson account. The plugin keeps the sign-in Dyson gives it in its configuration, and never your password."),w(),...i.signIn.challengeId===void 0?Dt():i.signIn.challengeId===null?$t():Tt()]}function Ie(){try{if(window.parent===window)return;let e=getComputedStyle(window.parent.document.body);for(let t of["--primary-color","--div-bg-color","--div-text-color","--main-bg-color","--main-label-color","--div-border-color","--main-light-color"]){let n=e.getPropertyValue(t).trim();n!==""&&document.documentElement.style.setProperty(`--host${t.slice(1)}`,n)}document.documentElement.style.setProperty("--host-font",e.fontFamily)}catch{}}pe({loading:()=>[o("p",{class:"notice working",role:"status"},o("span",{class:"spinner"}),"Loading\u2026")],home:be,signIn:$e,find:Ce,connect:we,options:Me,done:Se});Ie();c();W();
2
2
  /**
3
3
  * @file apps/frontend/src/api.ts
4
4
  * @description What the plugin's API sends and takes, and the call to it: see src/setup/setupApi.ts in the plugin.
5
5
  * @author Calvin Che
6
6
  * @created 2026-10-19
7
- * @version 1.0.0
7
+ * @version 1.1.0
8
8
  * @license Apache-2.0
9
9
  *
10
10
  * Copyright 2026 Calvin Che.
@@ -115,7 +115,7 @@ async function y(e,t,n){try{let[s,r]=e.split(" "),a={method:s};t!==void 0&&(a.he
115
115
  * @description What the setup page does: each action calls the plugin, changes the state of the page and redraws it.
116
116
  * @author Calvin Che
117
117
  * @created 2026-10-19
118
- * @version 1.1.0
118
+ * @version 1.2.0
119
119
  * @license Apache-2.0
120
120
  *
121
121
  * Copyright 2026 Calvin Che.