matterbridge-dyson-care 0.3.0 → 0.4.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,18 @@ 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.4.0] - 2026-10-20
8
+
9
+ ### Added
10
+
11
+ - [setup]: Set a machine to the Dyson cloud only in the Connection of its options, so that each connection skips the search of the network, which takes up to 3 seconds when the machine is not there. The Connect step searches the network for a new machine beside its check through the cloud, and starts one it does not find on the cloud only.
12
+
13
+ ### Changed
14
+
15
+ - [devices]: Find each machine on the local network by the service it announces over mDNS, with dyson-control 0.8.2, in place of its name `<serialNumber>.local`. The search runs again on each connection, so the plugin follows a machine its router gives a new IP address, and it needs no Avahi on Linux. A machine the search does not find in 3 seconds is reached through the Dyson cloud. A host given in the setup page is still used in place of the search.
16
+ - [setup]: The Connection field of the options leaves the host empty to search the network, and a machine online through the cloud alone says that it was not found on the network, or did not answer at the host given.
17
+ - [deps]: Update dyson-control to 0.8.2.
18
+
7
19
  ## [0.3.0] - 2026-10-20
8
20
 
9
21
  ### Added
package/README.md CHANGED
@@ -56,9 +56,9 @@ Then it walks through three steps:
56
56
 
57
57
  1. **Find.** The page lists the machines of your account and marks those already set up. It says why it cannot control one: a robot vacuum or another machine that is not a purifier, a fan, a heater or a humidifier, a machine of a type dyson-control does not know yet, or one reached through a Dyson cloud service it cannot connect to yet.
58
58
  2. **Connect.** For each machine you picked, the plugin checks that it answers through the Dyson cloud, and reads which sensors it has.
