matterbridge-dyson-care 0.5.0 → 0.6.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 +17 -0
- package/README.md +8 -6
- package/apps/frontend/build/app.js +2 -2
- package/apps/frontend/build/app.js.map +3 -3
- package/dist/accessory/dysonAccessory.js +2 -1
- package/dist/accessory/endpoints.js +3 -2
- package/dist/accessory/layout.js +2 -1
- package/dist/accessory/matterState.js +1 -1
- package/dist/accessory/pendingCommand.js +7 -4
- package/dist/controls.js +1 -0
- package/dist/errors.js +2 -0
- package/dist/mapping.js +32 -5
- package/dist/module.js +3 -3
- package/dist/setup/setupApi.js +2 -0
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ 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.6.0] - 2026-10-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- [setup]: Sign in with the password alone to an account that Dyson emails no code.
|
|
12
|
+
- [devices]: Show the air quality of a machine of the first generation, from its dust and its VOC.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- [devices]: Update to dyson-control 0.9.0.
|
|
17
|
+
- [devices]: Offer only the setpoints a heater takes on its thermostat: 11 to 32 °C on a 635 or a 918.
|
|
18
|
+
- [setup]: Name the first button of the sign in Continue, as Dyson may email no code.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- [devices]: Turn the oscillation on with the angles or the preset the machine was last at, as dyson-control 0.9.0 requires.
|
|
23
|
+
|
|
7
24
|
## [0.5.0] - 2026-10-25
|
|
8
25
|
|
|
9
26
|
### Added
|
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ Then set up your machines in the plugin's own page: see [Guided setup](#guided-s
|
|
|
50
50
|
|
|
51
51
|
Open the plugin frontend from the plugin's row on the Matterbridge home page. The first time, it asks you to sign in:
|
|
52
52
|
|
|
53
|
-
- **Sign in.** Enter the email address and the country of your Dyson account, the one you use in the MyDyson app. Dyson emails you a code: enter it with your password. The password is used once and not stored.
|
|
53
|
+
- **Sign in.** Enter the email address and the country of your Dyson account, the one you use in the MyDyson app. Dyson emails you a code: enter it with your password. An account that signs in with its password alone gets no code, and is asked for the password only. The password is used once and not stored.
|
|
54
54
|
|
|
55
55
|
Then it walks through three steps:
|
|
56
56
|
|
|
@@ -263,11 +263,11 @@ A machine that follows people, such as the Purifier Cool of variant N, finds the
|
|
|
263
263
|
|
|
264
264
|
On a heater, the heating appears in one of these places. It runs only while the machine is on, so turning it on turns the machine on.
|
|
265
265
|
|
|
266
|
-
| Option | In Matter
|
|
267
|
-
| ------------ |
|
|
268
|
-
| A thermostat | A thermostat that heats, with a setpoint from 1 to 37 °C in whole degrees, named `<name> Heater` as a device of its own. Off turns the heating off and leaves the fan running |
|
|
269
|
-
| A switch | A switch named `<name> Heat`. It heats to the setpoint set in the MyDyson app
|
|
270
|
-
| Not shown | The heating stays as the MyDyson app sets it
|
|
266
|
+
| Option | In Matter |
|
|
267
|
+
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
268
|
+
| A thermostat | A thermostat that heats, with a setpoint from 1 to 37 °C in whole degrees, or 11 to 32 °C on a 635 or a 918, named `<name> Heater` as a device of its own. Off turns the heating off and leaves the fan running |
|
|
269
|
+
| A switch | A switch named `<name> Heat`. It heats to the setpoint set in the MyDyson app |
|
|
270
|
+
| Not shown | The heating stays as the MyDyson app sets it |
|
|
271
271
|
|
|
272
272
|
The thermostat shows it heating while the heater runs, as the machine says. A machine that does not say so shows it heating while the room is below the setpoint. A setpoint changed in Matter turns the heating on, as it does in the MyDyson app. On a heater that shows a temperature in °C or °F, the thermostat has that unit as its display mode, which a Matter app can set.
|
|
273
273
|
|
|
@@ -341,6 +341,8 @@ Dyson rates the air from good to severe on one index for every pollutant, and Ma
|
|
|
341
341
|
| 11 to 13 | Extremely poor | Very poor | Critical |
|
|
342
342
|
| 13 and over | Severe | Extremely poor | Critical |
|
|
343
343
|
|
|
344
|
+
A machine of the first generation, such as the Pure Cool Link, rates its dust and its VOC from 0 to 9 on the same index, so it reaches very poor at 9 and tells no band apart above that. Its dust counts towards the air quality, and has no measurement of its own: Matter has none for dust.
|
|
345
|
+
|
|
344
346
|
- **No machine says which readings it has**, beyond whether it measures the air, so the plugin goes by what the machine reads. The setup page offers a temperature or a humidity sensor only once the machine has given that reading, so a machine without them, such as the HushJet Purifier Compact, is offered neither. A sensor gets its place in Matter the first time the machine gives its reading: at once for one it gave during setup, and otherwise at the next restart of Matterbridge after the reading comes, which the setup page and Matterbridge ask for.
|
|
345
347
|
- **A reading keeps its place from then on**, so a sensor does not come and go in your Matter app.
|
|
346
348
|
- **A reading the machine does not give right now shows no value**, instead of the last one it gave.
|
|
@@ -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 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:se,hasWickDryingSpeedSelect:$e,hasWaterHardnessSelect:Ie,hasContinuousMonitoringSwitch:Ee,hasAirQualitySensor:He,hasTemperatureSensor:Ne,hasHumiditySensor:Le,isComposed:Fe}=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:se,hasWickDryingSpeedSelect:$e,hasWaterHardnessSelect:Ie,hasContinuousMonitoringSwitch:Ee,hasAirQualitySensor:He,hasTemperatureSensor:Ne,hasHumiditySensor:Le,isComposed:Fe}}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 Oe={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},Re=32;function Pe(e,t){if(e===""||!t.has(e))return e;for(let n=2;;n++){let s=` ${n}`,r=`${e.slice(0,Re-s.length).trimEnd()}${s}`;if(!t.has(r))return r}}function re(e){let t=new Set((i.setup?.devices??[]).map(n=>n.name));return e.map(n=>{let s=Pe(n.name??"",t);return t.add(s),{machine:n,name:s,host:"",isCloudOnly:!1,isNew:!0,...L(Oe)}})}function ae(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 le={temperature:!1,humidity:!1,airQuality:!1,pm25:!1,pm10:!1,volatileOrganicCompounds:!1,nitrogenDioxide:!1,formaldehyde:!1,carbonDioxide:!1},ce={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 he(e,t,n){return JSON.stringify(Z(e,n))===JSON.stringify(F(t,n))}var X;function ue(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(),je()}async function je(){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 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 pe(e,t){let{email:n,country:s,challengeId:r}=i.signIn,a=await k("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}O(a.state),i.signIn={email:n,country:s},a.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=[],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 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}),te()}async function me(e){let t=e.name.trim(),n=We(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 te()}function We(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.",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. ${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 Qe(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:Qe(e),o("div",{class:"footer"},t?u("Try again",()=>{ne(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"},oe),o("div",{class:"footer"},u("Done",S,"primary"))]}function ve(e){return!e.isConfigured&&e.support==="supported"}function Ue(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 Be(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}`),...Ue(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(Be)):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(re(e)),"primary",e.length===0))]}var Ke={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 ze(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=>Ke[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 Je={filter:"Filter",outerFilter:"HEPA filter",innerFilter:"Carbon filter"};function Ve(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 Ge(e){let t=e.filters.map(({filter:a,lifeInPercent:l})=>`${Je[a]} ${l} %`).join(", "),n=e.cleanCycle===void 0?"":Ve(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 Ye(e){return e.replaceAll(/([a-z])([A-Z])/g,"$1 $2").toLowerCase()}function Ze(e){if(e===void 0)return[];let t=Ge(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(Ye).join(", ")}.`)]}var Xe={local:" on your network",remote:" through the Dyson cloud"};function _e(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 et(e){let n={online:`Online${e.hostType===void 0?"":Xe[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: ${ze(e)}.`),_e(e),...Ze(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([ae(e)]),"quiet",i.setup?.account===void 0),u("Remove",()=>{i.confirmingRemoval=e.serialNumber,h()})))}function tt(){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(),tt(),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(et)),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 nt(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 ot(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(he(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 it(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 st(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 ie(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 rt(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,`${ie(t.oscillationFanAngles)} its speeds`,!0,n):null)}function at(e,t){if(!t.hasOscillation)return null;if(t.tiltAngles.length>0)return rt(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 ${ie(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 ${ie(t.oscillationFanAngles)} the speeds above it`,t.oscillationAngles.includes("breeze"),n):null)}function lt(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 ct(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 ht(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 ut(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 pt(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 dt(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 ft(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 mt=[["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 gt(e,t){let n=e.reportedSensors??le,s=mt.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 yt(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 wt(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 St(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.${yt(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:wt(e))}function vt(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??ce;return[H(),U(),B(e,`Options for the ${j(e.machine)}. Power and fan speed always appear in Matter: choose how the rest appears.`),nt(e),ot(e,t),it(e,t),st(e,t),at(e,t),lt(e),ct(e,t),ht(e,t),ut(e,t),pt(e,t),ft(e,t),dt(e,t),gt(e,t),vt(e),St(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 Ct="An account in mainland China is on a Dyson cloud of its own: pick China for it.";function bt(){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},At(n))));return o("div",{class:"field"},o("label",{for:"country"},"Country of the account"),o("p",{class:"help"},Ct),t)}function At(e){try{return new Intl.DisplayNames(void 0,{type:"region"}).of(e)??e}catch{return e}}function kt(){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),bt(),o("div",{class:"footer"},u("Cancel",S),u("Email me a code",t,"primary"))]}function xt(){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===""||pe(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 De(){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?kt():xt()]}function Te(){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{}}ue({loading:()=>[o("p",{class:"notice working",role:"status"},o("span",{class:"spinner"}),"Loading\u2026")],home:be,signIn:De,find:Ce,connect:we,options:Me,done:Se});Te();h();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: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();
|
|
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.
|
|
@@ -288,7 +288,7 @@ async function y(e,t,n){try{let[s,r]=e.split(" "),a={method:s};t!==void 0&&(a.he
|
|
|
288
288
|
*/
|
|
289
289
|
/**
|
|
290
290
|
* @file apps/frontend/src/screens/signIn.ts
|
|
291
|
-
* @description The Sign in step: the address and the country of the Dyson account, then the code Dyson emails and the password.
|
|
291
|
+
* @description The Sign in step: the address and the country of the Dyson account, then the code Dyson emails and the password, or the password alone.
|
|
292
292
|
* @author Calvin Che
|
|
293
293
|
* @created 2026-10-19
|
|
294
294
|
* @version 1.0.0
|