59
- 3. **Options.** The name the machine gets in Matter, starting from its room name in the MyDyson app, whether it appears as an air purifier or a fan, and how the fan, the oscillation or the tilt, the night mode, the airflow direction, the find and follow, the heating, the humidifying, the clean cycle, the [more controls](#more-controls) and the readings appear in Matter, from what the machine can do, and whether they are one device or several. Start from a [preset](#presets) or choose each one yourself. Last, the host the machine is reached at on your network, left empty for the name it gives itself: see [Connection](#connection).
59
+ 3. **Options.** The name the machine gets in Matter, starting from its room name in the MyDyson app, whether it appears as an air purifier or a fan, and how the fan, the oscillation or the tilt, the night mode, the airflow direction, the find and follow, the heating, the humidifying, the clean cycle, the [more controls](#more-controls) and the readings appear in Matter, from what the machine can do, and whether they are one device or several. Start from a [preset](#presets) or choose each one yourself. Last, whether the machine is reached on your network first or through the Dyson cloud only, and the host it is reached at on your network, left empty to search the network for it: see [Connection](#connection).
60
60
 
61
- Afterwards the same page lists your machines with their status: online on your network or through the Dyson cloud, connecting while a machine just added or rebuilt connects, not answering, or not started. A machine online through the cloud alone says that it did not answer on your network, and at which host. Under each machine it shows how it appears in Matter, the life left in its filters, its clean cycle, such as when the next one is due or how long one that runs has left, how long the drying of its wick has left, and the faults it reports, such as a filter to replace or an empty tank. From there you can add more, change the options of one, reconnect one that stopped answering, remove one, switch to another account, sign in again when the Dyson cloud no longer accepts the sign-in, or sign out. None of this needs a Matterbridge restart.
61
+ Afterwards the same page lists your machines with their status: online on your network or through the Dyson cloud, connecting while a machine just added or rebuilt connects, not answering, or not started. A machine online through the cloud alone says that it was not found on your network, or did not answer at the host given. Under each machine it shows how it appears in Matter, the life left in its filters, its clean cycle, such as when the next one is due or how long one that runs has left, how long the drying of its wick has left, and the faults it reports, such as a filter to replace or an empty tank. From there you can add more, change the options of one, reconnect one that stopped answering, remove one, switch to another account, sign in again when the Dyson cloud no longer accepts the sign-in, or sign out. None of this needs a Matterbridge restart.
62
62
 
63
63
  <table>
64
64
  <tr>
@@ -360,11 +360,12 @@ Changing the choice builds the machine again in Matter, so a Matter app takes it
360
360
 
361
361
  ## Connection
362
362
 
363
- Each machine runs a broker of its own on the local network, which the MyDyson app talks to when it is on the same network. The plugin connects to it first, and to the Dyson cloud's when it does not answer, as [dyson-control](https://github.com/cheziyi/dyson-control#lan-connection) does given both:
363
+ Each machine runs a broker of its own on the local network, which the MyDyson app talks to when it is on the same network. The plugin connects to it first, and to the Dyson cloud's when it does not answer, as [dyson-control](https://github.com/cheziyi/dyson-control#lan-connection) does given an account:
364
364
 
365
- - **The address.** A machine names itself `<serialNumber>.local` over mDNS, such as `NK6-EU-MHA0001A.local`, which macOS and Windows resolve, and Linux with Avahi and `nss-mdns`. Where it does not resolve, such as in a Docker container on the default bridge network, enter the IP address of the machine in its options in the setup page, ideally one your router keeps for it. A new host connects the machine again, without building it again in Matter.
365
+ - **The search.** A machine announces its broker over mDNS, and the plugin finds it there on each connection, a reconnect included, so it follows a machine its router gives a new IP address. It needs no Avahi on Linux. Multicast does not reach a Docker container on the default bridge network: run Matterbridge with `--network host`, or enter the IP address of the machine in its options in the setup page, ideally one your router keeps for it. A machine given a host is no longer searched for, and a new host connects the machine again, without building it again in Matter.
366
366
  - **The password.** The machine list of the account gives the password of each machine's broker, which is stored with the device and never shown in the setup page. A device stored by an earlier version gets it at the next read of the machine list, at the start. Until then, and for a machine the cloud gives none for, it is reached through the cloud alone.
367
- - **The fallback.** Each connection, a reconnect included, tries the network first and opens the cloud's when the machine's own broker does not answer, refuses the password or its name does not resolve. A machine on the network that does not answer costs up to 10 seconds before the cloud, and macOS gives up on a `.local` name after 5 seconds. A reconnect tries the network again, so a machine that fell back to the cloud goes back to the network once it answers there.
367
+ - **The fallback.** Each connection, a reconnect included, tries the network first and opens the cloud's when the search finds nothing in 3 seconds, or the machine's own broker does not answer, refuses the password or its host does not resolve. A host given that does not answer costs up to 10 seconds before the cloud. A reconnect tries the network again, so a machine that fell back to the cloud goes back to the network once it answers there.
368
+ - **The cloud only.** A machine can be set to the Dyson cloud alone in its options, so that each connection skips the search, for a machine that is never on the network of Matterbridge. The **Connect** step of the guided setup searches the network for a new machine beside its check through the cloud, and starts one it does not find on the cloud only. Choose your network in its options to search for it again.
368
369
  - **The status.** The setup page shows whether each machine is online on your network or through the Dyson cloud, and the log says which one each connection goes through when it changes.
369
370
 
370
371
  The Dyson account is still needed: it gives the machine list and the password, and signing out stops every machine, on the network as well. The **Connect** step of the guided setup checks each machine through the cloud.
@@ -1,4 +1,4 @@
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 h=await fetch(new URL(`api/${r}${n===void 0?"":`?${n}`}`,document.baseURI),a);if(h.status===204)return{ok:!0};if(h.status===503)return{ok:!1,error:"The plugin is not running. Start it in Matterbridge, then reload this page."};let l=await h.json();return"ok"in l?l:{ok:!1,error:l.error??`The plugin answered with status ${h.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 z(e){return e.productName??`Dyson machine of type ${e.type}`}function j(e){let t=`${z(e)} ${e.serialNumber}`;return e.name===void 0?t:`${t}, named "${e.name}" in the MyDyson app`}var G=["hasChildLockSwitch","hasUvLightSwitch","hasIoniserSwitch","hasJetFocusSwitch","hasIndirectAirflowSwitch","hasAirQualityTargetSelect","hasWickDryingSwitch","hasWickDryingSpeedSelect","hasWaterHardnessSelect"];function L(e){let{deviceType:t,fanControl:n,fanAutoControl:s,oscillationControl:r,nightModeControl:a,airflowDirectionControl:h,heatControl:l,humidificationControl:p,findAndFollowControl:v,hasChildLockSwitch:C,hasUvLightSwitch:T,hasIoniserSwitch:P,hasJetFocusSwitch:m,hasIndirectAirflowSwitch:A,hasAirQualityTargetSelect:oe,hasWickDryingSwitch:ke,hasWickDryingSpeedSelect:Me,hasWaterHardnessSelect:De,hasContinuousMonitoringSwitch:Te,hasAirQualitySensor:$e,hasTemperatureSensor:Ee,hasHumiditySensor:Ne,isComposed:He}=e;return{deviceType:t,fanControl:n,fanAutoControl:s,oscillationControl:r,nightModeControl:a,airflowDirectionControl:h,heatControl:l,humidificationControl:p,findAndFollowControl:v,hasChildLockSwitch:C,hasUvLightSwitch:T,hasIoniserSwitch:P,hasJetFocusSwitch:m,hasIndirectAirflowSwitch:A,hasAirQualityTargetSelect:oe,hasWickDryingSwitch:ke,hasWickDryingSpeedSelect:Me,hasWaterHardnessSelect:De,hasContinuousMonitoringSwitch:Te,hasAirQualitySensor:$e,hasTemperatureSensor:Ee,hasHumiditySensor:Ne,isComposed:He}}function Y(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.hasHeat||(n.heatControl="hidden"),t.hasHumidification||(n.humidificationControl="hidden"),t.hasFindAndFollow||(n.findAndFollowControl="hidden");for(let r of G)n[r]&&=t.settingControls.includes(r);return n.hasContinuousMonitoringSwitch&&=t.hasContinuousMonitoring,n.hasAirQualitySensor&&=t.hasAirQualitySensor,n}var Ie={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},Le=32;function Re(e,t){if(e===""||!t.has(e))return e;for(let n=2;;n++){let s=` ${n}`,r=`${e.slice(0,Le-s.length).trimEnd()}${s}`;if(!t.has(r))return r}}function ie(e){let t=new Set((i.setup?.devices??[]).map(n=>n.name));return e.map(n=>{let s=Re(n.name??"",t);return t.add(s),{machine:n,name:s,host:"",isNew:!0,...L(Ie)}})}function se(e){let{capabilities:t,controls:n,reportedSensors:s,name:r,roomName:a,host:h,serialNumber:l,type:p,variant:v,mqttRootTopicLevel:C,model:T,productName:P,firmwareVersion:m,firmwareCapabilities:A}=e;return{machine:{serialNumber:l,type:p,category:"environmentalCare",name:a,variant:v,mqttRootTopicLevel:C,model:T,productName:P,firmwareVersion:m,firmwareCapabilities:A},name:r,host:h??"",isNew:!1,capabilities:t,controls:n,reportedSensors:s,...L(e)}}var re={temperature:!1,humidity:!1,airQuality:!1,pm25:!1,pm10:!1,volatileOrganicCompounds:!1,nitrogenDioxide:!1,formaldehyde:!1,carbonDioxide:!1},ae={hasAutoMode:!1,hasOscillation:!1,oscillationAngles:[],tiltAngles:[],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 R(e,t){let n=e==="matter",s=n?"native":"switch";return Y({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 le(e,t,n){return JSON.stringify(Y(e,n))===JSON.stringify(R(t,n))}var Z;function ce(e){Z=e}function c(){let e=document.getElementById("app");if(e===null||Z===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,h=new Map;for(let p of e.querySelectorAll("input[type=text], input[type=email], input[type=password]"))p.id!==""&&h.set(p.id,p.value);if(e.replaceChildren(...Z[i.screen]().filter(p=>p!==!1&&p!==null&&p!==void 0)),e.dataset.form=t,!n)return;for(let[p,v]of h){let C=document.getElementById(p);C instanceof HTMLInputElement&&(C.value=v)}if(r==="")return;let l=document.getElementById(r);l?.focus(),a!==void 0&&l instanceof HTMLInputElement&&l.setSelectionRange(a.start,a.end)}async function x(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(F(e.state),e.state.account===void 0){await E();return}if(e.state.devices.length===0&&!e.state.account.needsSignIn){await N();return}i.screen="home",c(),Fe()}async function Fe(){let e=await y("POST sync");!e.ok||JSON.stringify(e.state)===JSON.stringify(i.setup)||(F(e.state),c())}var Oe=2e3,X;function F(e){i.setup=e,!(X!==void 0||!e.devices.some(t=>t.status==="connecting"))&&(X=window.setTimeout(()=>{X=void 0,k().then(c)},Oe))}async function k(){let e=await y("GET state");e.ok&&F(e.state)}function S(){i.queue=[],i.saved=[],i.handled=[],i.error="",i.info="",i.screen="home",k().then(c),c()}async function E(){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 _(e,t){i.signIn={email:e,country:t};let n=await x(`Asking Dyson to email a code to ${e}\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 he(e,t){let{email:n,country:s,challengeId:r}=i.signIn,a=await x("Signing in\u2026",async()=>y("POST login",{email:n,country:s,challengeId:r,password:e,otpCode:t}));if(!a.ok){i.error=a.error,c();return}F(a.state),i.signIn={email:n,country:s},a.state.devices.length===0?await N():S()}async function ue(){let e=await x("Signing out\u2026",async()=>y("DELETE account"));i.confirmingSignOut=!1,e.ok||(i.error=e.error),await k(),c()}async function N(){i.screen="find";let e=await x("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 k(),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=[],ee()}async function ee(){let e=b();if(e===void 0){i.screen=i.saved.length>0?"done":"home",await k(),c();return}i.screen="connect",i.error="",i.info="",e.probe=void 0,c(),await te(e)}async function te(e){let t=await x(`Connecting to ${$(e)} through the Dyson cloud\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 k(),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,R("apple",e.controls)),i.screen="options"}c()}function pe(){let e=i.queue.shift();e!==void 0&&i.handled.push({label:$(e),isSaved:!1}),ee()}async function de(e){let t=e.name.trim(),n=Pe(e,t);if(n!==""){i.error=n,c();return}let{machine:s}=e,r={...s,name:t,roomName:s.name,host:e.host===""?void 0:e.host,...L(e)},a=await x(`Saving ${t}\u2026`,async()=>y("POST device",{device:r}));if(!a.ok){i.error=a.error,c();return}F(a.state),i.saved.push(t),i.handled.push({label:t,isSaved:!0}),i.queue.shift(),await ee()}function Pe(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 ne="Matterbridge's own list of devices shows the change once you reload its page.";async function fe(e){let t=await x(`Removing ${e.name}\u2026`,async()=>y("DELETE device",void 0,`serialNumber=${encodeURIComponent(e.serialNumber)}`));i.confirmingRemoval="",t.ok?i.info=`${e.name} is removed. ${ne}`:i.error=t.error,await k(),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 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 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",()=>{E()},"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 me(){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:je(e),o("div",{class:"footer"},t?u("Try again",()=>{te(e)}):null,u(n?"Skip this one":"Cancel",n?pe:S))]}function ge(){let e=i.saved;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."),o("p",{class:"help"},ne),o("div",{class:"footer"},u("Done",S,"primary"))]}function ye(e){return!e.isConfigured&&e.support==="supported"}function qe(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 We(e){let t=ye(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"},` ${z(e)}, ${e.serialNumber}`),...qe(e).map(r=>o("span",{class:"meta note"},r)))))}function we(){let e=i.machines.filter(n=>i.selected.has(n.serialNumber)&&ye(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(We)):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(ie(e)),"primary",e.length===0))]}var Qe={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"},O={native:"on the fan",switch:"as a switch",select:"as a mode select",hidden:""};function Ue(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],h=[`${r} with speed ${e.fanControl==="steps"?"steps":"as a percentage"}${n("hasAutoMode")?a:""}`],l=new Map,p=(m,A)=>{m!==""&&l.set(m,[...l.get(m)??[],A])};n("hasAutoMode")&&e.fanAutoControl==="switch"&&p(O.switch,"the auto mode"),n("hasOscillation")&&p(O[e.oscillationControl],s?"the tilt":"the oscillation"),n("hasFindAndFollow")&&p(O[e.findAndFollowControl],"the find and follow"),p(O[e.nightModeControl],"the night mode"),n("hasAirflowDirection")&&p(O[e.airflowDirectionControl],"the airflow direction");for(let[m,A]of l)h.push(`${A.join(", ")} ${m}`);n("hasHeat")&&e.heatControl!=="hidden"&&h.push(e.heatControl==="thermostat"?"a thermostat":"a heat switch"),n("hasHumidification")&&e.humidificationControl!=="hidden"&&h.push(e.humidificationControl==="select"?"a target humidity select":"humidify switches");let v=G.filter(m=>e[m]&&(t===void 0||t.settingControls.includes(m))).map(m=>Qe[m]);v.length>0&&h.push(`a control for the ${v.join(", ")}`),n("hasFilterLife")&&e.deviceType==="airPurifier"&&h.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&&h.push(`a sensor for the ${T.join(", ")}`),`${h.length===1?h[0]:`${h.slice(0,-1).join(", ")} and ${h[h.length-1]}`}${e.isComposed?", all as one device":""}`}var Be={filter:"Filter",outerFilter:"HEPA filter",innerFilter:"Carbon filter"};function Ke(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 Je(e){let t=e.filters.map(({filter:a,lifeInPercent:h})=>`${Be[a]} ${h} %`).join(", "),n=e.cleanCycle===void 0?"":Ke(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 Ve(e){return e.replaceAll(/([a-z])([A-Z])/g,"$1 $2").toLowerCase()}function ze(e){if(e===void 0)return[];let t=Je(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(Ve).join(", ")}.`)]}var Ge={local:" on your network",remote:" through the Dyson cloud"};function Ye(e){return e.status!=="online"||e.hostType!=="remote"||e.lanHost===void 0?null:o("p",{class:"meta"},`It did not answer on your network at ${e.lanHost}, so the plugin reaches it through the Dyson cloud. Check the host in its options.`)}function Ze(e){let n={online:`Online${e.hostType===void 0?"":Ge[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: ${Ue(e)}.`),Ye(e),...ze(e.report),e.status!=="online"&&e.error?o("p",{class:"meta problem-text"},e.error):null),s?o("div",{class:"actions"},o("span",{class:"meta"},"Remove it from Matter too?"),u("Remove",()=>{fe(e)},"danger"),u("Keep",()=>{i.confirmingRemoval="",c()})):o("div",{class:"actions"},u(e.status==="online"?"Options":"Reconnect",()=>Q([se(e)]),"quiet",i.setup?.account===void 0),u("Remove",()=>{i.confirmingRemoval=e.serialNumber,c()})))}function Xe(){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",()=>{E()},"primary"));let t=i.confirmingSignOut?[o("span",{class:"meta"},"The machines stay in Matter, unreachable until you sign in again."),u("Sign out",()=>{ue()},"danger"),u("Stay signed in",()=>{i.confirmingSignOut=!1,c()})]:[u(e.needsSignIn?"Sign in again":"Switch account",()=>{E()},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 Se(){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(),Xe(),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(Ze)),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>'},I="Few apps show a mode select: Home Assistant does, Apple Home does not.",V="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 J(e,t,n,s,r,a,h){let l=`${e}-${t}`,p=o("input",{type:"radio",name:e,id:l,checked:t===n,onchange:()=>h(t)});return o("label",{class:"choice pictured",for:l},p,o("span",{class:"glyph",html:a}),o("span",{},o("strong",{},s),o("span",{class:"meta"},r)))}function d(e,t,n,s,r,a){let h=`${e}-${t}`,l=o("input",{type:"radio",name:e,id:h,checked:t===n,onchange:()=>a(t)});return o("label",{class:"choice",for:h},l,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 _e(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 et(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(le(e,n,t)),onclick:()=>g(e,R(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 tt(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"},J("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),J("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 nt(e,t){let n=l=>g(e,{fanControl:l}),s=l=>g(e,{fanAutoControl:l}),r=t.hasAutoMode?e.fanAutoControl:"native",h=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"},J("fan","steps",e.fanControl,"Steps",`${h} A slider shows them as a percentage too.`,K.steps,n),J("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"},d("fan-auto","native",r,"Native fan mode only","Auto is a mode of the fan speed, beside its speeds, as Matter has it.",s),d("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),d("fan-auto","firstStep",r,"The first step","Auto comes below speed 1: step 1, or the bottom of the slider.",s),d("fan-auto","lastStep",r,"The last step","Auto comes above speed 10: the top step, or 100 %.",s))):null)}function ot(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 it(e){let t=n=>g(e,{oscillationControl:n});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.",d("oscillation","native",e.oscillationControl,"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. ${V}`,t),d("oscillation","switch",e.oscillationControl,"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.`,t),d("oscillation","select",e.oscillationControl,"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. ${I}`,t))}function st(e,t){if(!t.hasOscillation)return null;if(t.tiltAngles.length>0)return it(e);let n=a=>g(e,{oscillationControl:a}),s=t.oscillationAngles.length>0,r=e.oscillationControl==="select"&&!s?"switch":e.oscillationControl;return D("Oscillation","",d("oscillation","native",r,"Native rocking",`The left and right rocking of the fan control, as Matter has it. ${V}`,n),d("oscillation","switch",r,"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?d("oscillation","select",r,"A mode select",`${f(e,"Oscillation",!0)}, and picks off or one of ${ot(t.oscillationAngles)}. It takes the place of the rocking of the fan control, which Apple Home mishandles. ${I}`,n):null)}function rt(e){let t=n=>g(e,{nightModeControl:n});return D("Night mode","In its night mode the machine runs quieter and dims its display.",d("night-mode","native",e.nightModeControl,"Native sleep wind only",`The sleep wind of the fan control, as Matter has it. ${V}`,t),d("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 at(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.",d("airflow","native",e.airflowDirectionControl,"Native airflow direction only",`The airflow direction of the fan control, as Matter has it: forward is the front. ${V}`,n),d("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 lt(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.",d("find-and-follow","switch",e.findAndFollowControl,"A switch",`${f(e,"Find and Follow",!0)}. On follows the people the machine finds.`,n),d("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. ${I}`,n),d("find-and-follow","hidden",e.findAndFollowControl,"Not shown in Matter","Find and follow stays as the MyDyson app sets it.",n))}function ct(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.",d("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),d("heat","switch",e.heatControl,"A switch",`${f(e,"Heat",!0)}. It heats to the setpoint set in the MyDyson app.`,n),d("heat","hidden",e.heatControl,"Not shown in Matter","The heating stays as the MyDyson app sets it.",n))}function ht(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.",d("humidify","switches",e.humidificationControl,"Switches",`${f(e,"Humidify",!0)}, and humidifies to the target set in the MyDyson app.${s}`,n),d("humidify","select",e.humidificationControl,"A mode select",`${f(e,"Target Humidity",!0)}, and picks ${r}. ${I}`,n),d("humidify","hidden",e.humidificationControl,"Not shown in Matter","The humidifying stays as the MyDyson app sets it.",n))}var ve={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 ut(e,t){let n=Object.keys(ve).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:h}=ve[s],l=`Add a ${h?"mode select":"switch"} for the ${r.toLowerCase().replace("uv","UV")}`,p=h?`${f(e,r,!0)}, and ${a} ${I}`:`${f(e,r,!0)}. ${a}`;return M(s,e[s],l,p,v=>g(e,{[s]:v}))})))}function pt(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. ${I}`,n=>g(e,{hasWaterHardnessSelect:n})))):null}var dt=[["pm25","PM2.5"],["pm10","PM10"],["volatileOrganicCompounds","VOC"],["nitrogenDioxide","NO\u2082"],["formaldehyde","formaldehyde"],["carbonDioxide","CO\u2082"]];function Ce(e,t="and"){return e.length===1?e[0]:`${e.slice(0,-1).join(", ")} ${t} ${e[e.length-1]}`}function ft(e,t){let n=e.reportedSensors??re,s=dt.filter(([l])=>n[l]).map(([,l])=>l),r=s.length===0?"each pollutant the machine measures, once it gives it":`the ${Ce(s)} the machine measures`,a=[n.temperature?"":"room temperature",n.humidity?"":"humidity"].filter(l=>l!==""),h=a.length===0?"Each sensor shows what the machine measures.":`This machine has given no ${Ce(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"},h),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}.`,l=>g(e,{hasAirQualitySensor:l})):null,n.temperature?M("temperature",e.hasTemperatureSensor,"Add a temperature sensor",`${f(e,"Temperature",!1)}, with the room temperature the machine measures.`,l=>g(e,{hasTemperatureSensor:l})):null,n.humidity?M("humidity",e.hasHumiditySensor,"Add a humidity sensor",`${f(e,"Humidity",!1)}, with the room humidity the machine measures.`,l=>g(e,{hasHumiditySensor:l})):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.`,l=>g(e,{hasContinuousMonitoringSwitch:l})):null))}function mt(e){let t=`${e.machine.serialNumber}.local`,n=o("input",{type:"text",id:"device-host",value:e.host,placeholder:t,maxLength:253,autocomplete:"off",spellcheck:!1});return n.addEventListener("input",()=>{e.host=n.value.trim()}),o("section",{},o("h2",{},o("label",{for:"device-host"},"Connection")),o("p",{class:"help"},`The plugin reaches the machine on your network first, at ${t}, the name it gives itself, and through the Dyson cloud when it does not answer there. Where that name does not resolve, such as in a Docker container or on Linux without Avahi, enter the IP address of the machine, ideally one your router keeps for it. Leave it empty to use its own name.`),o("div",{class:"inline-form"},n))}function gt(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 be(){let e=b();if(e===void 0)return[w()];let t=e.controls??ae;return[H(),U(),B(e,`Options for the ${j(e.machine)}. Power and fan speed always appear in Matter: choose how the rest appears.`),_e(e),et(e,t),tt(e,t),nt(e,t),st(e,t),rt(e),at(e,t),lt(e,t),ct(e,t),ht(e,t),pt(e,t),ut(e,t),ft(e,t),gt(e),mt(e),e.isNew?null:o("p",{class:"help"},"Changing any of these but the connection rebuilds this machine in Matter. Your Matter app may take a moment to show the change."),w(),o("div",{class:"footer"},u("Cancel",S),u(e.isNew?`Add ${e.name}`:"Save options",()=>{de(e)},"primary"))]}var yt="An account in mainland China is on a Dyson cloud of its own: pick China for it.";function wt(){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},St(n))));return o("div",{class:"field"},o("label",{for:"country"},"Country of the account"),o("p",{class:"help"},yt),t)}function St(e){try{return new Intl.DisplayNames(void 0,{type:"region"}).of(e)??e}catch{return e}}function vt(){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===""||_(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."),e),wt(),o("div",{class:"footer"},u("Cancel",S),u("Email me a code",t,"primary"))]}function Ct(){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===""||he(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),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."),t),o("div",{class:"footer"},u("Use another address",()=>{i.signIn={email:i.signIn.email,country:i.signIn.country},i.error="",c()}),u("Send a new code",()=>{_(i.signIn.email,i.signIn.country)}),u("Sign in",n,"primary"))]}function Ae(){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?vt():Ct()]}function xe(){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{}}ce({loading:()=>[o("p",{class:"notice working",role:"status"},o("span",{class:"spinner"}),"Loading\u2026")],home:Se,signIn:Ae,find:we,connect:me,options:be,done:ge});xe();c();W();
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:k,hasAirQualityTargetSelect:z,hasWickDryingSwitch:ie,hasWickDryingSpeedSelect:Me,hasWaterHardnessSelect:De,hasContinuousMonitoringSwitch:Te,hasAirQualitySensor:$e,hasTemperatureSensor:Ie,hasHumiditySensor:Ee,isComposed:He}=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:k,hasAirQualityTargetSelect:z,hasWickDryingSwitch:ie,hasWickDryingSpeedSelect:Me,hasWaterHardnessSelect:De,hasContinuousMonitoringSwitch:Te,hasAirQualitySensor:$e,hasTemperatureSensor:Ie,hasHumiditySensor:Ee,isComposed:He}}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.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 Ne={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},Le=32;function Oe(e,t){if(e===""||!t.has(e))return e;for(let n=2;;n++){let s=` ${n}`,r=`${e.slice(0,Le-s.length).trimEnd()}${s}`;if(!t.has(r))return r}}function se(e){let t=new Set((i.setup?.devices??[]).map(n=>n.name));return e.map(n=>{let s=Oe(n.name??"",t);return t.add(s),{machine:n,name:s,host:"",isCloudOnly:!1,isNew:!0,...L(Ne)}})}function re(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:k,firmwareCapabilities:z}=e;return{machine:{serialNumber:d,type:v,category:"environmentalCare",name:a,variant:C,mqttRootTopicLevel:T,model:P,productName:m,firmwareVersion:k,firmwareCapabilities:z},name:r,host:l??"",isCloudOnly:c??!1,isNew:!1,capabilities:t,controls:n,reportedSensors:s,...L(e)}}var ae={temperature:!1,humidity:!1,airQuality:!1,pm25:!1,pm10:!1,volatileOrganicCompounds:!1,nitrogenDioxide:!1,formaldehyde:!1,carbonDioxide:!1},le={hasAutoMode:!1,hasOscillation:!1,oscillationAngles:[],tiltAngles:[],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 ce(e,t,n){return JSON.stringify(Z(e,n))===JSON.stringify(O(t,n))}var X;function he(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 A(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(R(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(),Re()}async function Re(){let e=await y("POST sync");!e.ok||JSON.stringify(e.state)===JSON.stringify(i.setup)||(R(e.state),h())}var Fe=2e3,_;function R(e){i.setup=e,!(_!==void 0||!e.devices.some(t=>t.status==="connecting"))&&(_=window.setTimeout(()=>{_=void 0,x().then(h)},Fe))}async function x(){let e=await y("GET state");e.ok&&R(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 A(`Asking Dyson to email a code to ${e}\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 ue(e,t){let{email:n,country:s,challengeId:r}=i.signIn,a=await A("Signing in\u2026",async()=>y("POST login",{email:n,country:s,challengeId:r,password:e,otpCode:t}));if(!a.ok){i.error=a.error,h();return}R(a.state),i.signIn={email:n,country:s},a.state.devices.length===0?await E():S()}async function pe(){let e=await A("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 A("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=[],te()}async function te(){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 ne(e)}async function ne(e){let t=await A(`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,O("apple",e.controls)),e.isNew&&n&&t.result.isFoundOnLan===!1&&(e.isCloudOnly=!0),i.screen="options"}h()}function de(){let e=i.queue.shift();e!==void 0&&i.handled.push({label:$(e),isSaved:!1}),te()}async function fe(e){let t=e.name.trim(),n=Pe(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 A(`Saving ${t}\u2026`,async()=>y("POST device",{device:a}));if(!l.ok){i.error=l.error,h();return}R(l.state),i.saved.push(t),i.handled.push({label:t,isSaved:!0}),i.queue.shift(),await te()}function Pe(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 oe="Matterbridge's own list of devices shows the change once you reload its page.";async function me(e){let t=await A(`Removing ${e.name}\u2026`,async()=>y("DELETE device",void 0,`serialNumber=${encodeURIComponent(e.serialNumber)}`));i.confirmingRemoval="",t.ok?i.info=`${e.name} is removed. ${oe}`: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 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",()=>{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 ge(){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:je(e),o("div",{class:"footer"},t?u("Try again",()=>{ne(e)}):null,u(n?"Skip this one":"Cancel",n?de:S))]}function ye(){let e=i.saved;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."),o("p",{class:"help"},oe),o("div",{class:"footer"},u("Done",S,"primary"))]}function we(e){return!e.isConfigured&&e.support==="supported"}function qe(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 We(e){let t=we(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}`),...qe(e).map(r=>o("span",{class:"meta note"},r)))))}function Se(){let e=i.machines.filter(n=>i.selected.has(n.serialNumber)&&we(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(We)):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(se(e)),"primary",e.length===0))]}var Qe={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"},F={native:"on the fan",switch:"as a switch",select:"as a mode select",hidden:""};function Ue(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,k)=>{m!==""&&c.set(m,[...c.get(m)??[],k])};n("hasAutoMode")&&e.fanAutoControl==="switch"&&d(F.switch,"the auto mode"),n("hasOscillation")&&d(F[e.oscillationControl],s?"the tilt":"the oscillation"),n("hasFindAndFollow")&&d(F[e.findAndFollowControl],"the find and follow"),d(F[e.nightModeControl],"the night mode"),n("hasAirflowDirection")&&d(F[e.airflowDirectionControl],"the airflow direction");for(let[m,k]of c)l.push(`${k.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=>Qe[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 Be={filter:"Filter",outerFilter:"HEPA filter",innerFilter:"Carbon filter"};function Ke(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 Je(e){let t=e.filters.map(({filter:a,lifeInPercent:l})=>`${Be[a]} ${l} %`).join(", "),n=e.cleanCycle===void 0?"":Ke(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 Ve(e){return e.replaceAll(/([a-z])([A-Z])/g,"$1 $2").toLowerCase()}function ze(e){if(e===void 0)return[];let t=Je(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(Ve).join(", ")}.`)]}var Ge={local:" on your network",remote:" through the Dyson cloud"};function Ye(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 Ze(e){let n={online:`Online${e.hostType===void 0?"":Ge[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: ${Ue(e)}.`),Ye(e),...ze(e.report),e.status!=="online"&&e.error?o("p",{class:"meta problem-text"},e.error):null),s?o("div",{class:"actions"},o("span",{class:"meta"},"Remove it from Matter too?"),u("Remove",()=>{me(e)},"danger"),u("Keep",()=>{i.confirmingRemoval="",h()})):o("div",{class:"actions"},u(e.status==="online"?"Options":"Reconnect",()=>Q([re(e)]),"quiet",i.setup?.account===void 0),u("Remove",()=>{i.confirmingRemoval=e.serialNumber,h()})))}function Xe(){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",()=>{pe()},"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 ve(){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(),Xe(),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(Ze)),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.",V="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 J(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 _e(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 et(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(ce(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 tt(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"},J("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),J("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 nt(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"},J("fan","steps",e.fanControl,"Steps",`${l} A slider shows them as a percentage too.`,K.steps,n),J("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 ot(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 it(e){let t=n=>g(e,{oscillationControl:n});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",e.oscillationControl,"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. ${V}`,t),p("oscillation","switch",e.oscillationControl,"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.`,t),p("oscillation","select",e.oscillationControl,"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}`,t))}function st(e,t){if(!t.hasOscillation)return null;if(t.tiltAngles.length>0)return it(e);let n=a=>g(e,{oscillationControl:a}),s=t.oscillationAngles.length>0,r=e.oscillationControl==="select"&&!s?"switch":e.oscillationControl;return D("Oscillation","",p("oscillation","native",r,"Native rocking",`The left and right rocking of the fan control, as Matter has it. ${V}`,n),p("oscillation","switch",r,"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",r,"A mode select",`${f(e,"Oscillation",!0)}, and picks off or one of ${ot(t.oscillationAngles)}. It takes the place of the rocking of the fan control, which Apple Home mishandles. ${N}`,n):null)}function rt(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. ${V}`,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 at(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. ${V}`,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 lt(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 ct(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 ht(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 Ce={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 ut(e,t){let n=Object.keys(Ce).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}=Ce[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 pt(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 dt=[["pm25","PM2.5"],["pm10","PM10"],["volatileOrganicCompounds","VOC"],["nitrogenDioxide","NO\u2082"],["formaldehyde","formaldehyde"],["carbonDioxide","CO\u2082"]];function be(e,t="and"){return e.length===1?e[0]:`${e.slice(0,-1).join(", ")} ${t} ${e[e.length-1]}`}function ft(e,t){let n=e.reportedSensors??ae,s=dt.filter(([c])=>n[c]).map(([,c])=>c),r=s.length===0?"each pollutant the machine measures, once it gives it":`the ${be(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 ${be(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 mt(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 gt(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 yt(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.${mt(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:gt(e))}function wt(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 ke(){let e=b();if(e===void 0)return[w()];let t=e.controls??le;return[H(),U(),B(e,`Options for the ${j(e.machine)}. Power and fan speed always appear in Matter: choose how the rest appears.`),_e(e),et(e,t),tt(e,t),nt(e,t),st(e,t),rt(e),at(e,t),lt(e,t),ct(e,t),ht(e,t),pt(e,t),ut(e,t),ft(e,t),wt(e),yt(e),e.isNew?null:o("p",{class:"help"},"Changing any of these but the connection rebuilds this machine in Matter. Your Matter app may take a moment to show the change."),w(),o("div",{class:"footer"},u("Cancel",S),u(e.isNew?`Add ${e.name}`:"Save options",()=>{fe(e)},"primary"))]}var St="An account in mainland China is on a Dyson cloud of its own: pick China for it.";function vt(){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},Ct(n))));return o("div",{class:"field"},o("label",{for:"country"},"Country of the account"),o("p",{class:"help"},St),t)}function Ct(e){try{return new Intl.DisplayNames(void 0,{type:"region"}).of(e)??e}catch{return e}}function bt(){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."),e),vt(),o("div",{class:"footer"},u("Cancel",S),u("Email me a code",t,"primary"))]}function kt(){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===""||ue(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),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."),t),o("div",{class:"footer"},u("Use another address",()=>{i.signIn={email:i.signIn.email,country:i.signIn.country},i.error="",h()}),u("Send a new code",()=>{ee(i.signIn.email,i.signIn.country)}),u("Sign in",n,"primary"))]}function Ae(){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?bt():kt()]}function xe(){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{}}he({loading:()=>[o("p",{class:"notice working",role:"status"},o("span",{class:"spinner"}),"Loading\u2026")],home:ve,signIn:Ae,find:Se,connect:ge,options:ke,done:ye});xe();h();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.