Repository: Philip2809/neato-connected Branch: main Commit: ad8388acdbfa Files: 146 Total size: 1.5 MB Directory structure: gitextract_34si2la9/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets/ │ ├── index-BhclUVWI.js │ └── index-DG4tEwHR.css ├── brainslug-tools/ │ ├── eslint.config.js │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── App.css │ │ ├── App.tsx │ │ ├── components/ │ │ │ ├── header.scss │ │ │ └── header.tsx │ │ ├── index.css │ │ ├── logic/ │ │ │ ├── connect.ts │ │ │ ├── parser.ts │ │ │ └── robot.ts │ │ ├── main.tsx │ │ └── pages/ │ │ ├── flasher.scss │ │ ├── flasher.tsx │ │ ├── ha-config.scss │ │ ├── ha-config.tsx │ │ ├── home.scss │ │ ├── home.tsx │ │ ├── robot.scss │ │ ├── robot.tsx │ │ ├── unsupported.scss │ │ └── unsupported.tsx │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── build/ │ ├── build.sh │ ├── dev.sh │ ├── docker-compose.yml │ └── new-version.md ├── config/ │ ├── .gitignore │ ├── boards/ │ │ ├── esp32.yaml │ │ ├── esp32c3.yaml │ │ ├── esp32c6.yaml │ │ ├── esp32s2.yaml │ │ └── esp32s3.yaml │ ├── comp/ │ │ ├── gen2.yaml │ │ ├── gen3.yaml │ │ ├── ha.yaml │ │ ├── no-ha.yaml │ │ ├── ros.yaml │ │ └── webserver.yaml │ ├── home-assistant/ │ │ ├── gen2-card.yaml │ │ ├── gen2-entity.yaml │ │ ├── gen3-entity.yaml │ │ └── gen3_card.yaml │ ├── js/ │ │ ├── 1.2.1.js │ │ └── 1.2.js │ ├── local.yaml │ ├── prebuilt/ │ │ ├── .gitignore │ │ ├── gen2-esp32.yaml │ │ ├── gen2-esp32c3.yaml │ │ ├── gen2-esp32c6.yaml │ │ ├── gen2-esp32s3.yaml │ │ ├── gen3-esp32.yaml │ │ ├── gen3-esp32c3.yaml │ │ ├── gen3-esp32c6.yaml │ │ └── gen3-esp32s3.yaml │ └── remote.yaml ├── docs/ │ ├── 404.html │ ├── CNAME │ ├── assets/ │ │ ├── esp32-DR_yen0A.js │ │ ├── esp32c2-CIvS3qc5.js │ │ ├── esp32c3-nJJ15ppW.js │ │ ├── esp32c5-DbWg37P4.js │ │ ├── esp32c6-snJpA5DJ.js │ │ ├── esp32c61-DUe22z0I.js │ │ ├── esp32h2-CCGwb3vw.js │ │ ├── esp32p4-BE8Lllij.js │ │ ├── esp32s2-klISvvyE.js │ │ ├── esp32s3-CjJ5O_5u.js │ │ ├── esp8266-BCrKhPpV.js │ │ ├── index-C3ZwmNhv.css │ │ ├── index-CCRny9cW.js │ │ ├── install-dialog-nQsOFibo.js │ │ ├── no-port-picked-DMlthFiI.js │ │ ├── rom-D7P4LvQr.js │ │ ├── stub_flasher_32-DMOwp-H2.js │ │ ├── stub_flasher_32c2-BglC1CA-.js │ │ ├── stub_flasher_32c3-DVjTuHxm.js │ │ ├── stub_flasher_32c5-Bw4Gkysk.js │ │ ├── stub_flasher_32c6-Bdx7wUin.js │ │ ├── stub_flasher_32c61-U7D4krFy.js │ │ ├── stub_flasher_32h2-wrcmrOHl.js │ │ ├── stub_flasher_32p4-Q6hgNTYb.js │ │ ├── stub_flasher_32s2-CjuleYts.js │ │ ├── stub_flasher_32s3-Bz348Cp3.js │ │ ├── stub_flasher_8266-BdPZCml8.js │ │ └── styles-Bnf3HqYs.js │ └── index.html ├── faq.md ├── ha-images.md ├── install-esp-device-gen2.md ├── install-esp-device-gen3.md ├── install-ha.md ├── install-no-ha.md ├── manual.md ├── research/ │ ├── all_errors_alerts_states.3.2.0.md │ ├── command-experiments.md │ ├── command_mapping.md │ ├── findings.md │ ├── gen4/ │ │ ├── nmap-D8.md │ │ └── serial-D8.md │ ├── hidden-commands.md │ ├── serial.md │ └── setup-network.md ├── status.md ├── supported-esp32.md └── webserver/ ├── .prettierrc.json ├── LICENSE ├── README.md ├── package.json ├── packages/ │ └── neato/ │ ├── index.html │ ├── package.json │ ├── selector.html │ ├── src/ │ │ ├── api.ts │ │ ├── css/ │ │ │ ├── app.ts │ │ │ ├── button.ts │ │ │ ├── esp-entity-table.ts │ │ │ ├── input.ts │ │ │ ├── reset.ts │ │ │ └── tab.ts │ │ ├── custom-button.ts │ │ ├── custom-table.ts │ │ ├── entity-store.ts │ │ ├── esp-app.ts │ │ ├── esp-log.ts │ │ ├── esp-range-slider.ts │ │ ├── esp-schedule.ts │ │ ├── esp-switch.ts │ │ ├── main.css │ │ ├── main.ts │ │ ├── manual-driving.ts │ │ ├── neato-entry.ts │ │ ├── neato-enums.ts │ │ ├── timezone-selector.ts │ │ ├── types.d.ts │ │ └── utils.ts │ └── vite.config.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: philip2809 tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry polar: # Replace with a single Polar username buy_me_a_coffee: philip2809 # Replace with a single Buy Me a Coffee username thanks_dev: # Replace with a single thanks.dev username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Build results _static/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and *not* Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2025 Philip Magyar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [buymeacoffee]: https://www.buymeacoffee.com/philip2809 [buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png
neato-brainslug Logo

neato-brainslug

_Control your Neato vacuum locally with an ESPHome brainslug_ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/V7V61PBIY6) [![Buy me a coffee][buymeacoffee-shield]][buymeacoffee]

Discord | FAQ | Manual | Beta | Brainslug Tools

Repair your Neato Robot Vacuum to be controlled locally after the shutdown of the Neato servers. The scope of this project is to give your robot at least the same functionallity as when you bought it, however the project is in a development state. The `main` branch only gets updates on releases, check out the `next` branch for the latest updates! Since there is a couple of robots from Neato and they have different firmware versions, they will need different configurations. I have decided they will be grouped based on their "generation". - `gen1` - (Generation 1) - XV11 - XV12 - XV14 - XV21 - XV RS/HP - XV signature - XV signature pro - `gen2` - (Generation 2) - Neato Botvac D70 - Neato Botvac D75 - Neato Botvac D80 - Neato Botvac D85 - Neato Botvac Connected **(Non DX)** `905-0249`, `905-0317` - `gen3` - (Generation 3) - Neato Botvac D3 Connected `905-0321`, `905-0437`, `905-0327` - Neato Botvac D4 Connected `905-0499`, `905-0514` - Neato Botvac D5 Connected `905-0358`, `905-0357`, `905-0402` - Neato Botvac D6 Connected `905-0496`, `905-0517` - Neato Botvac D7 Connected `905-0415`, `905-0537` - `gen 4` (Generation 4) - Sadly not yet supported - Neato D800 (very very likely the same as the d8-d10, not confirmed tho) - Neato D8 Intelligent `905-09596` - Neato D9 Intelligent `905-0559` - Neato D10 Intelligent `905-0724` | Feature | `gen1` | `gen2` | `gen3` | |---|---|---|---| | Start & Stop | ✅ | ✅ | ✅ | | Check errors | ✅ | ✅ | ✅ | | Status | 🟠 | 🟠 | ✅ | | Edit settings | ❌ | ✅ | ✅ | | Scheduling on robot (via screen) | ✅ | ✅ | ❌ | | Scheduling via ESPHome | ✅ | ✅ | ✅ | | Scheduling via HA Automation | ✅ ¹ | ✅ ¹ | ✅ ¹ | | Notifications | ✅ ¹ | ✅ ¹ | ✅ ¹ | | Return to start | ❌ ³ | ✅ | ❌ ³ | | Return to dock | ❌ ³ | ❌ ³ | ✅ | | Manual driving | ❌ ² | ❌ ² | ✅ | - ✅ - Supported - 🟠 - Limited functionality - ❌ - Not supported - ¹ - Home Assistant required - ² - Support can be added by version 1 - ³ - Support can be added by version 2 Please refer to the [status.md](./status.md) for project status, roadmap and version meanings! The ability to create, view and edit floormaps so the robot can get the same functionallity with no-go lines and zones is in the making. The reason this works on all Neato robots is because they all have the command interface, even the robots that originally don't have any WiFi, will still work, since it is no longer WiFi robot that is connecting to the WiFi, it is the ESP device. This also removes any limitations the robots WiFi may have had. **`gen1` robots** - We are currently looking into the best way to connect to these, please join the [Discord](https://discord.gg/PAgwhWvyD8) where we are currently talking about the best way to do it and the experimentation going on! **`gen4` robots** - These robots use a compleatly different board, chip and firmware, and we cannot interface with these directly. However one idea to get the start/stop feature back is to wire an esp32 to the button itself, join the [Discord](https://discord.gg/PAgwhWvyD8) or open an discussion here so we can discuss! # Getting started! For this repair you will need some tools to connect an ESP device, if you already have an ESP device, awesome try using that, but if you don't already have one you should buy one according to what we have found works best. ESPs recommended by ESPHome (`ESP32`, `ESP32S3` and `ESP32C3`) will all work and have prebuilt files for easy installation, however some ESP devices that are very cheap and small should be avoided since they have some defects and need to use a lower power output on the wifi signal. Please check [supported-esp32.md](./supported-esp32.md) for more information! First of all we need to get the version of your robot, do this with the [Brainslug Tools](https://brainslug.phma.dev/#/robot) Since we are parsing the data from the serial interface we need to be on the same verion of the robot firmware, this is what has been confirmed working: | `gen2` | `gen3` | |---|---| | `2.2.0` or `2.2.1` | `4.5.3` or `4.6.0` (note, many `4.X.X` versions will work) | If you have another version then these; please get in touch! There is two ways of using this repair: - [**Without Home Assistant**](./install-no-ha.md) - [**With Home Assistant**](./install-ha.md) **If you don't mind to tinker a little bit I really do recommend to check out Home Assistant and do that route.** It is an open source home automation tool that puts local control and privacy first. Read more about them on their [website](https://www.home-assistant.io/) and try their [live demo](https://demo.home-assistant.io) if you want! There is a lot of great guides and information about home hassistant on youtube and their forums! They also have some amazing guides on their [site](https://www.home-assistant.io/installation/) to get an home assistant installation going. If you have any questions or problems, don't hesitate to ask for help here in the [discusstions](https://github.com/Philip2809/neato-connected/discussions) section, on our [Discord](https://discord.gg/PAgwhWvyD8) or the home assistant [help](https://www.home-assistant.io/help/) page. **From version 2 of this project, when ROS is used for getting a floormap, you will need a separate computer running ROS, and the easiest and prioritized way to set that up will be via Home Assistant.** ## Beta The `main` branch will have the code and guide for the latest release, but in case you want to try the latest beta this version with live on the `next` branch. In case certain features are on their way into the beta, but not in a beta yet, they will live on `next/`. The config files for the betas will live in the `config` folder instead of the releases tab, where the links in the different guides will point to! ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=philip2809/neato-brainslug&type=date&legend=top-left)](https://www.star-history.com/#philip2809/neato-brainslug&type=date&legend=top-left) ## Acknowledgements - @Fabian Ullrich, Jiska Classen, Johannes Eger, Matthias Hollick, Fabian Freyer, crunch from Secure Mobile Networking Lab - [Security and Privacy for IoT Ecosystems](https://tuprints.ulb.tu-darmstadt.de/handle/tuda/4937) - [Vacuums in the Cloud: Analyzing Security in a Hardened IoT Ecosystem](https://www.usenix.org/system/files/woot19-paper_ullrich.pdf) - And all of their work on these robots! - [@jeroenterheerdt](https://github.com/jeroenterheerdt) for testing, reviewing, writing the guide for installing internally and the original [neato-serial](https://github.com/jeroenterheerdt/neato-serial) - [@algaen](https://github.com/algaen) for the info about the D8 (D9, D10?) robots - [@tomwj](https://github.com/tomwj) for testing and pictures installing it internally in a D7 - [@RobertSundling](https://github.com/RobertSundling) for the [firmware files](https://github.com/RobertSundling/neato-botvac) - [@mikeyp] for the amazing logo ================================================ FILE: assets/index-BhclUVWI.js ================================================ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var ee=Array.isArray;function S(){}var C={H:null,A:null,T:null,S:null},w=Object.prototype.hasOwnProperty;function T(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function te(e,t){return T(e.type,t,e.props)}function E(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ne(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var re=/\/+/g;function ie(e,t){return typeof e==`object`&&e&&e.key!=null?ne(``+e.key):t.toString(36)}function ae(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(S,S):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function oe(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,oe(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+ie(e,0):a,ee(o)?(i=``,c!=null&&(i=c.replace(re,`$&/`)+`/`),oe(o,r,i,``,function(e){return e})):o!=null&&(E(o)&&(o=te(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(re,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ee(e))for(var u=0;u{t.exports=l()})),d=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,ee||(ee=!0,E());else{var t=n(l);t!==null&&ie(x,t.startTime-e)}}var ee=!1,S=-1,C=5,w=-1;function T(){return g?!0:!(e.unstable_now()-wt&&T());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&ie(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?E():ee=!1}}}var E;if(typeof y==`function`)E=function(){y(te)};else if(typeof MessageChannel<`u`){var ne=new MessageChannel,re=ne.port2;ne.port1.onmessage=te,E=function(){re.postMessage(null)}}else E=function(){_(te,0)};function ie(t,n){S=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(S),S=-1):h=!0,ie(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,ee||(ee=!0,E()))),r},e.unstable_shouldYield=T,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),f=o(((e,t)=>{t.exports=d()})),p=o((e=>{var t=u();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=p()})),h=o((e=>{var t=f(),n=u(),r=m();function i(e){var t=`https://react.dev/errors/`+e;if(1de||(e.current=ue[de],ue[de]=null,de--)}function k(e,t){de++,ue[de]=e.current,e.current=t}var me=fe(null),he=fe(null),ge=fe(null),_e=fe(null);function ve(e,t){switch(k(ge,t),k(he,e),k(me,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}pe(me),k(me,e)}function ye(){pe(me),pe(he),pe(ge)}function be(e){e.memoizedState!==null&&k(_e,e);var t=me.current,n=Hd(t,e.type);t!==n&&(k(he,e),k(me,n))}function xe(e){he.current===e&&(pe(me),pe(he)),_e.current===e&&(pe(_e),Qf._currentValue=le)}var Se,Ce;function we(e){if(Se===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);Se=t&&t[1]||``,Ce=-1)`:-1i||c[r]!==l[i]){var u=` `+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{Te=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?we(n):``}function De(e,t){switch(e.tag){case 26:case 27:case 5:return we(e.type);case 16:return we(`Lazy`);case 13:return e.child!==t&&t!==null?we(`Suspense Fallback`):we(`Suspense`);case 19:return we(`SuspenseList`);case 0:case 15:return Ee(e.type,!1);case 11:return Ee(e.type.render,!1);case 1:return Ee(e.type,!0);case 31:return we(`Activity`);default:return``}}function Oe(e){try{var t=``,n=null;do t+=De(e,n),n=e,e=e.return;while(e);return t}catch(e){return` Error generating stack: `+e.message+` `+e.stack}}var ke=Object.prototype.hasOwnProperty,Ae=t.unstable_scheduleCallback,je=t.unstable_cancelCallback,Me=t.unstable_shouldYield,Ne=t.unstable_requestPaint,Pe=t.unstable_now,Fe=t.unstable_getCurrentPriorityLevel,Ie=t.unstable_ImmediatePriority,Le=t.unstable_UserBlockingPriority,Re=t.unstable_NormalPriority,ze=t.unstable_LowPriority,Be=t.unstable_IdlePriority,Ve=t.log,He=t.unstable_setDisableYieldValue,Ue=null,We=null;function Ge(e){if(typeof Ve==`function`&&He(e),We&&typeof We.setStrictMode==`function`)try{We.setStrictMode(Ue,e)}catch{}}var A=Math.clz32?Math.clz32:Je,Ke=Math.log,qe=Math.LN2;function Je(e){return e>>>=0,e===0?32:31-(Ke(e)/qe|0)|0}var Ye=256,Xe=262144,Ze=4194304;function Qe(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function $e(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Qe(n))):i=Qe(o):i=Qe(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Qe(n))):i=Qe(o)):i=Qe(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function et(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function tt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function nt(){var e=Ze;return Ze<<=1,!(Ze&62914560)&&(Ze=4194304),e}function rt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function it(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function at(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),vn=!1;if(_n)try{var yn={};Object.defineProperty(yn,`passive`,{get:function(){vn=!0}}),window.addEventListener(`test`,yn,yn),window.removeEventListener(`test`,yn,yn)}catch{vn=!1}var bn=null,xn=null,Sn=null;function Cn(){if(Sn)return Sn;var e,t=xn,n=t.length,r,i=`value`in bn?bn.value:bn.textContent,a=i.length;for(e=0;e=er),rr=` `,ir=!1;function ar(e,t){switch(e){case`keyup`:return Qn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function or(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var sr=!1;function cr(e,t){switch(e){case`compositionend`:return or(t);case`keypress`:return t.which===32?(ir=!0,rr):null;case`textInput`:return e=t.data,e===rr&&ir?null:e;default:return null}}function lr(e,t){if(sr)return e===`compositionend`||!$n&&ar(e,t)?(e=Cn(),Sn=xn=bn=null,sr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=jr(n)}}function Nr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Nr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Pr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Wt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Wt(e.document)}return t}function Fr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Ir=_n&&`documentMode`in document&&11>=document.documentMode,Lr=null,Rr=null,zr=null,Br=!1;function Vr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Br||Lr==null||Lr!==Wt(r)||(r=Lr,`selectionStart`in r&&Fr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),zr&&Ar(zr,r)||(zr=r,r=Ed(Rr,`onSelect`),0>=o,i-=o,Ni=1<<32-A(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),N&&Fi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),N&&Fi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return N&&Fi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),N&&Fi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===E&&Na(l)===r.type){n(e,r.sibling),c=a(r,o.props),Ba(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=bi(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=yi(o.type,o.key,o.props,null,e.mode,c),Ba(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=Ci(o,e.mode,c),c.return=e,e=c}return s(e);case E:return o=Na(o),b(e,r,o,c)}if(ce(o))return h(e,r,o,c);if(ae(o)){if(l=ae(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,za(o),c);if(o.$$typeof===S)return b(e,r,ca(e,o),c);Va(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=xi(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Ra=0;var i=b(e,t,n,r);return La=null,i}catch(t){if(t===Da||t===ka)throw t;var a=hi(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Ua=Ha(!0),Wa=Ha(!1),Ga=!1;function Ka(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function qa(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ja(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ya(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=fi(e),di(e,null,n),t}return ci(e,r,t,n),fi(e)}function Xa(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}function Za(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Qa=!1;function $a(){if(Qa){var e=va;if(e!==null)throw e}}function eo(e,t,n,r){Qa=!1;var i=e.updateQueue;Ga=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(J&f)===f:(r&f)===f){f!==0&&f===_a&&(Qa=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:Ga=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Gl|=o,e.lanes=o,e.memoizedState=d}}function to(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function no(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=D.T,s={};D.T=s,Ls(e,!1,t,n);try{var c=i(),l=D.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Is(e,t,xa(c,r),pu(e)):Is(e,t,r,pu(e))}catch(n){Is(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{O.p=a,o!==null&&s.types!==null&&(o.types=s.types),D.T=o}}function Es(){}function Ds(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Os(e).queue;Ts(e,a,t,le,n===null?Es:function(){return ks(e),n(r)})}function Os(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:le,baseState:le,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ro,lastRenderedState:le},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ro,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function ks(e){var t=Os(e);t.next===null&&(t=e.alternate.memoizedState),Is(e,t.next.queue,{},pu())}function As(){return sa(Qf)}function js(){return B().memoizedState}function Ms(){return B().memoizedState}function Ns(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=Ja(n);var r=Ya(t,e,n);r!==null&&(hu(r,t,n),Xa(r,t,n)),t={cache:pa()},e.payload=t;return}t=t.return}}function Ps(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Rs(e)?zs(t,n):(n=li(e,t,n,r),n!==null&&(hu(n,e,r),Bs(n,t,r)))}function Fs(e,t,n){Is(e,t,n,pu())}function Is(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Rs(e))zs(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,kr(s,o))return ci(e,t,i,0),K===null&&si(),!1}catch{}if(n=li(e,t,i,r),n!==null)return hu(n,e,r),Bs(n,t,r),!0}return!1}function Ls(e,t,n,r){if(r={lane:2,revertLane:dd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Rs(e)){if(t)throw Error(i(479))}else t=li(e,n,r,2),t!==null&&hu(t,e,2)}function Rs(e){var t=e.alternate;return e===I||t!==null&&t===I}function zs(e,t){yo=vo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Bs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}var Vs={readContext:sa,use:Io,useCallback:z,useContext:z,useEffect:z,useImperativeHandle:z,useLayoutEffect:z,useInsertionEffect:z,useMemo:z,useReducer:z,useRef:z,useState:z,useDebugValue:z,useDeferredValue:z,useTransition:z,useSyncExternalStore:z,useId:z,useHostTransitionStatus:z,useFormState:z,useActionState:z,useOptimistic:z,useMemoCache:z,useCacheRefresh:z};Vs.useEffectEvent=z;var Hs={readContext:sa,use:Io,useCallback:function(e,t){return No().memoizedState=[e,t===void 0?null:t],e},useContext:sa,useEffect:fs,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),us(4194308,4,vs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return us(4194308,4,e,t)},useInsertionEffect:function(e,t){us(4,2,e,t)},useMemo:function(e,t){var n=No();t=t===void 0?null:t;var r=e();if(bo){Ge(!0);try{e()}finally{Ge(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=No();if(n!==void 0){var i=n(t);if(bo){Ge(!0);try{n(t)}finally{Ge(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ps.bind(null,I,e),[r.memoizedState,e]},useRef:function(e){var t=No();return e={current:e},t.memoizedState=e},useState:function(e){e=Jo(e);var t=e.queue,n=Fs.bind(null,I,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:bs,useDeferredValue:function(e,t){return Cs(No(),e,t)},useTransition:function(){var e=Jo(!1);return e=Ts.bind(null,I,e.queue,!0,!1),No().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=I,a=No();if(N){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),K===null)throw Error(i(349));J&127||Uo(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,fs(Go.bind(null,r,o,e),[e]),r.flags|=2048,cs(9,{destroy:void 0},Wo.bind(null,r,o,n,t),null),n},useId:function(){var e=No(),t=K.identifierPrefix;if(N){var n=Pi,r=Ni;n=(r&~(1<<32-A(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=xo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[mt]=t,o[ht]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Pd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Fc(t)}}return H(t),Ic(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Fc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=ge.current,qi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Bi,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[mt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Md(e.nodeValue,n)),e||Wi(t,!0)}else e=Bd(e).createTextNode(r),e[mt]=t,t.stateNode=e}return H(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=qi(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[mt]=t}else Ji(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;H(t),e=!1}else n=Yi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(ho(t),t):(ho(t),null);if(t.flags&128)throw Error(i(558))}return H(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=qi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[mt]=t}else Ji(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;H(t),a=!1}else a=Yi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(ho(t),t):(ho(t),null)}return ho(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Rc(t,t.updateQueue),H(t),null);case 4:return ye(),e===null&&Sd(t.stateNode.containerInfo),H(t),null;case 10:return ta(t.type),H(t),null;case 19:if(pe(F),r=t.memoizedState,r===null)return H(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)zc(r,!1);else{if(X!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=go(e),o!==null){for(t.flags|=128,zc(r,!1),e=o.updateQueue,t.updateQueue=e,Rc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)vi(n,e),n=n.sibling;return k(F,F.current&1|2),N&&Fi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Pe()>tu&&(t.flags|=128,a=!0,zc(r,!1),t.lanes=4194304)}else{if(!a)if(e=go(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Rc(t,e),zc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!N)return H(t),null}else 2*Pe()-r.renderingStartTime>tu&&n!==536870912&&(t.flags|=128,a=!0,zc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(H(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Pe(),e.sibling=null,n=F.current,k(F,a?n&1|2:n&1),N&&Fi(t,r.treeForkCount),e);case 22:case 23:return ho(t),so(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(H(t),t.subtreeFlags&6&&(t.flags|=8192)):H(t),n=t.updateQueue,n!==null&&Rc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&pe(Ca),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),ta(P),H(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Vc(e,t){switch(Ri(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return ta(P),ye(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return xe(t),null;case 31:if(t.memoizedState!==null){if(ho(t),t.alternate===null)throw Error(i(340));Ji()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(ho(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Ji()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return pe(F),null;case 4:return ye(),null;case 10:return ta(t.type),null;case 22:case 23:return ho(t),so(),e!==null&&pe(Ca),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return ta(P),null;case 25:return null;default:return null}}function Hc(e,t){switch(Ri(t),t.tag){case 3:ta(P),ye();break;case 26:case 27:case 5:xe(t);break;case 4:ye();break;case 31:t.memoizedState!==null&&ho(t);break;case 13:ho(t);break;case 19:pe(F);break;case 10:ta(t.type);break;case 22:case 23:ho(t),so(),e!==null&&pe(Ca);break;case 24:ta(P)}}function Uc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Wc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function Gc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{no(t,n)}catch(t){Z(e,e.return,t)}}}function Kc(e,t,n){n.props=Ys(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function qc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function Jc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}function Yc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Z(e,e.return,t)}}function Xc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[ht]=t}catch(t){Z(e,e.return,t)}}function Zc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function Qc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Zc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function $c(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=cn));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for($c(e,t,n),e=e.sibling;e!==null;)$c(e,t,n),e=e.sibling}function el(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(el(e,t,n),e=e.sibling;e!==null;)el(e,t,n),e=e.sibling}function tl(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[mt]=e,t[ht]=n}catch(t){Z(e,e.return,t)}}var nl=!1,U=!1,rl=!1,il=typeof WeakSet==`function`?WeakSet:Set,al=null;function ol(e,t){if(e=e.containerInfo,Rd=sp,e=Pr(e),Fr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,al=t;al!==null;)if(t=al,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,al=e;else for(;al!==null;){switch(t=al,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(o,r,n),o[mt]=e,Dt(o),r=o;break a;case`link`:var s=Vf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Mr(s,h),v=Mr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,D.T=null,n=lu,lu=null;var o=au,s=su;if(iu=0,ou=au=null,su=0,G&6)throw Error(i(331));var c=G;if(G|=4,Fl(o.current),Dl(o,o.current,s,n),G=c,id(0,!1),We&&typeof We.onPostCommitFiberRoot==`function`)try{We.onPostCommitFiberRoot(Ue,o)}catch{}return!0}finally{O.p=a,D.T=r,Vu(e,t)}}function Wu(e,t,n){t=Ti(n,t),t=tc(e.stateNode,t,2),e=Ya(e,t,2),e!==null&&(it(e,2),rd(e))}function Z(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(ru===null||!ru.has(r))){e=Ti(n,e),n=nc(2),r=Ya(t,n,2),r!==null&&(rc(n,r,t,e),it(r,2),rd(r));break}}t=t.return}}function Gu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new zl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Ul=!0,i.add(n),e=Ku.bind(null,e,t,n),t.then(e,e))}function Ku(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,K===e&&(J&n)===n&&(X===4||X===3&&(J&62914560)===J&&300>Pe()-$l?!(G&2)&&Su(e,0):ql|=n,Yl===J&&(Yl=0)),rd(e)}function qu(e,t){t===0&&(t=nt()),e=ui(e,t),e!==null&&(it(e,t),rd(e))}function Ju(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function Yu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),qu(e,n)}function Xu(e,t){return Ae(e,t)}var Zu=null,Qu=null,$u=!1,ed=!1,td=!1,nd=0;function rd(e){e!==Qu&&e.next===null&&(Qu===null?Zu=Qu=e:Qu=Qu.next=e),ed=!0,$u||($u=!0,ud())}function id(e,t){if(!td&&ed){td=!0;do for(var n=!1,r=Zu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-A(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ld(r,a))}else a=J,a=$e(r,r===K?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||et(r,a)||(n=!0,ld(r,a));r=r.next}while(n);td=!1}}function ad(){od()}function od(){ed=$u=!1;var e=0;nd!==0&&Gd()&&(e=nd);for(var t=Pe(),n=null,r=Zu;r!==null;){var i=r.next,a=sd(r,t);a===0?(r.next=null,n===null?Zu=i:n.next=i,i===null&&(Qu=n)):(n=r,(e!==0||a&3)&&(ed=!0)),r=i}iu!==0&&iu!==5||id(e,!1),nd!==0&&(nd=0)}function sd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Kt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),Dt(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Kt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Kt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Kt(n.imageSizes)+`"]`)):i+=`[href="`+Kt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),Dt(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Kt(r)+`"][href="`+Kt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),Dt(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=Et(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);Dt(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Dt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Dt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var a=(a=ge.current)?gf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=Et(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=Et(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=Et(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Af(e){return`href="`+Kt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),Dt(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Kt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Kt(n.href)+`"]`);if(r)return t.instance=r,Dt(r),r;var a=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Dt(r),Pd(r,`style`,a),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Af(n.href);var o=e.querySelector(jf(a));if(o)return t.state.loading|=4,t.instance=o,Dt(o),o;r=Mf(n),(a=mf.get(a))&&Rf(r,a),o=(e.ownerDocument||e).createElement(`link`),Dt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(a=e.querySelector(Ff(o)))?(t.instance=a,Dt(a),a):(r=n,(a=mf.get(o))&&(r=h({},n),zf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),Dt(a),Pd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Dt(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),Dt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()}))(),_=c(u(),1),v=`modulepreload`,y=function(e){return`/`+e},b={},x=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=y(t,n),t in b)return;b[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:v,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},ee=`popstate`;function S(e){return typeof e==`object`&&!!e&&`pathname`in e&&`search`in e&&`hash`in e&&`state`in e&&`key`in e}function C(e={}){function t(e,t){let{pathname:n=`/`,search:r=``,hash:i=``}=ie(e.location.hash.substring(1));return!n.startsWith(`/`)&&!n.startsWith(`.`)&&(n=`/`+n),ne(``,{pathname:n,search:r,hash:i},t.state&&t.state.usr||null,t.state&&t.state.key||`default`)}function n(e,t){let n=e.document.querySelector(`base`),r=``;if(n&&n.getAttribute(`href`)){let t=e.location.href,n=t.indexOf(`#`);r=n===-1?t:t.slice(0,n)}return r+`#`+(typeof t==`string`?t:re(t))}function r(e,t){T(e.pathname.charAt(0)===`/`,`relative pathnames are not supported in hash history.push(${JSON.stringify(t)})`)}return ae(t,n,r,e)}function w(e,t){if(e===!1||e==null)throw Error(t)}function T(e,t){if(!e){typeof console<`u`&&console.warn(t);try{throw Error(t)}catch{}}}function te(){return Math.random().toString(36).substring(2,10)}function E(e,t){return{usr:e.state,key:e.key,idx:t,masked:e.unstable_mask?{pathname:e.pathname,search:e.search,hash:e.hash}:void 0}}function ne(e,t,n=null,r,i){return{pathname:typeof e==`string`?e:e.pathname,search:``,hash:``,...typeof t==`string`?ie(t):t,state:n,key:t&&t.key||r||te(),unstable_mask:i}}function re({pathname:e=`/`,search:t=``,hash:n=``}){return t&&t!==`?`&&(e+=t.charAt(0)===`?`?t:`?`+t),n&&n!==`#`&&(e+=n.charAt(0)===`#`?n:`#`+n),e}function ie(e){let t={};if(e){let n=e.indexOf(`#`);n>=0&&(t.hash=e.substring(n),e=e.substring(0,n));let r=e.indexOf(`?`);r>=0&&(t.search=e.substring(r),e=e.substring(0,r)),e&&(t.pathname=e)}return t}function ae(e,t,n,r={}){let{window:i=document.defaultView,v5Compat:a=!1}=r,o=i.history,s=`POP`,c=null,l=u();l??(l=0,o.replaceState({...o.state,idx:l},``));function u(){return(o.state||{idx:null}).idx}function d(){s=`POP`;let e=u(),t=e==null?null:e-l;l=e,c&&c({action:s,location:h.location,delta:t})}function f(e,t){s=`PUSH`;let r=S(e)?e:ne(h.location,e,t);n&&n(r,e),l=u()+1;let d=E(r,l),f=h.createHref(r.unstable_mask||r);try{o.pushState(d,``,f)}catch(e){if(e instanceof DOMException&&e.name===`DataCloneError`)throw e;i.location.assign(f)}a&&c&&c({action:s,location:h.location,delta:1})}function p(e,t){s=`REPLACE`;let r=S(e)?e:ne(h.location,e,t);n&&n(r,e),l=u();let i=E(r,l),d=h.createHref(r.unstable_mask||r);o.replaceState(i,``,d),a&&c&&c({action:s,location:h.location,delta:0})}function m(e){return oe(e)}let h={get action(){return s},get location(){return e(i,o)},listen(e){if(c)throw Error(`A history only accepts one active listener`);return i.addEventListener(ee,d),c=e,()=>{i.removeEventListener(ee,d),c=null}},createHref(e){return t(i,e)},createURL:m,encodeLocation(e){let t=m(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:f,replace:p,go(e){return o.go(e)}};return h}function oe(e,t=!1){let n=`http://localhost`;typeof window<`u`&&(n=window.location.origin===`null`?window.location.href:window.location.origin),w(n,`No window.location.(origin|href) available to create URL`);let r=typeof e==`string`?e:re(e);return r=r.replace(/ $/,`%20`),!t&&r.startsWith(`//`)&&(r=n+r),new URL(r,n)}function se(e,t,n=`/`){return ce(e,t,n,!1)}function ce(e,t,n,r){let i=Ce((typeof t==`string`?ie(t):t).pathname||`/`,n);if(i==null)return null;let a=O(e);ue(a);let o=null;for(let e=0;o==null&&e{let c={relativePath:s===void 0?e.path||``:s,caseSensitive:e.caseSensitive===!0,childrenIndex:a,route:e};if(c.relativePath.startsWith(`/`)){if(!c.relativePath.startsWith(r)&&o)return;w(c.relativePath.startsWith(r),`Absolute route path "${c.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),c.relativePath=c.relativePath.slice(r.length)}let l=je([r,c.relativePath]),u=n.concat(c);e.children&&e.children.length>0&&(w(e.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${l}".`),O(e.children,t,u,l,o)),!(e.path==null&&!e.index)&&t.push({path:l,score:_e(l,e.index),routesMeta:u})};return e.forEach((e,t)=>{if(e.path===``||!e.path?.includes(`?`))a(e,t);else for(let n of le(e.path))a(e,t,!0,n)}),t}function le(e){let t=e.split(`/`);if(t.length===0)return[];let[n,...r]=t,i=n.endsWith(`?`),a=n.replace(/\?$/,``);if(r.length===0)return i?[a,``]:[a];let o=le(r.join(`/`)),s=[];return s.push(...o.map(e=>e===``?a:[a,e].join(`/`))),i&&s.push(...o),s.map(t=>e.startsWith(`/`)&&t===``?`/`:t)}function ue(e){e.sort((e,t)=>e.score===t.score?ve(e.routesMeta.map(e=>e.childrenIndex),t.routesMeta.map(e=>e.childrenIndex)):t.score-e.score)}var de=/^:[\w-]+$/,fe=3,pe=2,k=1,me=10,he=-2,ge=e=>e===`*`;function _e(e,t){let n=e.split(`/`),r=n.length;return n.some(ge)&&(r+=he),t&&(r+=pe),n.filter(e=>!ge(e)).reduce((e,t)=>e+(de.test(t)?fe:t===``?k:me),r)}function ve(e,t){return e.length===t.length&&e.slice(0,-1).every((e,n)=>e===t[n])?e[e.length-1]-t[t.length-1]:0}function ye(e,t,n=!1){let{routesMeta:r}=e,i={},a=`/`,o=[];for(let e=0;e{if(t===`*`){let e=s[r]||``;o=a.slice(0,a.length-e.length).replace(/(.)\/+$/,`$1`)}let i=s[r];return n&&!i?e[t]=void 0:e[t]=(i||``).replace(/%2F/g,`/`),e},{}),pathname:a,pathnameBase:o,pattern:e}}function xe(e,t=!1,n=!0){T(e===`*`||!e.endsWith(`*`)||e.endsWith(`/*`),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,`/*`)}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,`/*`)}".`);let r=[],i=`^`+e.replace(/\/*\*?$/,``).replace(/^\/*/,`/`).replace(/[\\.*+^${}|()[\]]/g,`\\$&`).replace(/\/:([\w-]+)(\?)?/g,(e,t,n,i,a)=>{if(r.push({paramName:t,isOptional:n!=null}),n){let t=a.charAt(i+e.length);return t&&t!==`/`?`/([^\\/]*)`:`(?:/([^\\/]*))?`}return`/([^\\/]+)`}).replace(/\/([\w-]+)\?(\/|$)/g,`(/$1)?$2`);return e.endsWith(`*`)?(r.push({paramName:`*`}),i+=e===`*`||e===`/*`?`(.*)$`:`(?:\\/(.+)|\\/*)$`):n?i+=`\\/*$`:e!==``&&e!==`/`&&(i+=`(?:(?=\\/|$))`),[new RegExp(i,t?void 0:`i`),r]}function Se(e){try{return e.split(`/`).map(e=>decodeURIComponent(e).replace(/\//g,`%2F`)).join(`/`)}catch(t){return T(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function Ce(e,t){if(t===`/`)return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith(`/`)?t.length-1:t.length,r=e.charAt(n);return r&&r!==`/`?null:e.slice(n)||`/`}var we=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function Te(e,t=`/`){let{pathname:n,search:r=``,hash:i=``}=typeof e==`string`?ie(e):e,a;return n?(n=n.replace(/\/\/+/g,`/`),a=n.startsWith(`/`)?Ee(n.substring(1),`/`):Ee(n,t)):a=t,{pathname:a,search:Ne(r),hash:Pe(i)}}function Ee(e,t){let n=t.replace(/\/+$/,``).split(`/`);return e.split(`/`).forEach(e=>{e===`..`?n.length>1&&n.pop():e!==`.`&&n.push(e)}),n.length>1?n.join(`/`):`/`}function De(e,t,n,r){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function Oe(e){return e.filter((e,t)=>t===0||e.route.path&&e.route.path.length>0)}function ke(e){let t=Oe(e);return t.map((e,n)=>n===t.length-1?e.pathname:e.pathnameBase)}function Ae(e,t,n,r=!1){let i;typeof e==`string`?i=ie(e):(i={...e},w(!i.pathname||!i.pathname.includes(`?`),De(`?`,`pathname`,`search`,i)),w(!i.pathname||!i.pathname.includes(`#`),De(`#`,`pathname`,`hash`,i)),w(!i.search||!i.search.includes(`#`),De(`#`,`search`,`hash`,i)));let a=e===``||i.pathname===``,o=a?`/`:i.pathname,s;if(o==null)s=n;else{let e=t.length-1;if(!r&&o.startsWith(`..`)){let t=o.split(`/`);for(;t[0]===`..`;)t.shift(),--e;i.pathname=t.join(`/`)}s=e>=0?t[e]:`/`}let c=Te(i,s),l=o&&o!==`/`&&o.endsWith(`/`),u=(a||o===`.`)&&n.endsWith(`/`);return!c.pathname.endsWith(`/`)&&(l||u)&&(c.pathname+=`/`),c}var je=e=>e.join(`/`).replace(/\/\/+/g,`/`),Me=e=>e.replace(/\/+$/,``).replace(/^\/*/,`/`),Ne=e=>!e||e===`?`?``:e.startsWith(`?`)?e:`?`+e,Pe=e=>!e||e===`#`?``:e.startsWith(`#`)?e:`#`+e,Fe=class{constructor(e,t,n,r=!1){this.status=e,this.statusText=t||``,this.internal=r,n instanceof Error?(this.data=n.toString(),this.error=n):this.data=n}};function Ie(e){return e!=null&&typeof e.status==`number`&&typeof e.statusText==`string`&&typeof e.internal==`boolean`&&`data`in e}function Le(e){return e.map(e=>e.route.path).filter(Boolean).join(`/`).replace(/\/\/*/g,`/`)||`/`}var Re=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;function ze(e,t){let n=e;if(typeof n!=`string`||!we.test(n))return{absoluteURL:void 0,isExternal:!1,to:n};let r=n,i=!1;if(Re)try{let e=new URL(window.location.href),r=n.startsWith(`//`)?new URL(e.protocol+n):new URL(n),a=Ce(r.pathname,t);r.origin===e.origin&&a!=null?n=a+r.search+r.hash:i=!0}catch{T(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:i,to:n}}Object.getOwnPropertyNames(Object.prototype).sort().join(`\0`);var Be=_.createContext(null);Be.displayName=`DataRouter`;var Ve=_.createContext(null);Ve.displayName=`DataRouterState`;var He=_.createContext(!1),Ue=_.createContext({isTransitioning:!1});Ue.displayName=`ViewTransition`;var We=_.createContext(new Map);We.displayName=`Fetchers`;var Ge=_.createContext(null);Ge.displayName=`Await`;var A=_.createContext(null);A.displayName=`Navigation`;var Ke=_.createContext(null);Ke.displayName=`Location`;var qe=_.createContext({outlet:null,matches:[],isDataRoute:!1});qe.displayName=`Route`;var Je=_.createContext(null);Je.displayName=`RouteError`;var Ye=`REACT_ROUTER_ERROR`,Xe=`REDIRECT`,Ze=`ROUTE_ERROR_RESPONSE`;function Qe(e){if(e.startsWith(`${Ye}:${Xe}:{`))try{let t=JSON.parse(e.slice(28));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`&&typeof t.location==`string`&&typeof t.reloadDocument==`boolean`&&typeof t.replace==`boolean`)return t}catch{}}function $e(e){if(e.startsWith(`${Ye}:${Ze}:{`))try{let t=JSON.parse(e.slice(40));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`)return new Fe(t.status,t.statusText,t.data)}catch{}}function et(e,{relative:t}={}){w(tt(),`useHref() may be used only in the context of a component.`);let{basename:n,navigator:r}=_.useContext(A),{hash:i,pathname:a,search:o}=st(e,{relative:t}),s=a;return n!==`/`&&(s=a===`/`?n:je([n,a])),r.createHref({pathname:s,search:o,hash:i})}function tt(){return _.useContext(Ke)!=null}function nt(){return w(tt(),`useLocation() may be used only in the context of a component.`),_.useContext(Ke).location}var rt=`You should call navigate() in a React.useEffect(), not when your component is first rendered.`;function it(e){_.useContext(A).static||_.useLayoutEffect(e)}function at(){let{isDataRoute:e}=_.useContext(qe);return e?Et():ot()}function ot(){w(tt(),`useNavigate() may be used only in the context of a component.`);let e=_.useContext(Be),{basename:t,navigator:n}=_.useContext(A),{matches:r}=_.useContext(qe),{pathname:i}=nt(),a=JSON.stringify(ke(r)),o=_.useRef(!1);return it(()=>{o.current=!0}),_.useCallback((r,s={})=>{if(T(o.current,rt),!o.current)return;if(typeof r==`number`){n.go(r);return}let c=Ae(r,JSON.parse(a),i,s.relative===`path`);e==null&&t!==`/`&&(c.pathname=c.pathname===`/`?t:je([t,c.pathname])),(s.replace?n.replace:n.push)(c,s.state,s)},[t,n,a,i,e])}_.createContext(null);function st(e,{relative:t}={}){let{matches:n}=_.useContext(qe),{pathname:r}=nt(),i=JSON.stringify(ke(n));return _.useMemo(()=>Ae(e,JSON.parse(i),r,t===`path`),[e,i,r,t])}function ct(e,t){return lt(e,t)}function lt(e,t,n){w(tt(),`useRoutes() may be used only in the context of a component.`);let{navigator:r}=_.useContext(A),{matches:i}=_.useContext(qe),a=i[i.length-1],o=a?a.params:{},s=a?a.pathname:`/`,c=a?a.pathnameBase:`/`,l=a&&a.route;{let e=l&&l.path||``;Ot(s,!l||e.endsWith(`*`)||e.endsWith(`*?`),`You rendered descendant (or called \`useRoutes()\`) at "${s}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. Please change the parent to .`)}let u=nt(),d;if(t){let e=typeof t==`string`?ie(t):t;w(c===`/`||e.pathname?.startsWith(c),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${c}" but pathname "${e.pathname}" was given in the \`location\` prop.`),d=e}else d=u;let f=d.pathname||`/`,p=f;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);p=`/`+f.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let m=se(e,{pathname:p});T(l||m!=null,`No routes matched location "${d.pathname}${d.search}${d.hash}" `),T(m==null||m[m.length-1].route.element!==void 0||m[m.length-1].route.Component!==void 0||m[m.length-1].route.lazy!==void 0,`Matched leaf route at location "${d.pathname}${d.search}${d.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let h=gt(m&&m.map(e=>Object.assign({},e,{params:Object.assign({},o,e.params),pathname:je([c,r.encodeLocation?r.encodeLocation(e.pathname.replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:je([c,r.encodeLocation?r.encodeLocation(e.pathnameBase.replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathnameBase])})),i,n);return t&&h?_.createElement(Ke.Provider,{value:{location:{pathname:`/`,search:``,hash:``,state:null,key:`default`,unstable_mask:void 0,...d},navigationType:`POP`}},h):h}function ut(){let e=Tt(),t=Ie(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r=`rgba(200,200,200, 0.5)`,i={padding:`0.5rem`,backgroundColor:r},a={padding:`2px 4px`,backgroundColor:r},o=null;return console.error(`Error handled by React Router default ErrorBoundary:`,e),o=_.createElement(_.Fragment,null,_.createElement(`p`,null,`💿 Hey developer 👋`),_.createElement(`p`,null,`You can provide a way better UX than this when your app throws errors by providing your own `,_.createElement(`code`,{style:a},`ErrorBoundary`),` or`,` `,_.createElement(`code`,{style:a},`errorElement`),` prop on your route.`)),_.createElement(_.Fragment,null,_.createElement(`h2`,null,`Unexpected Application Error!`),_.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?_.createElement(`pre`,{style:i},n):null,o)}var dt=_.createElement(ut,null),ft=class extends _.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error(`React Router caught the following error during render`,e)}render(){let e=this.state.error;if(this.context&&typeof e==`object`&&e&&`digest`in e&&typeof e.digest==`string`){let t=$e(e.digest);t&&(e=t)}let t=e===void 0?this.props.children:_.createElement(qe.Provider,{value:this.props.routeContext},_.createElement(Je.Provider,{value:e,children:this.props.component}));return this.context?_.createElement(mt,{error:e},t):t}};ft.contextType=He;var pt=new WeakMap;function mt({children:e,error:t}){let{basename:n}=_.useContext(A);if(typeof t==`object`&&t&&`digest`in t&&typeof t.digest==`string`){let e=Qe(t.digest);if(e){let r=pt.get(t);if(r)throw r;let i=ze(e.location,n);if(Re&&!pt.get(t))if(i.isExternal||e.reloadDocument)window.location.href=i.absoluteURL||i.to;else{let n=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(i.to,{replace:e.replace}));throw pt.set(t,n),n}return _.createElement(`meta`,{httpEquiv:`refresh`,content:`0;url=${i.absoluteURL||i.to}`})}}return e}function ht({routeContext:e,match:t,children:n}){let r=_.useContext(Be);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),_.createElement(qe.Provider,{value:e},n)}function gt(e,t=[],n){let r=n?.state;if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(t.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let i=e,a=r?.errors;if(a!=null){let e=i.findIndex(e=>e.route.id&&a?.[e.route.id]!==void 0);w(e>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(a).join(`,`)}`),i=i.slice(0,Math.min(i.length,e+1))}let o=!1,s=-1;if(n&&r){o=r.renderFallback;for(let e=0;e=0?i.slice(0,s+1):[i[0]];break}}}}let c=n?.onError,l=r&&c?(e,t)=>{c(e,{location:r.location,params:r.matches?.[0]?.params??{},unstable_pattern:Le(r.matches),errorInfo:t})}:void 0;return i.reduceRight((e,n,c)=>{let u,d=!1,f=null,p=null;r&&(u=a&&n.route.id?a[n.route.id]:void 0,f=n.route.errorElement||dt,o&&(s<0&&c===0?(Ot(`route-fallback`,!1,"No `HydrateFallback` element provided to render during initial hydration"),d=!0,p=null):s===c&&(d=!0,p=n.route.hydrateFallbackElement||null)));let m=t.concat(i.slice(0,c+1)),h=()=>{let t;return t=u?f:d?p:n.route.Component?_.createElement(n.route.Component,null):n.route.element?n.route.element:e,_.createElement(ht,{match:n,routeContext:{outlet:e,matches:m,isDataRoute:r!=null},children:t})};return r&&(n.route.ErrorBoundary||n.route.errorElement||c===0)?_.createElement(ft,{location:r.location,revalidation:r.revalidation,component:f,error:u,children:h(),routeContext:{outlet:null,matches:m,isDataRoute:!0},onError:l}):h()},null)}function _t(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function vt(e){let t=_.useContext(Be);return w(t,_t(e)),t}function yt(e){let t=_.useContext(Ve);return w(t,_t(e)),t}function bt(e){let t=_.useContext(qe);return w(t,_t(e)),t}function xt(e){let t=bt(e),n=t.matches[t.matches.length-1];return w(n.route.id,`${e} can only be used on routes that contain a unique "id"`),n.route.id}function St(){return xt(`useRouteId`)}function Ct(){return yt(`useNavigation`).navigation}function wt(){let{matches:e,loaderData:t}=yt(`useMatches`);return _.useMemo(()=>e.map(e=>D(e,t)),[e,t])}function Tt(){let e=_.useContext(Je),t=yt(`useRouteError`),n=xt(`useRouteError`);return e===void 0?t.errors?.[n]:e}function Et(){let{router:e}=vt(`useNavigate`),t=xt(`useNavigate`),n=_.useRef(!1);return it(()=>{n.current=!0}),_.useCallback(async(r,i={})=>{T(n.current,rt),n.current&&(typeof r==`number`?await e.navigate(r):await e.navigate(r,{fromRouteId:t,...i}))},[e,t])}var Dt={};function Ot(e,t,n){!t&&!Dt[e]&&(Dt[e]=!0,T(!1,n))}_.useOptimistic,_.memo(kt);function kt({routes:e,future:t,state:n,isStatic:r,onError:i}){return lt(e,void 0,{state:n,isStatic:r,onError:i,future:t})}function At({to:e,replace:t,state:n,relative:r}){w(tt(),` may be used only in the context of a component.`);let{static:i}=_.useContext(A);T(!i,` must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.`);let{matches:a}=_.useContext(qe),{pathname:o}=nt(),s=at(),c=Ae(e,ke(a),o,r===`path`),l=JSON.stringify(c);return _.useEffect(()=>{s(JSON.parse(l),{replace:t,state:n,relative:r})},[s,l,r,t,n]),null}function jt(e){w(!1,`A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .`)}function Mt({basename:e=`/`,children:t=null,location:n,navigationType:r=`POP`,navigator:i,static:a=!1,unstable_useTransitions:o}){w(!tt(),`You cannot render a inside another . You should never have more than one in your app.`);let s=e.replace(/^\/*/,`/`),c=_.useMemo(()=>({basename:s,navigator:i,static:a,unstable_useTransitions:o,future:{}}),[s,i,a,o]);typeof n==`string`&&(n=ie(n));let{pathname:l=`/`,search:u=``,hash:d=``,state:f=null,key:p=`default`,unstable_mask:m}=n,h=_.useMemo(()=>{let e=Ce(l,s);return e==null?null:{location:{pathname:e,search:u,hash:d,state:f,key:p,unstable_mask:m},navigationType:r}},[s,l,u,d,f,p,r,m]);return T(h!=null,` is not able to match the URL "${l}${u}${d}" because it does not start with the basename, so the won't render anything.`),h==null?null:_.createElement(A.Provider,{value:c},_.createElement(Ke.Provider,{children:t,value:h}))}function Nt({children:e,location:t}){return ct(Pt(e),t)}function Pt(e,t=[]){let n=[];return _.Children.forEach(e,(e,r)=>{if(!_.isValidElement(e))return;let i=[...t,r];if(e.type===_.Fragment){n.push.apply(n,Pt(e.props.children,i));return}w(e.type===jt,`[${typeof e.type==`string`?e.type:e.type.name}] is not a component. All component children of must be a or `),w(!e.props.index||!e.props.children,`An index route cannot have child routes.`);let a={id:e.props.id||i.join(`-`),caseSensitive:e.props.caseSensitive,element:e.props.element,Component:e.props.Component,index:e.props.index,path:e.props.path,middleware:e.props.middleware,loader:e.props.loader,action:e.props.action,hydrateFallbackElement:e.props.hydrateFallbackElement,HydrateFallback:e.props.HydrateFallback,errorElement:e.props.errorElement,ErrorBoundary:e.props.ErrorBoundary,hasErrorBoundary:e.props.hasErrorBoundary===!0||e.props.ErrorBoundary!=null||e.props.errorElement!=null,shouldRevalidate:e.props.shouldRevalidate,handle:e.props.handle,lazy:e.props.lazy};e.props.children&&(a.children=Pt(e.props.children,i)),n.push(a)}),n}var Ft=`get`,It=`application/x-www-form-urlencoded`;function Lt(e){return typeof HTMLElement<`u`&&e instanceof HTMLElement}function Rt(e){return Lt(e)&&e.tagName.toLowerCase()===`button`}function zt(e){return Lt(e)&&e.tagName.toLowerCase()===`form`}function Bt(e){return Lt(e)&&e.tagName.toLowerCase()===`input`}function Vt(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Ht(e,t){return e.button===0&&(!t||t===`_self`)&&!Vt(e)}var Ut=null;function Wt(){if(Ut===null)try{new FormData(document.createElement(`form`),0),Ut=!1}catch{Ut=!0}return Ut}var Gt=new Set([`application/x-www-form-urlencoded`,`multipart/form-data`,`text/plain`]);function Kt(e){return e!=null&&!Gt.has(e)?(T(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${It}"`),null):e}function qt(e,t){let n,r,i,a,o;if(zt(e)){let o=e.getAttribute(`action`);r=o?Ce(o,t):null,n=e.getAttribute(`method`)||Ft,i=Kt(e.getAttribute(`enctype`))||It,a=new FormData(e)}else if(Rt(e)||Bt(e)&&(e.type===`submit`||e.type===`image`)){let o=e.form;if(o==null)throw Error(`Cannot submit a
Brainslug Logo
Brainslug Tools Tools to flash and manage your brainslug
GitHub Logo
{/* */}
) } export default Header ================================================ FILE: brainslug-tools/src/index.css ================================================ :root { --esp-tools-button-color: #5ac060; /* MDC Web (Older versions / some components) */ --mdc-typography-body1-line-height: 2rem; --mdc-typography-body1-font-size: 1.6rem; --mdc-typography-headline6-font-size: 1.8rem; --mdc-typography-headline5-font-size: 2rem; --mdc-typography-headline4-font-size: 2.4rem; --mdc-typography-headline3-font-size: 3rem; --mdc-typography-headline2-font-size: 3.6rem; --mdc-typography-headline1-font-size: 4.8rem; --mdc-typography-button-font-size: 1.6rem; --mdc-typography-caption-font-size: 1.4rem; --mdc-typography-overline-font-size: 1.4rem; /* Material 3 Web Typography (Used by esp-web-tools 10+) */ /* Fixes rem scaling issue caused by html font-size: 62.5% */ --md-sys-typescale-body-large-size: 1.6rem; --md-sys-typescale-body-large-line-height: 2.4rem; --md-sys-typescale-body-medium-size: 1.4rem; --md-sys-typescale-body-medium-line-height: 2.0rem; --md-sys-typescale-body-small-size: 1.2rem; --md-sys-typescale-body-small-line-height: 1.6rem; --md-sys-typescale-label-large-size: 1.4rem; --md-sys-typescale-label-large-line-height: 2.0rem; --md-sys-typescale-label-medium-size: 1.2rem; --md-sys-typescale-label-medium-line-height: 1.6rem; --md-sys-typescale-label-small-size: 1.1rem; --md-sys-typescale-label-small-line-height: 1.6rem; --md-sys-typescale-title-large-size: 2.2rem; --md-sys-typescale-title-large-line-height: 2.8rem; --md-sys-typescale-title-medium-size: 1.6rem; --md-sys-typescale-title-medium-line-height: 2.4rem; --md-sys-typescale-title-small-size: 1.4rem; --md-sys-typescale-title-small-line-height: 2.0rem; --md-sys-typescale-headline-large-size: 3.2rem; --md-sys-typescale-headline-large-line-height: 4.0rem; --md-sys-typescale-headline-medium-size: 2.8rem; --md-sys-typescale-headline-medium-line-height: 3.6rem; --md-sys-typescale-headline-small-size: 2.4rem; --md-sys-typescale-headline-small-line-height: 3.2rem; } #root { max-width: 100%; margin: 0 auto; text-align: center; min-height: 100svh; display: flex; flex-direction: column; box-sizing: border-box; width: 100%; font-size: 1.6rem; /* font-size: 160%; */ } body { background: #141414; color: #e0e0e0; font-family: 'Trebuchet MS', sans-serif; display: flex; align-items: center; justify-content: center; margin: 0; font-size: 1.6rem; } html { font-size: 62.5%; } ================================================ FILE: brainslug-tools/src/logic/connect.ts ================================================ import { Robot } from "./robot"; let robotConnection: SerialPort; export let robot: Robot | null = null; const listeners = new Set<() => void>(); let updateTick = 0; export const updateRobotStore = () => { // Simply bump a tick counter to notify React that internal class state changed updateTick++; listeners.forEach(listener => listener()); } export const subscribeToRobot = (listener: () => void) => { listeners.add(listener); return () => listeners.delete(listener); } export const getRobotUpdateTick = () => updateTick; export const ConnectionStatus = { NotConnected: 'NotConnected', Connecting: 'Connecting', Connected: 'Connected', Error: 'Error' } as const; export async function connectToRobot(setStatus: (status: string) => void, filterResults: boolean) { setStatus('Select device in browser prompt'); try { robotConnection = await navigator.serial.requestPort({ filters: filterResults ? [{ usbVendorId: 0x2108 } ] : [] }); } catch (e) { setStatus('No device selected'); return; } setStatus('Opening port...'); try { await robotConnection.open({ baudRate: 115200 }); } catch (e) { setStatus('Error opening port'); return; } setStatus('Connected successfully'); setTimeout(async () => { robot = new Robot(robotConnection); updateRobotStore(); // const writer = robotConnection?.writable?.getWriter(); // await writer?.write(new TextEncoder().encode('GetErr\n')); // // writer?.releaseLock(); // await writer?.write(new TextEncoder().encode('GetVersion\n')); // await writer?.write(new TextEncoder().encode('GetWarranty\n')); // await writer?.write(new TextEncoder().encode('GetState\n')); // await writer?.write(new TextEncoder().encode('GetCharger\n')); // // writer?.releaseLock(); // console.log('Sent GetVersion command'); }, 200); } ================================================ FILE: brainslug-tools/src/logic/parser.ts ================================================ import type { DataKey } from "./robot"; interface BaseDataKeyParser { key: string; basic?: true; } interface CustomDataKeyParser extends BaseDataKeyParser { parser: (value: string[]) => DataKey[]; name?: string; } interface DefaultDataKeyParser extends BaseDataKeyParser { name?: string; parser?: never; } type DataKeyParser = CustomDataKeyParser | DefaultDataKeyParser; function defaultParser(name: string, value: string[]): DataKey[] { return [[name, value[0]]]; } export function parseKeys(data: string, keys: DataKeyParser[]) { const basic: DataKey[] = []; const advanced: DataKey[] = []; const lines = data.split('\n'); lines.forEach(line => { const [stringKey, ...value] = line.split(','); const key = keys.find(k => k.key === stringKey); if (!key) return; (key.basic ? basic : advanced).push(...(key.parser ? key.parser(value) : defaultParser(key.name || key.key, value))); }); const now = Date.now(); return { lastUpdated: now, basic, advanced, } } export function parseGetErr(data: string) { const lines = data.split('\n'); // gen2 if (lines.length === 2) return { basic: [["Error", lines[1]]] as DataKey[], lastUpdated: Date.now() }; // gen3 return { basic: [["Error", lines[2]], ["Alert", lines[4]]] as DataKey[], lastUpdated: Date.now() }; } export const GetVersionKeys: DataKeyParser[] = [{ key: 'Model', basic: true, parser: (value: string[]) => { return [ ['Model', value[0]], ['ModelNum', value[1]] ]; } }, { key: 'Software', basic: true, parser: (value: string[]) => { return [ ['Software', value.slice(0, 3).join(".") + '-' + value[3]] ] } }, { key: 'MainBoard Serial Number', basic: true, parser: (value: string[]) => { return [ ['MFG Code', value[0]], ['Serial Number', value[1]] ] } }]; export const GetChargerKeys: DataKeyParser[] = [ { key: 'FuelPercent', name: 'Fuel Percent', basic: true, }, { key: 'BattTempCAvg', basic: true, name: 'Average Battery Temperature (C)', }, { key: 'VBattV', basic: true, name: 'Battery Voltage', }, { key: 'VExtV', basic: true, name: 'Charger Voltage', }, { key: 'BatteryOverTemp' }, { key: 'ChargingActive' }, { key: 'ChargingEnabled' }, { key: 'ConfidentOnFuel' }, { key: 'OnReservedFuel' }, { key: 'BatteryFailure' }, { key: 'ExtPwrPresent' }, { key: 'ThermistorPresent' }, { key: 'Discharge_mAH' }, { key: 'Charger_mAH' }, ]; ================================================ FILE: brainslug-tools/src/logic/robot.ts ================================================ import { updateRobotStore } from "./connect"; import { GetChargerKeys, GetVersionKeys, parseGetErr, parseKeys } from "./parser"; export interface VersionData { model?: string; modelNum?: string; software?: string; serial?: string; mfgCode?: string; } export type DataKey = [string, string]; export interface DataKeyData { basic: DataKey[]; advanced?: DataKey[]; command: string; lastUpdated: number; } export class Robot { serialConnection: SerialPort; version: DataKeyData = { basic: [], advanced: [], command: 'GetVersion', lastUpdated: 0 }; charger: DataKeyData = { basic: [], advanced: [], command: 'GetCharger', lastUpdated: 0 }; error: DataKeyData = { basic: [], advanced: [], command: 'GetErr', lastUpdated: 0 }; constructor(serialConnection: SerialPort) { this.serialConnection = serialConnection; this.startDataRead(); this.startWriteData(); this.sendCommand('GetVersion'); this.sendCommand('GetCharger'); this.sendCommand('GetErr'); this.startGetErrLoop(); } readBuffer: Uint8Array = new Uint8Array(); async startDataRead() { while (this.serialConnection.readable) { const reader = this.serialConnection.readable.getReader(); try { while (true) { const { value, done } = await reader.read(); if (done) { // |reader| has been canceled. break; } if (!value) continue; this.readBuffer = new Uint8Array([...this.readBuffer, ...value]); // when value has with \x1A we have a full message, decode and continue buffer for new data if (value.includes(0x1A)) { console.warn('Received message with 0x1A, decoding buffer'); const parts = new TextDecoder().decode(this.readBuffer).split('\x1A'); const lastPart = parts.pop(); parts.forEach(part => { this.parseData(part); }); if (lastPart === "") { this.readBuffer = new Uint8Array(); continue; } this.readBuffer = new TextEncoder().encode(lastPart); } } } catch (error) { // Handle |error|... } finally { reader.releaseLock(); } } } parseData(data: string) { console.log('Parsing data:', data); if (data.includes('Component,Major,Minor,Build,Aux')) this.version = { ...this.version, ...parseKeys(data, GetVersionKeys) }; else if (data.includes('Label,Value') && data.includes('BattTempCAvg')) this.charger = { ...this.charger, ...parseKeys(data, GetChargerKeys) }; else if (data.startsWith('GetErr')) this.error = { ...this.error, ...parseGetErr(data) }; console.log('Updated robot data:', { version: this.version, charger: this.charger }); updateRobotStore(); } cmdQueue: string[] = []; writeTimer?: number; serialWriter?: WritableStreamDefaultWriter; async startWriteData() { if (!this.serialConnection.writable) { console.error('Serial connection not writable'); return; } if (this.writeTimer) { this.stopWriteData(); return; }; this.serialWriter = this.serialConnection.writable.getWriter(); this.writeTimer = setInterval(() => { if (this.cmdQueue.length === 0) return; if (!this.serialWriter) { console.error('Serial writer not available'); this.stopWriteData(); return; } const cmd = this.cmdQueue.shift()!; console.log('Sending command:', cmd); this.serialWriter.write(new TextEncoder().encode(cmd)); }, 100); } async stopWriteData() { if (this.writeTimer) { clearInterval(this.writeTimer); this.writeTimer = undefined; } if (this.serialWriter) { await this.serialWriter.close(); this.serialWriter = undefined; } } async sendCommand(command: string) { this.cmdQueue.push(command + '\n'); } getErrLoopTimer?: number; startGetErrLoop() { if (this.getErrLoopTimer) { this.stopGetErrLoop(); return; } this.getErrLoopTimer = setInterval(() => { this.sendCommand('GetErr'); }, 2000); } stopGetErrLoop() { if (this.getErrLoopTimer) { clearInterval(this.getErrLoopTimer); this.getErrLoopTimer = undefined; } } } ================================================ FILE: brainslug-tools/src/main.tsx ================================================ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' import { HashRouter } from 'react-router-dom' createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: brainslug-tools/src/pages/flasher.scss ================================================ .gen-picker { .gen-pick { position: relative; cursor: pointer; font-size: 14px; padding: 8px 28px; color: var(--esp-tools-button-text-color, #fff); background-color: var(--esp-tools-button-color, #03a9f4); border: none; border-radius: 4px; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0, 0, 0, .12), 0 1px 5px 0 rgba(0, 0, 0, .2); } display: flex; gap: 1rem; justify-content: center; } ================================================ FILE: brainslug-tools/src/pages/flasher.tsx ================================================ import './flasher.scss' import { InstallButton } from "esp-web-tools" import { useEffect, useRef, useState } from "react"; import { useNavigate } from 'react-router-dom'; function Flasher() { const containerRef = useRef(null); const [gen, setGen] = useState<2 | 3 | null>(null); const navigate = useNavigate(); useEffect(() => { if (!('serial' in navigator)) { navigate('/unsupported', { replace: true }); } }, [navigate]); useEffect(() => { if (!gen) return; if (!containerRef.current) return; if (containerRef.current.children.length) { Array.from(containerRef.current.children).forEach(child => containerRef.current?.removeChild(child)); } const button = new InstallButton(); button.manifest = createManifest(gen); button.showLog = true; button.logConsole = true; containerRef.current.appendChild(button); containerRef.current.animate([ { opacity: 0, transform: 'scale(0.95)' }, { opacity: 1, transform: 'scale(1)' } ], { duration: 300, easing: 'ease-out' }); }, [gen]); return (

Brainslug Web Flasher

Select your Neato robot generation, D3-D7 robots are gen3 and D70-D85 + BotVac Connected (without D) is gen2. You can read more about the generations on the GitHub.

) } export default Flasher function createManifest(gen: number) { const genstr = gen === 2 ? 'gen2' : 'gen3'; const getUrl = (filename: string) => new URL(filename, window.location.href).href; const manifest = { "name": `Neato Brainslug - ${genstr.toUpperCase()}`, "version": "1.2.1", "home_assistant_domain": "esphome", "funding_url": "https://ko-fi.com/philip2809", "builds": [ { "chipFamily": "ESP32", "parts": [ { "path": getUrl(`/webflash/nbs-${genstr}-esp32.factory.bin`), "offset": 0 } ] }, { "chipFamily": "ESP32-S3", "parts": [ { "path": getUrl(`/webflash/nbs-${genstr}-esp32s3.factory.bin`), "offset": 0 } ] }, { "chipFamily": "ESP32-C3", "parts": [ { "path": getUrl(`/webflash/nbs-${genstr}-esp32c3.factory.bin`), "offset": 0 } ] }, { "chipFamily": "ESP32-C6", "parts": [ { "path": getUrl(`/webflash/nbs-${genstr}-esp32c6.factory.bin`), "offset": 0 } ] } ] } const json = JSON.stringify(manifest); const blob = new Blob([json], { type: "application/json" }); return URL.createObjectURL(blob); } ================================================ FILE: brainslug-tools/src/pages/ha-config.scss ================================================ .ha-config-container { padding: 2rem; max-width: fit-content; margin: 0 auto; text-align: left; display: flex; flex-direction: column; gap: 1rem; h1 { margin-bottom: 0.5rem; } p { margin-top: 0; margin-bottom: 2rem; color: #a0a0a0; } .config-layout { display: flex; gap: 2rem; @media (max-width: 800px) { flex-direction: column; } } .config-form { flex: 1; display: flex; flex-direction: column; gap: 1rem; background: #1e1e1e; padding: 1.5rem; border-radius: 8px; .form-group { display: flex; flex-direction: column; gap: 0.4rem; label { font-weight: bold; color: #ddd; } input[type="text"], select { padding: 0.6rem; background: #2a2a2a; border: 1px solid #444; border-radius: 4px; color: #fff; font-family: inherit; &:focus { border-color: #646cff; outline: none; } } } .checkbox-group { flex-direction: row; align-items: center; label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; } input[type="checkbox"] { cursor: pointer; width: 1.2rem; height: 1.2rem; } } .sub-group { padding-left: 1.5rem; border-left: 2px solid #444; display: flex; flex-direction: column; gap: 1rem; margin-top: -0.5rem; } hr { border: 0; border-top: 1px solid #444; margin: 0.5rem 0; } } .config-output { flex: 1; background: #161b22; border-radius: 8px; padding: 1.5rem; border: 1px solid #30363d; display: flex; flex-direction: column; .output-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; border-bottom: 1px solid #30363d; padding-bottom: 0.5rem; h3 { margin: 0; color: #fff; } .action-btn { background-color: #238636; color: #fff; border: none; padding: 0.4rem 1rem; border-radius: 4px; cursor: pointer; font-weight: 500; &:hover { background-color: #2ea043; } } } pre { margin: 0; overflow-x: auto; font-size: 1.1rem; color: #e6edf3; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; code { white-space: pre; } } } } ================================================ FILE: brainslug-tools/src/pages/ha-config.tsx ================================================ import { useState } from 'react'; import './ha-config.scss'; export default function HaConfig() { const [name, setName] = useState('neato-vacuum'); const [comment, setComment] = useState(''); const [infoInterval, setInfoInterval] = useState('2sec'); const [chargerInterval, setChargerInterval] = useState('2min'); const [otaPassword, setOtaPassword] = useState('!secret neato_vacuum_ota'); const [useHa, setUseHa] = useState(true); const [wifiSsid, setWifiSsid] = useState('!secret wifi_ssid'); const [wifiPassword, setWifiPassword] = useState('!secret wifi_password'); const [haEncryptionKey, setHaEncryptionKey] = useState('!secret neato_vacuum_api'); const [useCustomUart, setUseCustomUart] = useState(false); const [uartTx, setUartTx] = useState('17'); const [uartRx, setUartRx] = useState('16'); const [useDomainInfo, setUseDomainInfo] = useState(false); const [domain, setDomain] = useState('.lan'); const [useAddress, setUseAddress] = useState('192.168.205.199'); const [boardType, setBoardType] = useState('esp32s2.yaml'); const [neatoGen, setNeatoGen] = useState('gen3.yaml'); const generateYaml = () => { let yaml = ` substitutions: name: ${name} comment: "${comment}" infointerval: ${infoInterval} chargerinterval: ${chargerInterval} ota_password: ${otaPassword} `; if (useHa) { yaml += ` ### If you are using ha: wifi_ssid: ${wifiSsid} wifi_password: ${wifiPassword} ha_encryption_key: ${haEncryptionKey} `; } else { yaml += ` ### If you are using ha: # wifi_ssid: !secret wifi_ssid # wifi_password: !secret wifi_password # ha_encryption_key: !secret neato_vacuum_api `; } if (useCustomUart) { yaml += ` ### If you want to change the UART Pins uart_tx: ${uartTx} uart_rx: ${uartRx} `; } else { yaml += ` ### If you want to change the UART Pins # uart_tx: 17 # uart_rx: 16 `; } yaml += ` wifi: `; if (useDomainInfo) { yaml += ` ### If your router sets another TLD for local devices, specify that here domain: ${domain} ### If you are having issues with the dns or are 100% this will be the ip, you can spesify that here use_address: ${useAddress} `; } else { yaml += ` ### If your router sets another TLD for local devices, specify that here # domain: .lan ### If you are having issues with the dns or are 100% this will be the ip, you can spesify that here # use_address: 192.168.205.199 `; } yaml += ` packages: remote_package_files: url: https://github.com/philip2809/neato-brainslug files: `; const boards = ['esp32.yaml', 'esp32c3.yaml', 'esp32s3.yaml', 'esp32s2.yaml']; for (const b of boards) { yaml += b === boardType ? ` - config/boards/${b}\n` : ` # - config/boards/${b}\n`; } yaml += `\n`; if (useHa) { yaml += ` - config/ha.yaml # Remember to set wifi credentials and ha_encryption_key in the substitutions if you are using this # - config/no-ha.yaml `; } else { yaml += ` # - config/ha.yaml - config/no-ha.yaml `; } yaml += `\n - config/webserver.yaml\n`; if (neatoGen === 'gen3.yaml') { yaml += ` - config/gen3.yaml # - config/gen2.yaml\n`; } else { yaml += ` # - config/gen3.yaml - config/gen2.yaml\n`; } yaml += ` ref: main `; return yaml; }; const copyToClipboard = () => { navigator.clipboard.writeText(generateYaml()); }; return (

HA Config Generator

Configure your ESPHome YAML file easily.

setName(e.target.value)} />
setComment(e.target.value)} />
setInfoInterval(e.target.value)} />
setChargerInterval(e.target.value)} />
setOtaPassword(e.target.value)} />

{useHa && (
setWifiSsid(e.target.value)} />
setWifiPassword(e.target.value)} />
setHaEncryptionKey(e.target.value)} />
)}
{useCustomUart && (
setUartTx(e.target.value)} />
setUartRx(e.target.value)} />
)}
{useDomainInfo && (
setDomain(e.target.value)} />
setUseAddress(e.target.value)} />
)}

Generated YAML

            {generateYaml()}
          
); } ================================================ FILE: brainslug-tools/src/pages/home.scss ================================================ .home { text-align: center; max-width: 900px; p { font-size: 1.8rem; color: #aaa; line-height: 1.7; } strong { color: #e0e0e0; } .success { font-size: 2.2rem; font-weight: 600; color: #5ac060; } } ================================================ FILE: brainslug-tools/src/pages/home.tsx ================================================ import { Link } from 'react-router-dom'; import './home.scss'; function Home() { return (

Neato Brainslug

Neato Brainslug allows local control of your Neato robot, with or without, Home Assistant. To make the proccess of installing the Brainslug easier, tools on this site will help you along the way.

Use Robot Managment to connect to your robot and view live data or find errors with your device!
Use the Web Flasher to easily flash the Brainslug firmware to your ESP32, no matter your OS!

For furthur information about the project, check out the GitHub repository where you can find full install guides!

) } export default Home ================================================ FILE: brainslug-tools/src/pages/robot.scss ================================================ .connect-robot { background: #1e1e1e; border: 1px solid #2e2e2e; border-radius: 12px; padding: 2.5rem; width: 100%; max-width: 480px; text-align: left; display: flex; flex-direction: column; .subtitle { font-size: 1.4rem; color: #ccc; line-height: 1.5; } .title { font-size: 2.2rem; font-weight: 600; color: #e0e0e0; margin-bottom: 0.75rem; } .toggle-row { display: flex; align-items: center; justify-content: center; gap: 0.8rem; .toggle-label { font-size: 1.2rem; color: #aaa; user-select: none; cursor: pointer; } .badge { font-size: 0.85rem; font-weight: 600; background: #2a2218; color: #a07840; border: 1px solid #3a3020; border-radius: 4px; padding: 0.2rem 0.5rem; text-transform: uppercase; letter-spacing: 0.05em; } /* custom toggle switch */ .switch { position: relative; display: inline-block; width: 38px; height: 22px; flex-shrink: 0; input { opacity: 0; width: 0; height: 0; &:checked + .slider { background: #2a3a2a; border-color: #3a5a3a; } &:checked + .slider::before { transform: translateX(16px); background: #5a9e6f; } } } .slider { position: absolute; inset: 0; background: #2e2e2e; border: 1px solid #3a3a3a; border-radius: 20px; cursor: pointer; transition: background 0.2s, border-color 0.2s; &::before { content: ''; position: absolute; width: 14px; height: 14px; left: 3px; top: 3px; background: #888; border-radius: 50%; transition: transform 0.2s, background 0.2s; } } } button.connect { background: #2a2a2a; border: 1px solid #3a3a3a; color: #e0e0e0; font-family: inherit; font-size: 1.6rem; font-weight: 500; padding: 0.8rem 1.6rem; border-radius: 6px; cursor: pointer; transition: background 0.15s, border-color 0.15s; align-self: center; &:hover:not(:disabled) { background: #333; border-color: #4a4a4a; } &:disabled { opacity: 0.6; cursor: not-allowed; } } .status { font-size: 1.4rem; color: #888; align-self: center; } } .info-cards { display: flex; flex-direction: column; margin-top: 2rem; gap: 1.5rem; margin-bottom: 2rem; } .robot-info { font-size: 1.8rem; padding: 1.5rem; background: #1e1e1e; border: 1px solid #2e2e2e; border-radius: 12px; width: 100%; max-width: 550px; box-sizing: border-box; .info-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem; .title-group { display: flex; flex-direction: column; gap: 0.3rem; h2 { margin: 0; font-size: 2rem; font-weight: 600; color: #fff; } .time-ago { font-size: 1.2rem; color: #888; min-width: 15rem; /* Prevents text shifts from pushing buttons */ } } .action-group { display: flex; gap: 0.8rem; align-items: center; .tooltip-wrap { display: inline-flex; } .action-btn { background: #2a2a2a; border: 1px solid #3a3a3a; color: #e0e0e0; font-family: inherit; font-size: 1.3rem; font-weight: 500; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; transition: background 0.15s, border-color 0.15s, opacity 0.15s; &:hover:not(:disabled) { background: #333; border-color: #4a4a4a; } &:disabled { opacity: 0.4; cursor: not-allowed; } } } } .no-data { color: #666; font-size: 1.4rem; font-style: italic; margin-bottom: 1rem; } .table { display: flex; flex-direction: column; width: 100%; .row { padding: 0.8rem; display: flex; justify-content: space-between; background-color: #3a3a3a; &:nth-child(odd) { background-color: #2a2a2a; } .key { font-weight: 500; color: #aaa; } } } } ================================================ FILE: brainslug-tools/src/pages/robot.tsx ================================================ import { useEffect, useState, useSyncExternalStore } from 'react'; import './robot.scss'; import { connectToRobot, robot, subscribeToRobot, getRobotUpdateTick } from '../logic/connect'; import type { DataKeyData } from '../logic/robot'; import TimeAgo from 'react-timeago'; import { useNavigate } from 'react-router-dom'; function Robot() { const [status, setStatus] = useState('Not connected'); const [filterResults, setFilterResults] = useState(false); const navigate = useNavigate(); useEffect(() => { if (!('serial' in navigator)) { navigate('/unsupported', { replace: true }); } }, [navigate]); // Just use the external store to subscribe to a simple "update tick" counter. // When 'updateRobotStore()' is called, the tick increases, forcing React to rerender. useSyncExternalStore(subscribeToRobot, getRobotUpdateTick); if (!robot) return (
Connect your Neato

Remove the dustbin from your Neato vacuum and connect to the USB port. Click the button below and select your Neato, It may appear as "USB Serial Device (COMx)" on Windows, or "CDC Serial" / "ttyACM0" on Linux/MacOS. You can also try to unplug and replug to device to see which one appears if you're not sure.

setFilterResults(!filterResults)}> Filter by Neato device type Experimental
Status: {status}
) // Easily extract the value now to avoid ? all over the HTML const { version, charger, error } = robot; return (
) } function InfoCard({ title, data }: { title: string, data: DataKeyData }) { const hasData = data.basic.length > 0 || (data.advanced && data.advanced.length > 0); const [advancedVisible, setAdvancedVisible] = useState(false); return (

{title}

Updated:
{!hasData &&

No data received yet.

}
{data.basic.map(([key, value]) => (
{key}: {value}
))} {advancedVisible && data.advanced && data.advanced.map(([key, value]) => (
{key}: {value}
))}
) } export default Robot ================================================ FILE: brainslug-tools/src/pages/unsupported.scss ================================================ .unsupported { text-align: center; p { font-size: 1.8rem; color: #aaa; line-height: 1.7; } strong { color: #e0e0e0; } .warn { font-size: 1.6rem; font-weight: 600; color: #c0605a; } .success { font-size: 1.6rem; font-weight: 600; color: #5ac060; } } ================================================ FILE: brainslug-tools/src/pages/unsupported.tsx ================================================ import { useNavigate } from 'react-router-dom'; import './unsupported.scss'; import { useEffect } from 'react'; function Unsupported() { const navigate = useNavigate(); useEffect(() => { if (('serial' in navigator)) { navigate('/', { replace: true }); } }, [navigate]); return (

Browser not supported

WebSerial is required for the tools to work.
Please use a Chromium-based browser like Chrome, Brave, or Edge.

) } export default Unsupported ================================================ FILE: brainslug-tools/tsconfig.app.json ================================================ { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2023", "useDefineForClassFields": true, "lib": ["ES2023", "DOM", "DOM.Iterable"], "module": "ESNext", "types": ["vite/client", "w3c-web-serial"], "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, "include": ["src"] } ================================================ FILE: brainslug-tools/tsconfig.json ================================================ { "files": [], "references": [ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } ] } ================================================ FILE: brainslug-tools/tsconfig.node.json ================================================ { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", "target": "ES2023", "lib": ["ES2023"], "module": "ESNext", "types": ["node"], "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, "moduleDetection": "force", "noEmit": true, /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, "include": ["vite.config.ts"] } ================================================ FILE: brainslug-tools/vite.config.ts ================================================ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], }) ================================================ FILE: build/build.sh ================================================ set -e docker compose up -d docker exec -i esphome_builder bash << 'EOF' set -e BUILT_FACTORY="/config/prebuilt/.esphome/build/neato-vacuum/.pioenvs/neato-vacuum/firmware.factory.bin" BUILT_OTA="/config/prebuilt/.esphome/build/neato-vacuum/.pioenvs/neato-vacuum/firmware.ota.bin" cd /config/prebuilt esphome compile gen3-esp32.yaml cp $BUILT_FACTORY ./nbs-gen3-esp32.factory.bin cp $BUILT_OTA ./nbs-gen3-esp32.ota.bin esphome compile gen3-esp32s3.yaml cp $BUILT_FACTORY ./nbs-gen3-esp32s3.factory.bin cp $BUILT_OTA ./nbs-gen3-esp32s3.ota.bin esphome compile gen3-esp32c3.yaml cp $BUILT_FACTORY ./nbs-gen3-esp32c3.factory.bin cp $BUILT_OTA ./nbs-gen3-esp32c3.ota.bin esphome compile gen2-esp32.yaml cp $BUILT_FACTORY ./nbs-gen2-esp32.factory.bin cp $BUILT_OTA ./nbs-gen2-esp32.ota.bin esphome compile gen2-esp32s3.yaml cp $BUILT_FACTORY ./nbs-gen2-esp32s3.factory.bin cp $BUILT_OTA ./nbs-gen2-esp32s3.ota.bin esphome compile gen2-esp32c3.yaml cp $BUILT_FACTORY ./nbs-gen2-esp32c3.factory.bin cp $BUILT_OTA ./nbs-gen2-esp32c3.ota.bin EOF ================================================ FILE: build/dev.sh ================================================ set -e docker compose up -d docker exec -e DO_UPLOAD="$1" -i esphome_builder bash << 'EOF' set -e BUILT_OTA="/config/.esphome/build/neato-vacuum/.pioenvs/neato-vacuum/firmware.ota.bin" BUILT_FACTORY="/config/.esphome/build/neato-vacuum/.pioenvs/neato-vacuum/firmware.factory.bin" cd /config rm -f dev.ota.bin rm -f dev.factory.bin esphome compile .local.yaml cp $BUILT_OTA dev.ota.bin cp $BUILT_FACTORY dev.factory.bin chown 1000:1000 dev.ota.bin chown 1000:1000 dev.factory.bin if [ "$DO_UPLOAD" = "upload" ]; then echo "Uploading to ESP..." curl -X POST "http://192.168.205.199/update" \ -H "Accept: application/octet-stream" \ -F "update=@./dev.ota.bin;type=application/octet-stream" fi EOF ================================================ FILE: build/docker-compose.yml ================================================ services: esphome: container_name: esphome_builder image: ghcr.io/esphome/esphome volumes: - ../config:/config - /etc/localtime:/etc/localtime:ro restart: always privileged: true network_mode: host environment: - USERNAME=test - PASSWORD=ChangeMe ================================================ FILE: build/new-version.md ================================================ note for myself to release new version - update version in gen3 and gen2 config - update version in brainslug tools - update version in webserver - update version in ha card ================================================ FILE: config/.gitignore ================================================ # Gitignore settings for ESPHome # This is an example and may include too much for your use-case. # You can modify this file to suit your needs. .esphome/ secrets.yaml dev.factory.bin dev.ota.bin dev.yaml ================================================ FILE: config/boards/esp32.yaml ================================================ esp32: variant: ESP32 framework: type: esp-idf substitutions: uart_tx: 17 uart_rx: 16 ================================================ FILE: config/boards/esp32c3.yaml ================================================ esp32: variant: ESP32C3 framework: type: esp-idf substitutions: uart_tx: 7 uart_rx: 6 ================================================ FILE: config/boards/esp32c6.yaml ================================================ esp32: variant: ESP32C6 framework: type: esp-idf substitutions: uart_tx: 7 uart_rx: 6 ================================================ FILE: config/boards/esp32s2.yaml ================================================ esp32: variant: ESP32S2 framework: type: esp-idf substitutions: uart_tx: 17 uart_rx: 16 ================================================ FILE: config/boards/esp32s3.yaml ================================================ esp32: variant: ESP32S3 framework: type: esp-idf substitutions: uart_tx: 17 uart_rx: 16 ================================================ FILE: config/comp/gen2.yaml ================================================ substitutions: version: "1.2.1" type: "gen2" buildinfo: "${version} [TX:${uart_tx} RX:${uart_rx}]" logger: improv_serial: captive_portal: wifi: ap: ssid: "neato-brainslug" password: "make-it-suck-again" ota: - platform: esphome password: ${ota_password} esphome: name: ${name} project: name: philip2809.neato-brainslug version: ${buildinfo} comment: "${type}|${buildinfo}|${comment}" on_boot: - priority: 600 then: - lambda: !lambda |- id(uiState).publish_state("Starting..."); - logger.set_level: INFO - priority: -100 then: - lambda: !lambda |- id(uiState).publish_state("Starting..."); // Update nbs_time and text entity from persisted global if (!id(g_timezone).empty()) { id(nbs_time).set_timezone(id(g_timezone).c_str()); id(timezone_text).publish_state(id(g_timezone).c_str()); ESP_LOGI("timezone", "Restored timezone: %s", id(g_timezone).c_str()); } char buf[40]; snprintf( buf, sizeof(buf), "%u,%u,%u,%u,%u,%u,%u,%u", id(g_schedule)[0], id(g_schedule)[1], id(g_schedule)[2], id(g_schedule)[3], id(g_schedule)[4], id(g_schedule)[5], id(g_schedule)[6], id(g_schedule)[7] ); id(scheduleset).publish_state(buf); script: - id: get_user_settings mode: queued then: - delay: 100ms - uart.write: "GetUserSettings \n" - id: get_all_data mode: queued then: - uart.write: "GetErr \n" - delay: 50ms - uart.write: "GetCharger \n" - delay: 50ms - uart.write: "GetUserSettings \n" - delay: 50ms - uart.write: "SetSystemMode \n" - delay: 50ms - uart.write: "GetVersion \n" - id: initial_data_get # when the robot has booted, run this initial fetch after 5 seconds of being able to send commands, the 5 sec needed to the robot can calculate battery level etc. mode: queued then: - delay: 5sec - script.execute: get_all_data - id: robotShutdown mode: queued then: - lambda: 'id(uiState).publish_state("Shutting down...");' - uart.write: "TestMode On \n" - delay: 100ms - uart.write: "SetSystemMode Shutdown \n" - id: powercycle mode: queued then: - lambda: 'id(initial_ready) = false;' # I want to refetch all data in case we powercycle - lambda: 'id(uiState).publish_state("Shutting down...");' - uart.write: "TestMode On \n" - delay: 100ms - uart.write: "SetSystemMode PowerCycle \n" - id: play_extra_sounds parameters: soundid: int then: - if: condition: lambda: |- return id(g_play_extra_sounds); then: - uart.write: !lambda |- std::string s = "PlaySound SoundId " + std::to_string(soundid) + "\n"; return std::vector(s.begin(), s.end()); globals: - id: initial_ready type: bool restore_value: no initial_value: 'false' # --- Restore Value --- - id: g_play_extra_sounds type: bool restore_value: yes - id: g_schedule type: uint8_t[8] restore_value: yes initial_value: '{0,0,0,0,0,0,0,0}' - id: g_timezone type: std::string restore_value: yes initial_value: '' interval: - interval: ${infointerval} then: - uart.write: "GetErr \n" - interval: ${chargerinterval} then: - uart.write: "GetCharger \n" uart: id: uart_bus baud_rate: 115200 tx_pin: ${uart_tx} rx_pin: ${uart_rx} rx_buffer_size: 2048 debug: dummy_receiver: true direction: RX after: bytes: 0 delimiter: "\x1A" sequence: - lambda: !lambda |- UARTDebug::log_string(direction, bytes); // convert bytes -> std::string std::string str(bytes.begin(), bytes.end()); // ---- TRIM FUNCTION ---- auto trim = [](std::string &s) { // trim leading s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch){ return !std::isspace(ch); })); // trim trailing s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch){ return !std::isspace(ch); }).base(), s.end()); }; // --- HEX DECODER --- auto hex_to_uint32 = [](const std::string &hex) -> uint32_t { return static_cast(strtoul(hex.c_str(), nullptr, 16)); }; // ---- SPLIT ON CRLF ---- std::vector lines; size_t start = 0; size_t end = 0; while ((end = str.find("\r\n", start)) != std::string::npos) { std::string line = str.substr(start, end - start); trim(line); if (!line.empty()) lines.push_back(line); start = end + 2; } if (start < str.size()) { std::string last = str.substr(start); trim(last); if (!last.empty()) lines.push_back(last); } if (lines.empty()) return; // ---- DETECT WHICH COMMAND WAS USED ---- std::string command = lines[0]; // first line (GetErr, GetState, etc.) ESP_LOGI("uart_parser", "COMMAND: %s", command.c_str()); // ---- OPTIONAL: print each line for debugging ---- for (auto &line : lines) { std::vector v(line.begin(), line.end()); UARTDebug::log_string(direction, v); //ESP_LOGI("uart_parser", "LINE: %s", line.c_str()); } // ---- SPECIAL PARSING FOR GetErr ---- if (command == "GetErr") { // A GetErr response looks like for gen2: // 0: GetErr // 1: if (lines.size() == 3 && lines[1] != "") id(robotError).publish_state(lines[1].c_str()); else id(robotError).publish_state("No errors"); // If we can get erros/alerts, the system is ready for other commands: if (!id(initial_ready)) { id(initial_ready) = true; id(uiState).publish_state("Started, ready for commands!"); id(initial_data_get).execute(); } } else if (command == "GetCharger") { // Format after the header: // Label,Value // FuelPercent,53 // BatteryOverTemp,0 // ... for (size_t i = 2; i < lines.size(); i++) { std::string &line = lines[i]; size_t comma = line.find(','); if (comma == std::string::npos) continue; std::string key = line.substr(0, comma); std::string value = line.substr(comma + 1); // Trim them: trim(key); trim(value); // ---- MATCH KEYS AND PUBLISH ---- if (key == "FuelPercent") id(chargerFuelPercent).publish_state(atof(value.c_str())); else if (key == "BatteryOverTemp") id(chargerBatteryOverTemp).publish_state(value == "1"); else if (key == "ChargingActive") id(chargerChargingActive).publish_state(value == "1"); else if (key == "ChargingEnabled") id(chargerChargingEnabled).publish_state(value == "1"); else if (key == "ConfidentOnFuel") id(chargerConfidentOnFuel).publish_state(value == "1"); else if (key == "OnReservedFuel") id(chargerOnReservedFuel).publish_state(value == "1"); else if (key == "EmptyFuel") id(chargerEmptyFuel).publish_state(value == "1"); else if (key == "BatteryFailure") id(chargerBatteryFailure).publish_state(value == "1"); else if (key == "ExtPwrPresent") id(chargerExtPwrPresent).publish_state(value == "1"); else if (key == "ThermistorPresent") id(chargerThermistorPresent).publish_state(value == "1"); else if (key == "BattTempCAvg") id(chargerBattTempCAvg).publish_state(atof(value.c_str())); else if (key == "VBattV") id(chargerVBattV).publish_state(atof(value.c_str())); else if (key == "VExtV") id(chargerVExtV).publish_state(atof(value.c_str())); else if (key == "Charger_mAH") id(chargerCharger_mAH).publish_state(atof(value.c_str())); else if (key == "Discharge_mAH") id(chargerDischarge_mAH).publish_state(atof(value.c_str())); } } else if (command == "SetSystemMode") { // Using this command to figure out if you have testmode on or off // 0: SetSystemMode // 1: "TestMode must be on to use this command." OR "No options specified" if (lines.size() != 2) return; if (lines[1] == "No options specified") id(testMode).publish_state(true); else if (lines[1] == "TestMode must be on to use this command.") id(testMode).publish_state(false); } else if (command == "GetVersion") { for (auto &line : lines) { size_t comma = line.find(','); if (comma == std::string::npos) continue; std::string key = line.substr(0, comma); std::string value = line.substr(comma + 1); trim(key); trim(value); if (key == "Serial Number") id(robotSerial).publish_state(value.c_str()); else if (key == "Model") id(robotModel).publish_state(value.c_str()); else if (key == "Software") id(robotSoftware).publish_state(value.c_str()); } } else if (command == "GetUserSettings") { for (auto &line : lines) { trim(line); // Skip lines that start with binary/invalid chars if (!line.empty() && static_cast(line[0]) >= 0x80) continue; // ---- Key,Value lines ---- size_t comma = line.find(','); if (comma != std::string::npos) { std::string key = line.substr(0, comma); std::string value = line.substr(comma + 1); trim(key); trim(value); if (key == "ClickSounds") id(clickSounds).publish_state(value == "ON"); else if (key == "StealthLED") id(stealthLed).publish_state(value == "ON"); else if (key == "LED") id(led).publish_state(value == "ON"); else if (key == "AutoShutdown") id(autoShutdownEnabled).publish_state(value == "ON"); else if (key == "Wall Enable") id(wallEnable).publish_state(value == "ON"); else if (key == "Eco Mode") id(ecoMode).publish_state(value == "ON"); else if (key == "IntenseClean") id(intenseClean).publish_state(value == "ON"); else if (key == "WiFi") id(wifiOnOff).publish_state(value == "ON"); else if (key == "Melody Sounds") id(melodySounds).publish_state(value == "ON"); else if (key == "Warning Sounds") id(warningSounds).publish_state(value == "ON"); else if (key == "Bin Full Detect") id(binFullDetect).publish_state(value == "ON"); } else if (line.find("Schedule is Enabled") != std::string::npos) id(scheduleEnabled).publish_state(true); else if (line.find("Schedule is Disabled") != std::string::npos) id(scheduleEnabled).publish_state(false); } } else if (command.find("") != std::string::npos) id(uiState).publish_state("Starting..."); time: - platform: ${time_platform} id: nbs_time on_time: - minutes: /10 seconds: 0 then: - lambda: !lambda |- auto now = id(nbs_time).now(); if (!now.is_valid()) return; uint8_t flags = id(g_schedule)[0]; // bit 0 = global enable if (!(flags & 0x01)) { ESP_LOGD("schedule", "Global disabled"); return; } // day_of_week: Sunday=1 ... Saturday=7 uint8_t dow = now.day_of_week; // bit for day enable: bit1 = Sunday, bit2 = Monday, ... uint8_t day_bit = 1 << dow; if (!(flags & day_bit)) { ESP_LOGD("schedule", "Day %d disabled", dow); return; } // schedule value for today uint8_t value = id(g_schedule)[dow]; // convert value → hour/min uint8_t target_hour = value / 6; uint8_t target_min = (value % 6) * 10; ESP_LOGD("schedule", "Now %02d:%02d | Target %02d:%02d (value=%d)", now.hour, now.minute, target_hour, target_min, value ); // check match if (now.hour == target_hour && now.minute == target_min) { ESP_LOGD("schedule", "Schedule matched → DO ACTION"); id(houseClean).press(); } text: - platform: template id: scheduleset name: "ScheduleSet" mode: TEXT set_action: lambda: !lambda |- int i = 0; char *p = strtok((char *)x.c_str(), ","); while (p && i < 8) { int v = atoi(p); if (i == 0) { // flags if (v < 0) v = 0; if (v > 255) v = 255; } else { // day values if (v < 0) v = 0; if (v > 143) v = 143; } id(g_schedule)[i++] = (uint8_t)v; p = strtok(nullptr, ","); } if (i != 8) { ESP_LOGW("schedule", "Invalid schedule string"); } char buf[40]; snprintf( buf, sizeof(buf), "%u,%u,%u,%u,%u,%u,%u,%u", id(g_schedule)[0], id(g_schedule)[1], id(g_schedule)[2], id(g_schedule)[3], id(g_schedule)[4], id(g_schedule)[5], id(g_schedule)[6], id(g_schedule)[7] ); id(scheduleset).publish_state(buf); - platform: template id: timezone_text name: "Timezone" mode: TEXT set_action: lambda: !lambda |- id(timezone_text).publish_state(x.c_str()); // update this text component id(g_timezone) = std::string(x.c_str()); // store tz in global id(nbs_time).set_timezone(x.c_str()); // set timezone for time ESP_LOGI("timezone", "Timezone set to: %s", x.c_str()); // log sensor: # --- GetCharger --- - platform: template id: chargerFuelPercent name: "Fuel Percent" unit_of_measurement: "%" - platform: template id: chargerBattTempCAvg name: "Battery Temp C Avg" unit_of_measurement: "°C" entity_category: "diagnostic" - platform: template id: chargerVBattV name: "Battery Voltage V" unit_of_measurement: "V" entity_category: "diagnostic" - platform: template id: chargerVExtV name: "External Voltage V" unit_of_measurement: "V" entity_category: "diagnostic" - platform: template id: chargerCharger_mAH name: "Charger mAh" unit_of_measurement: "mAh" entity_category: "diagnostic" - platform: template id: chargerDischarge_mAH name: "Discharge mAh" unit_of_measurement: "mAh" entity_category: "diagnostic" binary_sensor: # --- GetCharger --- - platform: template id: chargerBatteryOverTemp name: "Battery Over Temp" entity_category: "diagnostic" - platform: template id: chargerChargingActive name: "Charging Active" entity_category: "diagnostic" - platform: template id: chargerChargingEnabled name: "Charging Enabled" entity_category: "diagnostic" - platform: template id: chargerConfidentOnFuel name: "Confident On Fuel" entity_category: "diagnostic" - platform: template id: chargerOnReservedFuel name: "On Reserved Fuel" entity_category: "diagnostic" - platform: template id: chargerEmptyFuel name: "Empty Fuel" entity_category: "diagnostic" - platform: template id: chargerBatteryFailure name: "Battery Failure" entity_category: "diagnostic" - platform: template id: chargerExtPwrPresent name: "Ext Power Present" entity_category: "diagnostic" - platform: template id: chargerThermistorPresent name: "Thermistor Present" entity_category: "diagnostic" text_sensor: - platform: template id: nbs_time_text name: "NBS Time" lambda: !lambda |- char buf[10]; auto t = id(nbs_time).now(); snprintf(buf, sizeof(buf), "%02d:%02d", t.hour, t.minute); return std::string(buf); # --- GetErr --- - platform: template id: robotError name: "Robot Error" # --- GetVersion --- - platform: template id: robotSerial name: "Serial Number" entity_category: "diagnostic" - platform: template id: robotModel name: "Model" entity_category: "diagnostic" - platform: template id: robotSoftware name: "Software" entity_category: "diagnostic" # --- GetState --- - platform: template name: "UI State" id: uiState switch: - platform: template name: "Test Mode" # optimistic: true id: testMode turn_on_action: - uart.write: "TestMode On \n" - delay: 500ms - uart.write: "SetSystemMode \n" turn_off_action: - uart.write: "TestMode Off \n" - delay: 500ms - uart.write: "SetSystemMode \n" entity_category: "diagnostic" - platform: template name: "Play Extra Sounds" optimistic: true id: switch_play_extra_sounds turn_on_action: - lambda: 'id(g_play_extra_sounds) = true;' turn_off_action: - lambda: 'id(g_play_extra_sounds) = false;' entity_category: "diagnostic" # --- SetUserSettings --- - platform: template name: "Click sounds" optimistic: true id: clickSounds turn_on_action: - uart.write: "SetUserSettings ButtonClick ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings ButtonClick OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "StealthLED" optimistic: true id: stealthLed turn_on_action: - uart.write: "SetUserSettings StealthLED ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings StealthLED OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "LEDs enabled" optimistic: true id: led turn_on_action: - uart.write: "SetUserSettings LEDEnable ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings LEDEnable OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "AutoShutdown" optimistic: true id: autoShutdownEnabled turn_on_action: - uart.write: "SetUserSettings AutoShutdown ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings AutoShutdown OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "Wall Enable" optimistic: true id: wallEnable turn_on_action: - uart.write: "SetUserSettings WallEnable ON \n" - delay: 100ms - uart.write: "GetUserSettings \n" turn_off_action: - uart.write: "SetUserSettings WallEnable OFF \n" - delay: 100ms - uart.write: "GetUserSettings \n" entity_category: "diagnostic" - platform: template name: "Eco Mode" optimistic: true id: ecoMode turn_on_action: - uart.write: "SetUserSettings EcoMode ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings EcoMode OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "IntenseClean" optimistic: true id: intenseClean turn_on_action: - uart.write: "SetUserSettings IntenseClean ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings IntenseClean OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "WiFi" optimistic: true id: wifiOnOff turn_on_action: - uart.write: "SetUserSettings WiFi ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings WiFi OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "Melody Sounds" optimistic: true id: melodySounds turn_on_action: - uart.write: "SetUserSettings Melodies ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings Melodies OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "Warning Sounds" optimistic: true id: warningSounds turn_on_action: - uart.write: "SetUserSettings Warnings ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings Warnings OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "Bin Full Detect" optimistic: true id: binFullDetect turn_on_action: - uart.write: "SetUserSettings BinFullDetect ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings BinFullDetect OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "Robot Schedule" optimistic: true id: scheduleEnabled turn_on_action: - uart.write: "SetUserSettings Schedule ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings Schedule OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" number: # --- Spot cleaning --- - platform: template id: spot_width min_value: 100 max_value: 400 step: 1 unit_of_measurement: cm mode: SLIDER name: Spot Clean Width optimistic: true - platform: template id: spot_height min_value: 100 max_value: 400 step: 1 unit_of_measurement: cm mode: SLIDER name: Spot Clean Height optimistic: true select: - platform: logger name: "Logger select" button: - platform: template name: "House Clean" id: houseClean icon: mdi:home on_press: - script.execute: id: play_extra_sounds soundid: 1 - uart.write: "Clean House \n" - platform: template name: "Spot Clean" icon: mdi:target on_press: - script.execute: id: play_extra_sounds soundid: 1 - uart.write: "Clean Spot \n" - platform: template name: "Spot Clean (Height & Width)" icon: mdi:target on_press: then: - uart.write: !lambda |- // Convert numbers to integers int w = (int) id(spot_width).state; int h = (int) id(spot_height).state; std::string cmd = "Clean Spot Width " + std::to_string(w) + " Height " + std::to_string(h) + "\n"; id(play_extra_sounds)->execute(1); return std::vector(cmd.begin(), cmd.end()); - platform: template name: "Stop Cleaning" icon: mdi:stop on_press: - uart.write: "SetButton start \n" - delay: 1250ms - uart.write: "SetButton soft \n" - platform: template name: "Pause Cleaning" icon: mdi:pause on_press: - uart.write: "SetButton start \n" - platform: template name: "Resume Cleaning" icon: mdi:play on_press: - uart.write: "SetButton start \n" - platform: template name: "Send to start" icon: mdi:home on_press: - uart.write: "SetButton down \n" - delay: 1250ms - uart.write: "SetButton soft \n" - platform: template name: "Locate Robot" icon: mdi:volume-high on_press: - uart.write: "PlaySound SoundId 2 \n" - platform: template name: "Update status" icon: mdi:refresh on_press: - script.execute: get_all_data entity_category: "diagnostic" - platform: template name: "Clear errors" icon: mdi:notification-clear-all on_press: - uart.write: "GetErr clear \n" entity_category: "diagnostic" - platform: template name: "Shutdown" icon: mdi:power on_press: - script.execute: robotShutdown entity_category: "diagnostic" - platform: template name: "PowerCycle" icon: mdi:restart on_press: - script.execute: powercycle entity_category: "diagnostic" - platform: restart name: Reboot ESP ================================================ FILE: config/comp/gen3.yaml ================================================ substitutions: version: "1.2.1" type: "gen3" buildinfo: "${version} [TX:${uart_tx} RX:${uart_rx}]" logger: improv_serial: captive_portal: wifi: ap: ssid: "neato-brainslug" password: "make-it-suck-again" ota: - platform: esphome password: ${ota_password} esphome: name: ${name} project: name: philip2809.neato-brainslug version: ${buildinfo} comment: "${type}|${buildinfo}|${comment}" on_boot: - priority: 600 then: - lambda: !lambda |- id(uiState).publish_state("Starting..."); - logger.set_level: INFO - priority: -100 then: - lambda: !lambda |- id(uiState).publish_state("Starting..."); ESPTime t = ESPTime::from_epoch_local(id(g_last_cleaning_start_time)); std::string ts = t.strftime("%Y-%m-%d %H:%M:%S"); id(last_cleaning_time).publish_state(ts.c_str()); if (id(g_last_cleaning_type) == 0) { id(last_cleaning_type).publish_state("HOUSE"); } else if (id(g_last_cleaning_type) == 1) { id(last_cleaning_type).publish_state("SPOT"); } id(last_cleaning_duration).publish_state(id(g_last_cleaning_duration)); // Update nbs_time and text entity from persisted global if (!id(g_timezone).empty()) { id(nbs_time).set_timezone(id(g_timezone).c_str()); id(timezone_text).publish_state(id(g_timezone).c_str()); ESP_LOGI("timezone", "Restored timezone: %s", id(g_timezone).c_str()); } char buf[40]; snprintf( buf, sizeof(buf), "%u,%u,%u,%u,%u,%u,%u,%u", id(g_schedule)[0], id(g_schedule)[1], id(g_schedule)[2], id(g_schedule)[3], id(g_schedule)[4], id(g_schedule)[5], id(g_schedule)[6], id(g_schedule)[7] ); id(scheduleset).publish_state(buf); script: - id: get_user_settings mode: queued then: - delay: 100ms - uart.write: "GetUserSettings \n" - id: get_all_data mode: queued then: - uart.write: "GetErr \n" - delay: 50ms - uart.write: "GetState \n" - delay: 50ms - uart.write: "GetCharger \n" - delay: 50ms - uart.write: "GetUserSettings \n" - delay: 50ms - uart.write: "GetWarranty \n" - delay: 50ms - uart.write: "TestMode \n" - delay: 50ms - uart.write: "GetVersion \n" - id: initial_data_get # when the robot has booted, run this initial fetch after 5 seconds of being able to send commands, the 5 sec needed to the robot can calculate battery level etc. mode: queued then: - delay: 5sec - script.execute: get_all_data - id: robotShutdown mode: queued then: - lambda: 'id(uiState).publish_state("Shutting down...");' - uart.write: "TestMode On \n" - delay: 100ms - uart.write: "SetSystemMode Shutdown \n" - id: powercycle mode: queued then: - lambda: 'id(initial_ready) = false;' # I want to refetch all data in case we powercycle - lambda: 'id(uiState).publish_state("Shutting down...");' - uart.write: "TestMode On \n" - delay: 100ms - uart.write: "SetSystemMode PowerCycle \n" - id: cleaning_stopped then: - lambda: !lambda |- uint64_t now_ts = id(nbs_time).now().timestamp; uint64_t start_ts = id(g_last_cleaning_start_time); if (start_ts > 0) { uint32_t duration_min = (now_ts - start_ts) / 60; id(g_last_cleaning_duration) = duration_min; id(last_cleaning_duration).publish_state(duration_min); } - id: play_extra_sounds parameters: soundid: int then: - if: condition: lambda: |- return id(g_play_extra_sounds); then: - uart.write: !lambda |- std::string s = "PlaySound SoundId " + std::to_string(soundid) + "\n"; return std::vector(s.begin(), s.end()); - id: send_event parameters: event: string then: - uart.write: !lambda |- std::string s = "SetEvent event " + event + " SKey " + id(robot_skey) + "\n"; return std::vector(s.begin(), s.end()); globals: - id: initial_ready type: bool restore_value: no initial_value: 'false' - id: robot_skey type: std::string restore_value: no # --- Restore Value --- - id: g_last_cleaning_start_time type: uint64_t restore_value: yes - id: g_last_cleaning_type type: uint32_t restore_value: yes - id: g_last_cleaning_duration type: uint32_t restore_value: yes - id: g_play_extra_sounds type: bool restore_value: yes - id: g_schedule type: uint8_t[8] restore_value: yes initial_value: '{0,0,0,0,0,0,0,0}' - id: g_timezone type: std::string restore_value: yes initial_value: '' interval: - interval: ${infointerval} then: - uart.write: "GetErr \n" - uart.write: "GetState \n" - interval: ${chargerinterval} then: - uart.write: "GetCharger \n" uart: id: uart_bus baud_rate: 115200 tx_pin: ${uart_tx} rx_pin: ${uart_rx} rx_buffer_size: 2048 debug: dummy_receiver: true direction: RX after: bytes: 0 delimiter: "\x1A" sequence: - lambda: !lambda |- UARTDebug::log_string(direction, bytes); // convert bytes -> std::string std::string str(bytes.begin(), bytes.end()); // ---- TRIM FUNCTION ---- auto trim = [](std::string &s) { // trim leading s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch){ return !std::isspace(ch); })); // trim trailing s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch){ return !std::isspace(ch); }).base(), s.end()); }; // --- HEX DECODER --- auto hex_to_uint32 = [](const std::string &hex) -> uint32_t { return static_cast(strtoul(hex.c_str(), nullptr, 16)); }; // ---- SPLIT ON CRLF ---- std::vector lines; size_t start = 0; size_t end = 0; while ((end = str.find("\r\n", start)) != std::string::npos) { std::string line = str.substr(start, end - start); trim(line); if (!line.empty()) lines.push_back(line); start = end + 2; } if (start < str.size()) { std::string last = str.substr(start); trim(last); if (!last.empty()) lines.push_back(last); } if (lines.empty()) return; // ---- DETECT WHICH COMMAND WAS USED ---- std::string command = lines[0]; // first line (GetErr, GetState, etc.) ESP_LOGI("uart_parser", "COMMAND: %s", command.c_str()); // ---- OPTIONAL: print each line for debugging ---- for (auto &line : lines) { std::vector v(line.begin(), line.end()); UARTDebug::log_string(direction, v); //ESP_LOGI("uart_parser", "LINE: %s", line.c_str()); } // ---- SPECIAL PARSING FOR GetErr ---- if (command == "GetErr") { // A GetErr response looks like for gen3: // 0: GetErr // 1: Error // 2: // 3: Alert // 4: // 5: USB state // 6: NOT connected if (lines.size() < 7) return; if (lines[1] != "Error" || lines[3] != "Alert") return; id(robotError).publish_state(lines[2].c_str()); id(robotAlert).publish_state(lines[4].c_str()); id(usbConnected).publish_state(lines[6].find("NOT connected") == std::string::npos); // If we can get erros/alerts, the system is ready for other commands: if (!id(initial_ready)) { id(initial_ready) = true; id(initial_data_get).execute(); } } else if (command == "GetCharger") { // Format after the header: // Label,Value // FuelPercent,53 // BatteryOverTemp,0 // ... for (size_t i = 2; i < lines.size(); i++) { std::string &line = lines[i]; size_t comma = line.find(','); if (comma == std::string::npos) continue; std::string key = line.substr(0, comma); std::string value = line.substr(comma + 1); // Trim them: trim(key); trim(value); // ---- MATCH KEYS AND PUBLISH ---- if (key == "FuelPercent") id(chargerFuelPercent).publish_state(atof(value.c_str())); else if (key == "BatteryOverTemp") id(chargerBatteryOverTemp).publish_state(value == "1"); else if (key == "ChargingActive") id(chargerChargingActive).publish_state(value == "1"); else if (key == "ChargingEnabled") id(chargerChargingEnabled).publish_state(value == "1"); else if (key == "ConfidentOnFuel") id(chargerConfidentOnFuel).publish_state(value == "1"); else if (key == "OnReservedFuel") id(chargerOnReservedFuel).publish_state(value == "1"); else if (key == "EmptyFuel") id(chargerEmptyFuel).publish_state(value == "1"); else if (key == "BatteryFailure") id(chargerBatteryFailure).publish_state(value == "1"); else if (key == "ExtPwrPresent") id(chargerExtPwrPresent).publish_state(value == "1"); else if (key == "ThermistorPresent") id(chargerThermistorPresent).publish_state(value == "1"); else if (key == "BattTempCAvg") id(chargerBattTempCAvg).publish_state(atof(value.c_str())); else if (key == "VBattV") id(chargerVBattV).publish_state(atof(value.c_str())); else if (key == "VExtV") id(chargerVExtV).publish_state(atof(value.c_str())); else if (key == "Charger_mAH") id(chargerCharger_mAH).publish_state(atof(value.c_str())); else if (key == "Discharge_mAH") id(chargerDischarge_mAH).publish_state(atof(value.c_str())); } } else if (command == "GetWarranty") { // Format after the header: // Item,Value // CumulativeCleaningTimeInSecs,001ce34f // CumulativeBatteryCycles,01f7 // ValidationCode,15648b6e for (size_t i = 2; i < lines.size(); i++) { std::string &line = lines[i]; size_t comma = line.find(','); if (comma == std::string::npos) continue; std::string key = line.substr(0, comma); std::string value = line.substr(comma + 1); // Trim whitespace trim(key); trim(value); if (key == "CumulativeBatteryCycles") { uint32_t cycles = hex_to_uint32(value); id(batteryCycles).publish_state(cycles); } } } else if (command == "GetVersion") { for (auto &line : lines) { size_t comma = line.find(','); if (comma == std::string::npos) continue; std::string key = line.substr(0, comma); std::string value = line.substr(comma + 1); trim(key); trim(value); if (key == "Serial Number") id(robotSerial).publish_state(value.c_str()); else if (key == "Model") id(robotModel).publish_state(value.c_str()); else if (key == "Software") id(robotSoftware).publish_state(value.c_str()); } } else if (command == "TestMode") { if (lines[1].find(": On") != std::string::npos) id(testMode).publish_state(true); else if (lines[1].find(": Off") != std::string::npos) id(testMode).publish_state(false); } else if (command == "GetState") { std::string &states_line = lines[1]; // "Current UI State is: ...\nCurrent Robot State is: ...\n\x1A" size_t sep = states_line.find('\n'); if (sep == std::string::npos) return; // safety std::string ui_line = states_line.substr(0, sep); std::string robot_line = states_line.substr(sep + 1); static const char* prefix_ui = "Current UI State is: "; static const char* prefix_robot = "Current Robot State is: "; // Extract UI state if (ui_line.find(prefix_ui) == 0) { std::string ui_state = ui_line.substr(strlen(prefix_ui)); trim(ui_state); std::string old_state = id(uiState).state; bool old_spot = (old_state == "UIMGR_STATE_SPOTCLEANINGPAUSED" || old_state == "UIMGR_STATE_SPOTCLEANINGRUNNING"); bool new_spot = (ui_state == "UIMGR_STATE_SPOTCLEANINGPAUSED" || ui_state == "UIMGR_STATE_SPOTCLEANINGRUNNING"); bool old_house = (old_state == "UIMGR_STATE_HOUSECLEANINGPAUSED" || old_state == "UIMGR_STATE_HOUSECLEANINGRUNNING"); bool new_house = (ui_state == "UIMGR_STATE_HOUSECLEANINGPAUSED" || ui_state == "UIMGR_STATE_HOUSECLEANINGRUNNING"); // Cleaning started if (!old_spot && new_spot) { // Mark start time id(g_last_cleaning_start_time) = id(nbs_time).now().timestamp; id(g_last_cleaning_type) = 1; // SPOT = 1 id(last_cleaning_time).publish_state(id(nbs_time).now().strftime("%Y-%m-%d %H:%M:%S")); id(last_cleaning_type).publish_state("SPOT"); } else if (!old_house && new_house) { id(g_last_cleaning_start_time) = id(nbs_time).now().timestamp; id(g_last_cleaning_type) = 0; // HOUSE = 0 id(last_cleaning_time).publish_state(id(nbs_time).now().strftime("%Y-%m-%d %H:%M:%S")); id(last_cleaning_type).publish_state("HOUSE"); } else { // Cleaning stopped bool old_cleaning = old_spot || old_house; bool new_cleaning = new_spot || new_house; if (old_cleaning && !new_cleaning) id(cleaning_stopped).execute(); } id(uiState).publish_state(ui_state.c_str()); } // Extract Robot state if (robot_line.find(prefix_robot) == 0) { std::string robot_state = robot_line.substr(strlen(prefix_robot)); // Remove trailing \x1A if present size_t ctrl_z = robot_state.find('\x1A'); if (ctrl_z != std::string::npos) { robot_state.erase(ctrl_z); } trim(robot_state); id(robotState).publish_state(robot_state.c_str()); } } else if (command == "GetUserSettings") { for (auto &line : lines) { trim(line); // Skip lines that start with binary/invalid chars if (!line.empty() && static_cast(line[0]) >= 0x80) continue; // ---- Key,Value lines ---- size_t comma = line.find(','); if (comma != std::string::npos) { std::string key = line.substr(0, comma); std::string value = line.substr(comma + 1); trim(key); trim(value); if (key == "ClickSounds") id(clickSounds).publish_state(value == "ON"); else if (key == "LED") id(led).publish_state(value == "ON"); else if (key == "Wall Enable") id(wallEnable).publish_state(value == "ON"); else if (key == "Eco Mode") id(ecoMode).publish_state(value == "ON"); else if (key == "IntenseClean") id(intenseClean).publish_state(value == "ON"); else if (key == "WiFi") id(wifiOnOff).publish_state(value == "ON"); else if (key == "Melody Sounds") id(melodySounds).publish_state(value == "ON"); else if (key == "Warning Sounds") id(warningSounds).publish_state(value == "ON"); else if (key == "Bin Full Detect") id(binFullDetect).publish_state(value == "ON"); else if (key == "Filter Change Time (seconds)") id(filterChangeTime).publish_state(atof(value.c_str())); else if (key == "Brush Change Time (seconds)") id(brushChangeTime).publish_state(atof(value.c_str())); else if (key == "Dirt Bin Alert Reminder Interval (minutes)") id(dirtBinAlertReminder).publish_state(atof(value.c_str())); } // ---- Special lines without commas ---- else if (line.find("Current Dirt Bin Runtime is:") == 0) { std::string val = line.substr(strlen("Current Dirt Bin Runtime is:")); trim(val); id(currentDirtBinRuntime).publish_state(atof(val.c_str())); } else if (line.find("Number of Cleanings where Dust Bin was Full is:") == 0) { std::string val = line.substr(strlen("Number of Cleanings where Dust Bin was Full is:")); trim(val); id(numberDustBinFull).publish_state(atof(val.c_str())); } } } else if (command.find("ARCHES Board") != std::string::npos) id(uiState).publish_state("Starting..."); time: - platform: ${time_platform} id: nbs_time on_time: - minutes: /10 seconds: 0 then: - lambda: !lambda |- auto now = id(nbs_time).now(); if (!now.is_valid()) return; uint8_t flags = id(g_schedule)[0]; // bit 0 = global enable if (!(flags & 0x01)) { ESP_LOGD("schedule", "Global disabled"); return; } // day_of_week: Sunday=1 ... Saturday=7 uint8_t dow = now.day_of_week; // bit for day enable: bit1 = Sunday, bit2 = Monday, ... uint8_t day_bit = 1 << dow; if (!(flags & day_bit)) { ESP_LOGD("schedule", "Day %d disabled", dow); return; } // schedule value for today uint8_t value = id(g_schedule)[dow]; // convert value → hour/min uint8_t target_hour = value / 6; uint8_t target_min = (value % 6) * 10; ESP_LOGD("schedule", "Now %02d:%02d | Target %02d:%02d (value=%d)", now.hour, now.minute, target_hour, target_min, value ); // check match if (now.hour == target_hour && now.minute == target_min) { ESP_LOGD("schedule", "Schedule matched → DO ACTION"); id(houseClean).press(); } text: - platform: template id: scheduleset name: "ScheduleSet" mode: TEXT set_action: lambda: !lambda |- int i = 0; char *p = strtok((char *)x.c_str(), ","); while (p && i < 8) { int v = atoi(p); if (i == 0) { // flags if (v < 0) v = 0; if (v > 255) v = 255; } else { // day values if (v < 0) v = 0; if (v > 143) v = 143; } id(g_schedule)[i++] = (uint8_t)v; p = strtok(nullptr, ","); } if (i != 8) { ESP_LOGW("schedule", "Invalid schedule string"); } char buf[40]; snprintf( buf, sizeof(buf), "%u,%u,%u,%u,%u,%u,%u,%u", id(g_schedule)[0], id(g_schedule)[1], id(g_schedule)[2], id(g_schedule)[3], id(g_schedule)[4], id(g_schedule)[5], id(g_schedule)[6], id(g_schedule)[7] ); id(scheduleset).publish_state(buf); - platform: template id: timezone_text name: "Timezone" mode: TEXT set_action: lambda: !lambda |- id(timezone_text).publish_state(x.c_str()); // update this text component id(g_timezone) = std::string(x.c_str()); // store tz in global id(nbs_time).set_timezone(x.c_str()); // set timezone for time ESP_LOGI("timezone", "Timezone set to: %s", x.c_str()); // log sensor: # --- GetCharger --- - platform: template id: chargerFuelPercent name: "Fuel Percent" unit_of_measurement: "%" - platform: template id: chargerBattTempCAvg name: "Battery Temp C Avg" unit_of_measurement: "°C" entity_category: "diagnostic" - platform: template id: chargerVBattV name: "Battery Voltage V" unit_of_measurement: "V" entity_category: "diagnostic" - platform: template id: chargerVExtV name: "External Voltage V" unit_of_measurement: "V" entity_category: "diagnostic" - platform: template id: chargerCharger_mAH name: "Charger mAh" unit_of_measurement: "mAh" entity_category: "diagnostic" - platform: template id: chargerDischarge_mAH name: "Discharge mAh" unit_of_measurement: "mAh" entity_category: "diagnostic" # --- GetUserSettings --- - platform: template name: "Filter Change Time" id: filterChangeTime unit_of_measurement: "s" entity_category: "diagnostic" - platform: template name: "Brush Change Time" id: brushChangeTime unit_of_measurement: "s" entity_category: "diagnostic" - platform: template name: "Dirt Bin Alert Reminder" id: dirtBinAlertReminder unit_of_measurement: "min" entity_category: "diagnostic" - platform: template name: "Current Dirt Bin Runtime" id: currentDirtBinRuntime unit_of_measurement: "s" entity_category: "diagnostic" - platform: template name: "Number of Full Dust Bin Cleanings" id: numberDustBinFull unit_of_measurement: "count" entity_category: "diagnostic" # --- GetWarranty --- - platform: template name: "Battery Cycles" id: batteryCycles unit_of_measurement: "cycles" entity_category: "diagnostic" # --- Last cleaning --- - platform: template name: "Last cleaning duration" id: last_cleaning_duration unit_of_measurement: "min" binary_sensor: # --- GetErr --- - platform: template id: usbConnected name: "USB Connected" entity_category: "diagnostic" # --- GetCharger --- - platform: template id: chargerBatteryOverTemp name: "Battery Over Temp" entity_category: "diagnostic" - platform: template id: chargerChargingActive name: "Charging Active" entity_category: "diagnostic" - platform: template id: chargerChargingEnabled name: "Charging Enabled" entity_category: "diagnostic" - platform: template id: chargerConfidentOnFuel name: "Confident On Fuel" entity_category: "diagnostic" - platform: template id: chargerOnReservedFuel name: "On Reserved Fuel" entity_category: "diagnostic" - platform: template id: chargerEmptyFuel name: "Empty Fuel" entity_category: "diagnostic" - platform: template id: chargerBatteryFailure name: "Battery Failure" entity_category: "diagnostic" - platform: template id: chargerExtPwrPresent name: "Ext Power Present" entity_category: "diagnostic" - platform: template id: chargerThermistorPresent name: "Thermistor Present" entity_category: "diagnostic" text_sensor: - platform: template id: nbs_time_text name: "NBS Time" lambda: !lambda |- char buf[10]; auto t = id(nbs_time).now(); snprintf(buf, sizeof(buf), "%02d:%02d", t.hour, t.minute); return std::string(buf); # --- Last cleaning --- - platform: template name: "Last cleaning time" id: last_cleaning_time - platform: template name: "Last cleaning type" id: last_cleaning_type # --- GetErr --- - platform: template id: robotError name: "Robot Error" - platform: template id: robotAlert name: "Robot Alert" # --- GetVersion --- - platform: template id: robotSerial name: "Serial Number" entity_category: "diagnostic" on_value: then: - lambda: !lambda |- // Get the mac address, abort if the serial is malformed size_t comma_pos = x.find(','); if (comma_pos == std::string::npos) { ESP_LOGE("config", "Serial invalid (no comma): %s", x.c_str()); abort(); } std::string mac = x.substr(comma_pos + 1, 12); if (mac.length() != 12) { ESP_LOGE("config", "Invalid MAC length (%d): %s", mac.length(), mac.c_str()); abort(); } // Calculate based on mac static uint8_t t1[256]; static uint8_t t2[256]; static char result[128]; const uint8_t byte_array[] = { 0x68, 0x36, 0x43, 0x58, 0x09, 0x09, 0x3A, 0x3C, 0x2A, 0x7B, 0x59 }; for (int i = 0; i < 256; i++) t1[i] = i; int j = 0, i = 0; for (int i = 0; i < 256; i++) { j = (j + t1[i] + byte_array[i % 11]) & 0xFF; uint8_t tmp = t1[i]; t1[i] = t1[j]; t1[j] = tmp; } j = 0; for (size_t k = 0; k < 12; k++) { i = (i + 1) & 0xFF; j = (j + t1[i]) & 0xFF; uint8_t tmp = t1[i]; t1[i] = t1[j]; t1[j] = tmp; t2[k] = t1[(t1[i] + t1[j]) & 0xFF]; } size_t len = mac.length(); for (size_t j = 0; j < len; j++) { snprintf(&result[j * 2], 3, "%02x", t2[j] ^ (uint8_t) mac[j]); } result[len * 2] = result[len / 2]; id(robot_skey) = std::string(result); - platform: template id: robotModel name: "Model" entity_category: "diagnostic" - platform: template id: robotSoftware name: "Software" entity_category: "diagnostic" # --- GetState --- - platform: template name: "UI State" id: uiState - platform: template name: "Robot State" id: robotState switch: - platform: template name: "Test Mode" # optimistic: true id: testMode turn_on_action: - uart.write: "TestMode On \n" - delay: 500ms - uart.write: "TestMode \n" turn_off_action: - uart.write: "TestMode Off \n" - delay: 500ms - uart.write: "TestMode \n" entity_category: "diagnostic" - platform: template name: "Play Extra Sounds" optimistic: true id: switch_play_extra_sounds turn_on_action: - lambda: 'id(g_play_extra_sounds) = true;' turn_off_action: - lambda: 'id(g_play_extra_sounds) = false;' entity_category: "diagnostic" # --- SetUserSettings --- - platform: template name: "Click sounds" optimistic: true id: clickSounds turn_on_action: - uart.write: "SetUserSettings ButtonClick ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings ButtonClick OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "LED" optimistic: true id: led turn_on_action: - uart.write: "SetUserSettings StealthLED ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings StealthLED OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "Wall Enable" optimistic: true id: wallEnable turn_on_action: - uart.write: "SetUserSettings WallEnable ON \n" - delay: 100ms - uart.write: "GetUserSettings \n" turn_off_action: - uart.write: "SetUserSettings WallEnable OFF \n" - delay: 100ms - uart.write: "GetUserSettings \n" entity_category: "diagnostic" - platform: template name: "Eco Mode" optimistic: true id: ecoMode turn_on_action: - uart.write: "SetUserSettings EcoMode ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings EcoMode OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "IntenseClean" optimistic: true id: intenseClean turn_on_action: - uart.write: "SetUserSettings IntenseClean ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings IntenseClean OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "WiFi" optimistic: true id: wifiOnOff turn_on_action: - uart.write: "SetUserSettings WiFi ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings WiFi OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "Melody Sounds" optimistic: true id: melodySounds turn_on_action: - uart.write: "SetUserSettings Melodies ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings Melodies OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "Warning Sounds" optimistic: true id: warningSounds turn_on_action: - uart.write: "SetUserSettings Warnings ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings Warnings OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" - platform: template name: "Bin Full Detect" optimistic: true id: binFullDetect turn_on_action: - uart.write: "SetUserSettings BinFullDetect ON \n" - script.execute: get_user_settings turn_off_action: - uart.write: "SetUserSettings BinFullDetect OFF \n" - script.execute: get_user_settings entity_category: "diagnostic" number: # --- Spot cleaning --- - platform: template id: spot_width min_value: 100 max_value: 400 step: 1 unit_of_measurement: cm mode: SLIDER name: Spot Clean Width optimistic: true - platform: template id: spot_height min_value: 100 max_value: 400 step: 1 unit_of_measurement: cm mode: SLIDER name: Spot Clean Height optimistic: true select: - platform: logger name: "Logger select" - platform: template name: "Navigation Mode" id: navigationMode optimistic: true options: - "Normal" - "Gentle" - "Deep" - "Quick" icon: "mdi:robot-vacuum" on_value: then: - uart.write: !lambda |- std::string cmd = std::string("SetNavigationMode ") + x + "\n"; return std::vector(cmd.begin(), cmd.end()); button: - platform: template name: "House Clean" id: houseClean icon: mdi:home on_press: - script.execute: id: play_extra_sounds soundid: 1 - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_START_HOUSE_CLEANING" - platform: template name: "Spot Clean" icon: mdi:target on_press: - script.execute: id: play_extra_sounds soundid: 1 - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_START_SPOT_CLEANING" - platform: template name: "Spot Clean (Height & Width)" icon: mdi:target on_press: then: - uart.write: !lambda |- // Convert numbers to integers int w = (int) id(spot_width).state; int h = (int) id(spot_height).state; std::string cmd = "Clean Spot Width " + std::to_string(w) + " Height " + std::to_string(h) + "\n"; id(play_extra_sounds)->execute(1); return std::vector(cmd.begin(), cmd.end()); - platform: template name: "Stop Cleaning" icon: mdi:stop on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_STOP_CLEANING" - platform: template name: "Pause Cleaning" icon: mdi:pause on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_PAUSE_CLEANING" - platform: template name: "Send to base" icon: mdi:home on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_SEND_TO_BASE" - platform: template name: "Resume Cleaning" icon: mdi:play on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_RESUME_CLEANING" - platform: template name: "Locate Robot" icon: mdi:volume-high on_press: - uart.write: "PlaySound SoundId 20 \n" - platform: template name: "Manual Drive Forward Up" icon: mdi:arrow-up on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_FORWARD_UP" - platform: template name: "Manual Drive Backwards Up" icon: mdi:arrow-down on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_BACKWARDS_UP" - platform: template name: "Manual Drive Turn Left Up" icon: mdi:arrow-left on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_TURN_LEFT_UP" - platform: template name: "Manual Drive Turn Right Up" icon: mdi:arrow-right on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_TURN_RIGHT_UP" - platform: template name: "Manual Drive Arc Left Up" icon: mdi:rotate-left on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_ARC_LEFT_UP" - platform: template name: "Manual Drive Arc Right Up" icon: mdi:rotate-right on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_ARC_RIGHT_UP" - platform: template name: "Manual Drive Forward Down" icon: mdi:arrow-up-bold on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_FORWARD_DOWN" - platform: template name: "Manual Drive Backwards Down" icon: mdi:arrow-down-bold on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_BACKWARDS_DOWN" - platform: template name: "Manual Drive Turn Left Down" icon: mdi:arrow-left-bold on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_TURN_LEFT_DOWN" - platform: template name: "Manual Drive Turn Right Down" icon: mdi:arrow-right-bold on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_TURN_RIGHT_DOWN" - platform: template name: "Manual Drive Arc Left Down" icon: mdi:rotate-left on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_ARC_LEFT_DOWN" - platform: template name: "Manual Drive Arc Right Down" icon: mdi:rotate-right on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_ARC_RIGHT_DOWN" - platform: template name: "Manual Drive Button Timeout" icon: mdi:timer-off on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_DRIVE_MANUAL_BTN_TIMEOUT" - platform: template name: "Start Manual Cleaning" icon: mdi:play-circle on_press: - script.execute: id: send_event event: "UIMGR_EVENT_SMARTAPP_START_MANUAL_CLEANING" - platform: template name: "Update status" icon: mdi:refresh on_press: - script.execute: get_all_data entity_category: "diagnostic" - platform: template name: "Clear errors" icon: mdi:notification-clear-all on_press: then: - uart.write: "SetUIError clearall \n" entity_category: "diagnostic" - platform: template name: "Shutdown" icon: mdi:power on_press: - script.execute: robotShutdown entity_category: "diagnostic" - platform: template name: "PowerCycle" icon: mdi:restart on_press: - script.execute: powercycle entity_category: "diagnostic" - platform: restart name: Reboot ESP ================================================ FILE: config/comp/ha.yaml ================================================ substitutions: time_platform: homeassistant wifi: ssid: ${wifi_ssid} password: ${wifi_password} api: encryption: key: ${ha_encryption_key} actions: - action: send_cmd variables: command: string then: - uart.write: !lambda |- // Convert to std::string std::string s = command; // IMPORTANT: keep newline exactly like YAML version if (s.empty() || s.back() != '\n') s += "\n"; // convert string → vector return std::vector(s.begin(), s.end()); - action: play_sound variables: soundid: int then: - uart.write: !lambda |- // Create std::string std::string s = "PlaySound SoundId " + std::to_string(soundid) + "\n"; // convert string → vector return std::vector(s.begin(), s.end()); ================================================ FILE: config/comp/no-ha.yaml ================================================ substitutions: time_platform: sntp ================================================ FILE: config/comp/ros.yaml ================================================ external_components: # - source: github://tube0013/esphome-stream-server-v2 - source: github://oxan/esphome-stream-server stream_server: id: ss buffer_size: 4096 uart_id: uart_bus port: 6638 # binary_sensor: # - platform: stream_server # stream_server: ss # name: "ROS Stream Server Connected" ================================================ FILE: config/comp/webserver.yaml ================================================ web_server: port: 80 js_url: "https://cdn.jsdelivr.net/gh/philip2809/neato-brainslug@main/config/js/${version}.js" ================================================ FILE: config/home-assistant/gen2-card.yaml ================================================ type: vertical-stack variables: NOTE: > Combatability between esphome config and this card is ensured as long as the major version's match, for relase candicates, betas etc (indicated by X.X-a/b/rc.X), the version has to match perfectly otherwise compatability is not promised. Please try to keep the versions the same, and check the github for updates! project: philip2809.neato-brainslug version: "1.2.1" type: "gen2" cards: - type: custom:button-card variables: robot_id: neato_vacuum icon: mdi:robot-vacuum-variant entity: "[[[ return `sensor.${variables.robot_id}_robot_error`; ]]]" size: 50% color_type: icon tap_action: action: none hold_action: action: none double_tap_action: action: none name: | [[[ const uiState = states[`sensor.${variables.robot_id}_ui_state`]?.state || ''; return uiState ]]] styles: icon: - color: | [[[ const uiState = states[`sensor.${variables.robot_id}_ui_state`]?.state || ''; if (uiState === "Starting...") return '#edb926'; if (uiState === "Shutting down...") return 'gray'; const err = states[`sensor.${variables.robot_id}_robot_error`]?.state || ''; if (err !== "No errors") return '#950606'; return '#00cc00'; ]]] card: - border-radius: 20px - display: flex - justify-content: center - align-items: center - margin: 0 auto - position: relative custom_fields: battery: - position: absolute - top: 8px - right: 8px - padding: 4px 8px - border-radius: 8px - font-weight: bold - animation: | [[[ if (states[`binary_sensor.${variables.robot_id}_charging_active`]?.state !== 'on') return ''; const pct = Number(states[`sensor.${variables.robot_id}_fuel_percent`]?.state); return pct > 35 ? 'pulseGreen 3s infinite' : 'pulseOrange 3s infinite'; ]]] - background-color: | [[[ const pct = Number(states[`sensor.${variables.robot_id}_fuel_percent`]?.state); return pct > 35 ? 'rgb(124, 252, 0)' : 'rgb(255, 140, 0)'; ]]] - color: black dock: - position: absolute - top: 8px - left: 8px - padding: 4px - border-radius: 8px - background-color: rgba(255,255,255,0.2) - display: | [[[ return states[`binary_sensor.${variables.robot_id}_ext_power_present`]?.state === 'on' ? 'block' : 'none'; ]]] custom_fields: battery: |- [[[ const v = states[`sensor.${variables.robot_id}_fuel_percent`]?.state; return (v ?? '0') + '%'; ]]] dock: | [[[ if (states[`binary_sensor.${variables.robot_id}_ext_power_present`]?.state === 'on') { return 'DOCKED'; } else { return ''; } ]]] extra_styles: | @keyframes pulseGreen { 0% { box-shadow: 0 0 5px 0 rgba(124, 252, 0, 0.6); } 50% { box-shadow: 0 0 20px 6px rgba(124, 252, 0, 1.0); } 100% { box-shadow: 0 0 5px 0 rgba(124, 252, 0, 0.6); } } @keyframes pulseOrange { 0% { box-shadow: 0 0 5px 0 rgba(255, 140, 0, 0.6); } 50% { box-shadow: 0 0 20px 6px rgba(255, 140, 0, 1.0); } 100% { box-shadow: 0 0 5px 0 rgba(255, 140, 0, 0.6); } } - type: vertical-stack cards: - type: horizontal-stack cards: - type: custom:button-card variables: robot_id: neato_vacuum show_icon: false entity: "[[[ return `sensor.${variables.robot_id}_robot_error`; ]]]" show_state: false styles: card: - border-color: | [[[ const err = states[`sensor.${variables.robot_id}_robot_error`]?.state || ''; return err === "No errors" ? "" : "#950606"; ]]] name: | [[[ const err = states[`sensor.${variables.robot_id}_robot_error`]?.state || ''; return err; ]]] - type: horizontal-stack cards: - type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_house_clean`; ]]]" name: Clean tap_action: action: toggle - type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_resume_cleaning`; ]]]" name: Resume tap_action: action: toggle - type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_pause_cleaning`; ]]]" name: Pause tap_action: action: toggle - type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_spot_clean`; ]]]" name: Spot Clean tap_action: action: toggle hold_action: action: fire-dom-event browser_mod: service: browser_mod.popup data: title: Spot Clean - Height and Width size: normal content: type: vertical-stack cards: - type: entities entities: - entity: number.neato_vacuum_spot_clean_height name: Height icon: mdi:arrow-up-down - entity: number.neato_vacuum_spot_clean_width name: Width icon: mdi:arrow-left-right - type: custom:button-card size: 20% name: Start Spot Cleaning entity: button.neato_vacuum_spot_clean_height_width tap_action: action: toggle - type: horizontal-stack cards: - type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_stop_cleaning`; ]]]" name: STOP tap_action: action: toggle - type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_locate_robot`; ]]]" name: Locate tap_action: action: toggle - type: custom:button-card entity: button.neato_vacuum_send_to_start name: Send to start tap_action: action: toggle - type: custom:button-card variables: robot_id: neato_vacuum name: Settings icon: mdi:cog tap_action: action: fire-dom-event browser_mod: service: browser_mod.popup data: title: More options size: normal content: type: vertical-stack cards: - type: horizontal-stack cards: - show_name: true show_icon: true entity: >- [[[ return `switch.${variables.robot_id}_eco_mode`; ]]] name: ECO Mode type: button - show_name: true show_icon: true entity: >- [[[ return `switch.${variables.robot_id}_test_mode`; ]]] name: Test Mode type: button - entity: >- [[[ return `button.${variables.robot_id}_update_status`; ]]] name: Fetch status show_icon: true type: button tap_action: action: toggle - entity: >- [[[ return `button.${variables.robot_id}_clear_errors`; ]]] name: Clear Errors show_icon: true type: button tap_action: action: toggle - type: entities entities: - entity: >- [[[ return `switch.${variables.robot_id}_play_extra_sounds`; ]]] name: Play extra sounds - entity: >- [[[ return `switch.${variables.robot_id}_click_sounds`; ]]] name: Click Sounds - entity: >- [[[ return `switch.${variables.robot_id}_melody_sounds`; ]]] name: Melody Sounds - entity: >- [[[ return `switch.${variables.robot_id}_warning_sounds`; ]]] name: Warning Sounds - entity: >- [[[ return `switch.${variables.robot_id}_bin_full_detect`; ]]] name: Bin Full Detect - entity: >- [[[ return `switch.${variables.robot_id}_autoshutdown`; ]]] name: Auto Shutdown - entity: "[[[ return `switch.${variables.robot_id}_led`; ]]]" name: LED - entity: >- [[[ return `switch.${variables.robot_id}_stealthled`; ]]] name: Stealth Led - entity: >- [[[ return `switch.${variables.robot_id}_wall_enable`; ]]] name: Wall Follower - entity: >- [[[ return `switch.${variables.robot_id}_intenseclean`; ]]] name: Intense Clean - entity: "[[[ return `switch.${variables.robot_id}_wifi`; ]]]" name: WiFi - entity: >- [[[ return `switch.${variables.robot_id}_robot_schedule`; ]]] name: Robot Schedule state_color: false show_header_toggle: false - type: entities entities: - entity: "[[[ return `sensor.${variables.robot_id}_model`;]]]" name: Model - entity: >- [[[ return `sensor.${variables.robot_id}_serial_number`;]]] name: S/N - entity: "[[[ return `sensor.${variables.robot_id}_software`;]]]" name: Software - entity: "[[[ return `sensor.${variables.robot_id}_nbs_time`;]]]" name: Robot Time - entity: >- [[[ return `sensor.${variables.robot_id}_battery_voltage_v`;]]] name: Battery Voltage - entity: >- [[[ return `sensor.${variables.robot_id}_battery_temp_c_avg`;]]] name: Battery Temp (avg) - entity: >- [[[ return `sensor.${variables.robot_id}_charger_mah`;]]] name: Charging - entity: >- [[[ return `sensor.${variables.robot_id}_discharge_mah`;]]] name: Discharging - type: horizontal-stack cards: - show_name: true show_icon: true entity: "[[[ return `button.${variables.robot_id}_shutdown`;]]]" type: button name: Power Off tap_action: action: toggle - show_name: true show_icon: true entity: >- [[[ return `button.${variables.robot_id}_powercycle`;]]] name: Reboot Robot type: button tap_action: action: toggle - show_name: true show_icon: true type: button entity: >- [[[ return `button.${variables.robot_id}_reboot_esp`;]]] name: Reboot ESP tap_action: action: toggle ================================================ FILE: config/home-assistant/gen2-entity.yaml ================================================ - vacuum: - name: Template Neato Vacuum unique_id: template_neato_vacuum state: "{% set robot_id = 'neato_vacuum' %} {% set error_state = states('sensor.' ~ robot_id ~ '_robot_error') %} {% if is_state('binary_sensor.' ~ robot_id ~ '_ext_power_present', 'on') %} docked {% elif 'No errors' not in error_state %} error {% else %} unknown {% endif %}" attributes: error: "{{ states('sensor.neato_vacuum_robot_error') }}" start: service: button.press target: entity_id: button.neato_vacuum_house_clean clean_spot: service: button.press target: entity_id: button.neato_vacuum_spot_clean pause: service: button.press target: entity_id: button.neato_vacuum_pause_cleaning stop: service: button.press target: entity_id: button.neato_vacuum_stop_cleaning return_to_base: service: button.press target: entity_id: button.neato_vacuum_send_to_base locate: service: button.press target: entity_id: button.neato_vacuum_locate_robot ================================================ FILE: config/home-assistant/gen3-entity.yaml ================================================ - vacuum: - name: Template Neato Vacuum unique_id: template_neato_vacuum state: "{% set robot_id = 'neato_vacuum' %} {% set ui_state = states('sensor.' ~ robot_id ~ '_ui_state') %} {% set error_state = states('sensor.' ~ robot_id ~ '_robot_error') %} {% if '200' not in error_state %} error {% elif is_state('binary_sensor.' ~ robot_id ~ '_ext_power_present', 'on') %} docked {% elif 'STATE_IDLE' or 'STATE_STANDBY' in ui_state %} idle {% elif 'RUNNING' or 'STATE_START' in ui_state %} cleaning {% elif 'PAUSED' in ui_state %} paused {% elif 'DOCKINGRUNNING' in ui_state %} returning {% else %} error {% endif %}" attributes: alert: "{{ states('sensor.neato_vacuum_robot_alert') }}" error: "{{ states('sensor.neato_vacuum_robot_error') }}" start: service: button.press target: entity_id: button.neato_vacuum_house_clean clean_spot: service: button.press target: entity_id: button.neato_vacuum_spot_clean pause: service: button.press target: entity_id: button.neato_vacuum_pause_cleaning stop: service: button.press target: entity_id: button.neato_vacuum_stop_cleaning return_to_base: service: button.press target: entity_id: button.neato_vacuum_send_to_base locate: service: button.press target: entity_id: button.neato_vacuum_locate_robot ================================================ FILE: config/home-assistant/gen3_card.yaml ================================================ type: vertical-stack variables: NOTE: > Combatability between esphome config and this card is ensured as long as the major version's match, for relase candicates, betas etc (indicated by X.X-a/b/rc.X), the version has to match perfectly otherwise compatability is not promised. Please try to keep the versions the same, and check the github for updates! project: philip2809.neato-brainslug version: "1.2.1" type: "gen3" cards: - type: custom:button-card variables: robot_id: neato_vacuum icon: mdi:robot-vacuum-variant entity: "[[[ return `sensor.${variables.robot_id}_robot_error`; ]]]" size: 50% color_type: icon tap_action: action: none hold_action: action: none double_tap_action: action: none name: | [[[ const uiState = states[`sensor.${variables.robot_id}_ui_state`]?.state || ''; if (uiState.includes('STATE_IDLE')) return 'IDLE'; else if (uiState.includes('STATE_STANDBY')) return 'STANDBY'; else if (uiState.includes('STATE_TESTMODE')) return 'Testmode on!'; else if (uiState.includes('STATE_USB_LOGCOPY')) return 'Copying logs to USB'; else if (uiState.includes('STATE_STARTHOUSECLEANING')) return 'Starting House Cleaning'; else if (uiState.includes('STATE_HOUSECLEANINGRUNNING')) return 'Cleaning House'; else if (uiState.includes('STATE_HOUSECLEANINGPAUSED')) return 'House Cleaning Paused'; else if (uiState.includes('STATE_STARTSPOTCLEANING')) return 'Starting Spot Clean'; else if (uiState.includes('STATE_SPOTCLEANINGRUNNING')) return 'Spot Cleaning'; else if (uiState.includes('STATE_SPOTCLEANINGPAUSED')) return 'Spot Cleaning Paused'; else return uiState || 'Unknown'; ]]] styles: icon: - color: | [[[ const uiState = states[`sensor.${variables.robot_id}_ui_state`]?.state || ''; if (uiState === "Starting...") return '#edb926'; if (uiState === "Shutting down...") return 'gray'; const err = states[`sensor.${variables.robot_id}_robot_error`]?.state || ''; if (!err.startsWith('200')) return '#950606'; const alert = states[`sensor.${variables.robot_id}_robot_alert`]?.state || ''; if (!alert.startsWith('200')) return '#edb926'; return '#00cc00'; ]]] card: - border-radius: 20px - display: flex - justify-content: center - align-items: center - margin: 0 auto - position: relative custom_fields: battery: - position: absolute - top: 8px - right: 8px - padding: 4px 8px - border-radius: 8px - font-weight: bold - animation: | [[[ if (states[`binary_sensor.${variables.robot_id}_charging_active`]?.state !== 'on') return ''; const pct = Number(states[`sensor.${variables.robot_id}_fuel_percent`]?.state); return pct > 35 ? 'pulseGreen 3s infinite' : 'pulseOrange 3s infinite'; ]]] - background-color: | [[[ const pct = Number(states[`sensor.${variables.robot_id}_fuel_percent`]?.state); return pct > 35 ? 'rgb(124, 252, 0)' : 'rgb(255, 140, 0)'; ]]] - color: black dock: - position: absolute - top: 8px - left: 8px - padding: 4px - border-radius: 8px - background-color: rgba(255,255,255,0.2) - display: | [[[ return states[`binary_sensor.${variables.robot_id}_ext_power_present`]?.state === 'on' ? 'block' : 'none'; ]]] manual_drive: - position: absolute - bottom: 8px - right: 8px - padding: 4px - border-radius: 8px - height: 15% - width: 10% custom_fields: battery: |- [[[ const v = states[`sensor.${variables.robot_id}_fuel_percent`]?.state; return (v ?? '0') + '%'; ]]] dock: | [[[ if (states[`binary_sensor.${variables.robot_id}_ext_power_present`]?.state === 'on') { return 'DOCKED'; } else { return ''; } ]]] manual_drive: card: type: custom:button-card icon: mdi:steering size: 100% styles: card: - background-color: rgba(255,255,255,0.2) tap_action: action: fire-dom-event browser_mod: service: browser_mod.popup data: title: Manual driving size: normal content: type: vertical-stack cards: - type: horizontal-stack cards: - type: custom:button-card entity: button.neato_vacuum_manual_drive_arc_left_down name: Arc Left press_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_arc_left_down release_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_arc_left_up - type: custom:button-card entity: button.neato_vacuum_manual_drive_forward_down name: Forward press_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_forward_down release_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_forward_up - type: custom:button-card entity: button.neato_vacuum_manual_drive_arc_right_down name: Arc Right press_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_arc_right_down release_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_arc_right_up - type: horizontal-stack cards: - type: custom:button-card entity: button.neato_vacuum_manual_drive_turn_left_down name: Left press_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_turn_left_down release_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_turn_left_up - type: custom:button-card entity: button.neato_vacuum_manual_drive_button_timeout name: Timeout tap_action: action: toggle - type: custom:button-card entity: button.neato_vacuum_manual_drive_turn_right_down name: Right press_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_turn_right_down release_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_turn_right_up - type: horizontal-stack cards: - type: custom:button-card entity: button.neato_vacuum_start_manual_cleaning name: Start manual tap_action: action: toggle - type: custom:button-card entity: button.neato_vacuum_manual_drive_backwards_down name: Backward press_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_backwards_down release_action: action: call-service service: button.press target: entity_id: button.neato_vacuum_manual_drive_backwards_up - type: custom:button-card entity: button.neato_vacuum_stop_cleaning name: Stop tap_action: action: toggle extra_styles: | @keyframes pulseGreen { 0% { box-shadow: 0 0 5px 0 rgba(124, 252, 0, 0.6); } 50% { box-shadow: 0 0 20px 6px rgba(124, 252, 0, 1.0); } 100% { box-shadow: 0 0 5px 0 rgba(124, 252, 0, 0.6); } } @keyframes pulseOrange { 0% { box-shadow: 0 0 5px 0 rgba(255, 140, 0, 0.6); } 50% { box-shadow: 0 0 20px 6px rgba(255, 140, 0, 1.0); } 100% { box-shadow: 0 0 5px 0 rgba(255, 140, 0, 0.6); } } - type: vertical-stack cards: - type: horizontal-stack cards: - type: custom:button-card variables: robot_id: neato_vacuum show_icon: false entity: "[[[ return `sensor.${variables.robot_id}_robot_alert`; ]]]" show_state: false styles: card: - border-color: | [[[ const alert = states[`sensor.${variables.robot_id}_robot_alert`]?.state || ''; return alert.startsWith('200') ? '' : '#edb926'; ]]] name: | [[[ const alert = states[`sensor.${variables.robot_id}_robot_alert`]?.state || ''; if (alert.startsWith('200')) return 'No alerts'; try { return alert.match(/\(UI_(ERROR|ALERT)_(.+)\)/)[2].replaceAll('_',' '); } catch { return alert; } ]]] - type: custom:button-card variables: robot_id: neato_vacuum show_icon: false entity: "[[[ return `sensor.${variables.robot_id}_robot_error`; ]]]" show_state: false styles: card: - border-color: | [[[ const err = states[`sensor.${variables.robot_id}_robot_error`]?.state || ''; return err.startsWith('200') ? '' : '#950606'; ]]] name: | [[[ const err = states[`sensor.${variables.robot_id}_robot_error`]?.state || ''; if (err.startsWith('200')) return 'No errors'; try { return err.match(/\(UI_(ERROR|ALERT)_(.+)\)/)[2].replaceAll('_',' '); } catch { return err; } ]]] - type: horizontal-stack cards: - type: conditional conditions: - condition: and conditions: - condition: state entity: sensor.neato_vacuum_ui_state state_not: UIMGR_STATE_HOUSECLEANINGPAUSED - condition: state entity: sensor.neato_vacuum_ui_state state_not: UIMGR_STATE_HOUSECLEANINGRUNNING - condition: state entity: sensor.neato_vacuum_ui_state state_not: UIMGR_STATE_SPOTCLEANINGPAUSED - condition: state entity: sensor.neato_vacuum_ui_state state_not: UIMGR_STATE_SPOTCLEANINGRUNNING card: type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_house_clean`; ]]]" name: Clean tap_action: action: toggle - type: conditional conditions: - condition: or conditions: - condition: state entity: sensor.neato_vacuum_ui_state state: UIMGR_STATE_HOUSECLEANINGPAUSED - condition: state entity: sensor.neato_vacuum_ui_state state: UIMGR_STATE_SPOTCLEANINGPAUSED card: type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_resume_cleaning`; ]]]" name: Resume tap_action: action: toggle - type: conditional conditions: - condition: or conditions: - condition: state entity: sensor.neato_vacuum_ui_state state: UIMGR_STATE_HOUSECLEANINGRUNNING - condition: state entity: sensor.neato_vacuum_ui_state state: UIMGR_STATE_SPOTCLEANINGRUNNING card: type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_pause_cleaning`; ]]]" name: Pause tap_action: action: toggle - type: conditional conditions: - condition: and conditions: - condition: state entity: sensor.neato_vacuum_ui_state state_not: UIMGR_STATE_HOUSECLEANINGPAUSED - condition: state entity: sensor.neato_vacuum_ui_state state_not: UIMGR_STATE_HOUSECLEANINGRUNNING - condition: state entity: sensor.neato_vacuum_ui_state state_not: UIMGR_STATE_SPOTCLEANINGPAUSED - condition: state entity: sensor.neato_vacuum_ui_state state_not: UIMGR_STATE_SPOTCLEANINGRUNNING card: type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_spot_clean`; ]]]" name: Spot Clean tap_action: action: toggle hold_action: action: fire-dom-event browser_mod: service: browser_mod.popup data: title: Spot Clean - Height and Width size: normal content: type: vertical-stack cards: - type: entities entities: - entity: number.neato_vacuum_spot_clean_height name: Height icon: mdi:arrow-up-down - entity: number.neato_vacuum_spot_clean_width name: Width icon: mdi:arrow-left-right - type: custom:button-card size: 20% name: Start Spot Cleaning entity: button.neato_vacuum_spot_clean_height_width tap_action: action: toggle - type: conditional conditions: - condition: or conditions: - condition: state entity: sensor.neato_vacuum_ui_state state: UIMGR_STATE_HOUSECLEANINGPAUSED - condition: state entity: sensor.neato_vacuum_ui_state state: UIMGR_STATE_SPOTCLEANINGPAUSED - condition: state entity: sensor.neato_vacuum_ui_state state: UIMGR_STATE_HOUSECLEANINGRUNNING - condition: state entity: sensor.neato_vacuum_ui_state state: UIMGR_STATE_SPOTCLEANINGRUNNING card: type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_stop_cleaning`; ]]]" name: STOP tap_action: action: toggle - type: custom:button-card variables: robot_id: neato_vacuum entity: "[[[ return `button.${variables.robot_id}_locate_robot`; ]]]" name: Locate tap_action: action: toggle - type: conditional conditions: - entity: binary_sensor.neato_vacuum_ext_power_present state_not: "on" card: type: custom:button-card entity: button.neato_vacuum_send_to_base name: Send to base tap_action: action: toggle - type: custom:button-card variables: robot_id: neato_vacuum name: Settings icon: mdi:cog tap_action: action: fire-dom-event browser_mod: service: browser_mod.popup data: title: More options size: normal content: type: vertical-stack cards: - type: horizontal-stack cards: - show_name: true show_icon: true entity: >- [[[ return `switch.${variables.robot_id}_eco_mode`; ]]] name: ECO Mode type: button - show_name: true show_icon: true entity: >- [[[ return `switch.${variables.robot_id}_test_mode`; ]]] name: Test Mode type: button - entity: >- [[[ return `button.${variables.robot_id}_update_status`; ]]] name: Fetch status show_icon: true type: button tap_action: action: toggle - entity: >- [[[ return `button.${variables.robot_id}_clear_errors`; ]]] name: Clear Errors show_icon: true type: button tap_action: action: toggle - type: entities entities: - entity: >- [[[ return `select.${variables.robot_id}_navigation_mode`; ]]] name: Navigation Mode - entity: >- [[[ return `switch.${variables.robot_id}_play_extra_sounds`; ]]] name: Play extra sounds - entity: >- [[[ return `switch.${variables.robot_id}_click_sounds`; ]]] name: Click Sounds - entity: >- [[[ return `switch.${variables.robot_id}_melody_sounds`; ]]] name: Melody Sounds - entity: >- [[[ return `switch.${variables.robot_id}_warning_sounds`; ]]] name: Warning Sounds - entity: >- [[[ return `switch.${variables.robot_id}_bin_full_detect`; ]]] name: Bin Full Detect - entity: "[[[ return `switch.${variables.robot_id}_led`; ]]]" name: LED - entity: >- [[[ return `switch.${variables.robot_id}_wall_enable`; ]]] name: Wall Follower - entity: >- [[[ return `switch.${variables.robot_id}_intenseclean`; ]]] name: Intense Clean - entity: "[[[ return `switch.${variables.robot_id}_wifi`; ]]]" name: WiFi state_color: false show_header_toggle: false - type: entities entities: - entity: "[[[ return `sensor.${variables.robot_id}_model`;]]]" name: Model - entity: >- [[[ return `sensor.${variables.robot_id}_serial_number`;]]] name: S/N - entity: "[[[ return `sensor.${variables.robot_id}_software`;]]]" name: Software - entity: "[[[ return `sensor.${variables.robot_id}_nbs_time`;]]]" name: Robot Time - entity: >- [[[ return `sensor.${variables.robot_id}_battery_voltage_v`;]]] name: Battery Voltage - entity: >- [[[ return `sensor.${variables.robot_id}_battery_temp_c_avg`;]]] name: Battery Temp (avg) - entity: >- [[[ return `sensor.${variables.robot_id}_battery_cycles`;]]] name: Battery Cycles - entity: >- [[[ return `sensor.${variables.robot_id}_charger_mah`;]]] name: Charging - entity: >- [[[ return `sensor.${variables.robot_id}_discharge_mah`;]]] name: Discharging - type: horizontal-stack cards: - show_name: true show_icon: true entity: "[[[ return `button.${variables.robot_id}_shutdown`;]]]" type: button name: Power Off tap_action: action: toggle - show_name: true show_icon: true entity: >- [[[ return `button.${variables.robot_id}_powercycle`;]]] name: Reboot Robot type: button tap_action: action: toggle - show_name: true show_icon: true type: button entity: >- [[[ return `button.${variables.robot_id}_reboot_esp`;]]] name: Reboot ESP tap_action: action: toggle ================================================ FILE: config/js/1.2.1.js ================================================ var Bi=Object.defineProperty;var Ri=(t,e,i)=>e in t?Bi(t,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[e]=i;var Ae=(t,e,i)=>(Ri(t,typeof e!="symbol"?e+"":e,i),i);const Ui=function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))r(n);new MutationObserver(n=>{for(const s of n)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function i(n){const s={};return n.integrity&&(s.integrity=n.integrity),n.referrerpolicy&&(s.referrerPolicy=n.referrerpolicy),n.crossorigin==="use-credentials"?s.credentials="include":n.crossorigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(n){if(n.ep)return;n.ep=!0;const s=i(n);fetch(n.href,s)}};Ui();const Me=window,dt=Me.ShadowRoot&&(Me.ShadyCSS===void 0||Me.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,ht=Symbol(),yt=new WeakMap;class ii{constructor(e,i,r){if(this._$cssResult$=!0,r!==ht)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=i}get styleSheet(){let e=this.o;const i=this.t;if(dt&&e===void 0){const r=i!==void 0&&i.length===1;r&&(e=yt.get(i)),e===void 0&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),r&&yt.set(i,e))}return e}toString(){return this.cssText}}const Gi=t=>new ii(typeof t=="string"?t:t+"",void 0,ht),S=(t,...e)=>{const i=t.length===1?t[0]:e.reduce((r,n,s)=>r+(o=>{if(o._$cssResult$===!0)return o.cssText;if(typeof o=="number")return o;throw Error("use css function "+o+". Use unsafeCSS")})(n)+t[s+1],t[0]);return new ii(i,t,ht)},Hi=(t,e)=>{dt?t.adoptedStyleSheets=e.map(i=>i instanceof CSSStyleSheet?i:i.styleSheet):e.forEach(i=>{const r=document.createElement("style"),n=Me.litNonce;n!==void 0&&r.setAttribute("nonce",n),r.textContent=i.cssText,t.appendChild(r)})},vt=dt?t=>t:t=>t instanceof CSSStyleSheet?(e=>{let i="";for(const r of e.cssRules)i+=r.cssText;return Gi(i)})(t):t;var Ue;const xe=window,At=xe.trustedTypes,Vi=At?At.emptyScript:"",Tt=xe.reactiveElementPolyfillSupport,Xe={toAttribute(t,e){switch(e){case Boolean:t=t?Vi:null;break;case Object:case Array:t=t==null?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=t!==null;break;case Number:i=t===null?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch{i=null}}return i}},ni=(t,e)=>e!==t&&(e==e||t==t),Ge={attribute:!0,type:String,converter:Xe,reflect:!1,hasChanged:ni},et="finalized";class F extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this._$Eu()}static addInitializer(e){var i;this.finalize(),((i=this.h)!==null&&i!==void 0?i:this.h=[]).push(e)}static get observedAttributes(){this.finalize();const e=[];return this.elementProperties.forEach((i,r)=>{const n=this._$Ep(r,i);n!==void 0&&(this._$Ev.set(n,r),e.push(n))}),e}static createProperty(e,i=Ge){if(i.state&&(i.attribute=!1),this.finalize(),this.elementProperties.set(e,i),!i.noAccessor&&!this.prototype.hasOwnProperty(e)){const r=typeof e=="symbol"?Symbol():"__"+e,n=this.getPropertyDescriptor(e,r,i);n!==void 0&&Object.defineProperty(this.prototype,e,n)}}static getPropertyDescriptor(e,i,r){return{get(){return this[i]},set(n){const s=this[e];this[i]=n,this.requestUpdate(e,s,r)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)||Ge}static finalize(){if(this.hasOwnProperty(et))return!1;this[et]=!0;const e=Object.getPrototypeOf(this);if(e.finalize(),e.h!==void 0&&(this.h=[...e.h]),this.elementProperties=new Map(e.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const i=this.properties,r=[...Object.getOwnPropertyNames(i),...Object.getOwnPropertySymbols(i)];for(const n of r)this.createProperty(n,i[n])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(e){const i=[];if(Array.isArray(e)){const r=new Set(e.flat(1/0).reverse());for(const n of r)i.unshift(vt(n))}else e!==void 0&&i.push(vt(e));return i}static _$Ep(e,i){const r=i.attribute;return r===!1?void 0:typeof r=="string"?r:typeof e=="string"?e.toLowerCase():void 0}_$Eu(){var e;this._$E_=new Promise(i=>this.enableUpdating=i),this._$AL=new Map,this._$Eg(),this.requestUpdate(),(e=this.constructor.h)===null||e===void 0||e.forEach(i=>i(this))}addController(e){var i,r;((i=this._$ES)!==null&&i!==void 0?i:this._$ES=[]).push(e),this.renderRoot!==void 0&&this.isConnected&&((r=e.hostConnected)===null||r===void 0||r.call(e))}removeController(e){var i;(i=this._$ES)===null||i===void 0||i.splice(this._$ES.indexOf(e)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach((e,i)=>{this.hasOwnProperty(i)&&(this._$Ei.set(i,this[i]),delete this[i])})}createRenderRoot(){var e;const i=(e=this.shadowRoot)!==null&&e!==void 0?e:this.attachShadow(this.constructor.shadowRootOptions);return Hi(i,this.constructor.elementStyles),i}connectedCallback(){var e;this.renderRoot===void 0&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),(e=this._$ES)===null||e===void 0||e.forEach(i=>{var r;return(r=i.hostConnected)===null||r===void 0?void 0:r.call(i)})}enableUpdating(e){}disconnectedCallback(){var e;(e=this._$ES)===null||e===void 0||e.forEach(i=>{var r;return(r=i.hostDisconnected)===null||r===void 0?void 0:r.call(i)})}attributeChangedCallback(e,i,r){this._$AK(e,r)}_$EO(e,i,r=Ge){var n;const s=this.constructor._$Ep(e,r);if(s!==void 0&&r.reflect===!0){const o=(((n=r.converter)===null||n===void 0?void 0:n.toAttribute)!==void 0?r.converter:Xe).toAttribute(i,r.type);this._$El=e,o==null?this.removeAttribute(s):this.setAttribute(s,o),this._$El=null}}_$AK(e,i){var r;const n=this.constructor,s=n._$Ev.get(e);if(s!==void 0&&this._$El!==s){const o=n.getPropertyOptions(s),c=typeof o.converter=="function"?{fromAttribute:o.converter}:((r=o.converter)===null||r===void 0?void 0:r.fromAttribute)!==void 0?o.converter:Xe;this._$El=s,this[s]=c.fromAttribute(i,o.type),this._$El=null}}requestUpdate(e,i,r){let n=!0;e!==void 0&&(((r=r||this.constructor.getPropertyOptions(e)).hasChanged||ni)(this[e],i)?(this._$AL.has(e)||this._$AL.set(e,i),r.reflect===!0&&this._$El!==e&&(this._$EC===void 0&&(this._$EC=new Map),this._$EC.set(e,r))):n=!1),!this.isUpdatePending&&n&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(i){Promise.reject(i)}const e=this.scheduleUpdate();return e!=null&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var e;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach((n,s)=>this[s]=n),this._$Ei=void 0);let i=!1;const r=this._$AL;try{i=this.shouldUpdate(r),i?(this.willUpdate(r),(e=this._$ES)===null||e===void 0||e.forEach(n=>{var s;return(s=n.hostUpdate)===null||s===void 0?void 0:s.call(n)}),this.update(r)):this._$Ek()}catch(n){throw i=!1,this._$Ek(),n}i&&this._$AE(r)}willUpdate(e){}_$AE(e){var i;(i=this._$ES)===null||i===void 0||i.forEach(r=>{var n;return(n=r.hostUpdated)===null||n===void 0?void 0:n.call(r)}),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(e){return!0}update(e){this._$EC!==void 0&&(this._$EC.forEach((i,r)=>this._$EO(r,this[r],i)),this._$EC=void 0),this._$Ek()}updated(e){}firstUpdated(e){}}F[et]=!0,F.elementProperties=new Map,F.elementStyles=[],F.shadowRootOptions={mode:"open"},Tt==null||Tt({ReactiveElement:F}),((Ue=xe.reactiveElementVersions)!==null&&Ue!==void 0?Ue:xe.reactiveElementVersions=[]).push("1.6.3");var He;const Ce=window,K=Ce.trustedTypes,wt=K?K.createPolicy("lit-html",{createHTML:t=>t}):void 0,tt="$lit$",P=`lit$${(Math.random()+"").slice(9)}$`,ri="?"+P,qi=`<${ri}>`,U=document,oe=()=>U.createComment(""),ae=t=>t===null||typeof t!="object"&&typeof t!="function",si=Array.isArray,zi=t=>si(t)||typeof(t==null?void 0:t[Symbol.iterator])=="function",Ve=`[ \f\r]`,te=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Et=/-->/g,Mt=/>/g,j=RegExp(`>|${Ve}(?:([^\\s"'>=/]+)(${Ve}*=${Ve}*(?:[^ \f\r"'\`<>=]|("|')|))|$)`,"g"),$t=/'/g,St=/"/g,oi=/^(?:script|style|textarea|title)$/i,Fi=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),h=Fi(1),W=Symbol.for("lit-noChange"),g=Symbol.for("lit-nothing"),xt=new WeakMap,B=U.createTreeWalker(U,129,null,!1);function ai(t,e){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return wt!==void 0?wt.createHTML(e):e}const Ki=(t,e)=>{const i=t.length-1,r=[];let n,s=e===2?"":"",o=te;for(let c=0;c"?(o=n!=null?n:te,d=-1):u[1]===void 0?d=-2:(d=o.lastIndex-u[2].length,l=u[1],o=u[3]===void 0?j:u[3]==='"'?St:$t):o===St||o===$t?o=j:o===Et||o===Mt?o=te:(o=j,n=void 0);const f=o===j&&t[c+1].startsWith("/>")?" ":"";s+=o===te?a+qi:d>=0?(r.push(l),a.slice(0,d)+tt+a.slice(d)+P+f):a+P+(d===-2?(r.push(void 0),c):f)}return[ai(t,s+(t[i]||"")+(e===2?"":"")),r]};class ce{constructor({strings:e,_$litType$:i},r){let n;this.parts=[];let s=0,o=0;const c=e.length-1,a=this.parts,[l,u]=Ki(e,i);if(this.el=ce.createElement(l,r),B.currentNode=this.el.content,i===2){const d=this.el.content,p=d.firstChild;p.remove(),d.append(...p.childNodes)}for(;(n=B.nextNode())!==null&&a.length0){n.textContent=K?K.emptyScript:"";for(let f=0;f2||r[0]!==""||r[1]!==""?(this._$AH=Array(r.length-1).fill(new String),this.strings=r):this._$AH=g}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(e,i=this,r,n){const s=this.strings;let o=!1;if(s===void 0)e=J(this,e,i,0),o=!ae(e)||e!==this._$AH&&e!==W,o&&(this._$AH=e);else{const c=e;let a,l;for(e=s[0],a=0;a{var r,n;const s=(r=i==null?void 0:i.renderBefore)!==null&&r!==void 0?r:e;let o=s._$litPart$;if(o===void 0){const c=(n=i==null?void 0:i.renderBefore)!==null&&n!==void 0?n:null;s._$litPart$=o=new fe(e.insertBefore(oe(),c),c,void 0,i!=null?i:{})}return o._$AI(t),o};var qe,ze;class E extends F{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){var e,i;const r=super.createRenderRoot();return(e=(i=this.renderOptions).renderBefore)!==null&&e!==void 0||(i.renderBefore=r.firstChild),r}update(e){const i=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=en(i,this.renderRoot,this.renderOptions)}connectedCallback(){var e;super.connectedCallback(),(e=this._$Do)===null||e===void 0||e.setConnected(!0)}disconnectedCallback(){var e;super.disconnectedCallback(),(e=this._$Do)===null||e===void 0||e.setConnected(!1)}render(){return W}}E.finalized=!0,E._$litElement$=!0,(qe=globalThis.litElementHydrateSupport)===null||qe===void 0||qe.call(globalThis,{LitElement:E});const kt=globalThis.litElementPolyfillSupport;kt==null||kt({LitElement:E});((ze=globalThis.litElementVersions)!==null&&ze!==void 0?ze:globalThis.litElementVersions=[]).push("3.3.3");const k=t=>e=>typeof e=="function"?((i,r)=>(customElements.define(i,r),r))(t,e):((i,r)=>{const{kind:n,elements:s}=r;return{kind:n,elements:s,finisher(o){customElements.define(i,o)}}})(t,e),tn=(t,e)=>e.kind==="method"&&e.descriptor&&!("value"in e.descriptor)?{...e,finisher(i){i.createProperty(e.key,t)}}:{kind:"field",key:Symbol(),placement:"own",descriptor:{},originalKey:e.key,initializer(){typeof e.initializer=="function"&&(this[e.key]=e.initializer.call(this))},finisher(i){i.createProperty(e.key,t)}},nn=(t,e,i)=>{e.constructor.createProperty(i,t)};function b(t){return(e,i)=>i!==void 0?nn(t,e,i):tn(t,e)}function x(t){return b({...t,state:!0})}const rn=({finisher:t,descriptor:e})=>(i,r)=>{var n;if(r===void 0){const s=(n=i.originalKey)!==null&&n!==void 0?n:i.key,o=e!=null?{kind:"method",placement:"prototype",key:s,descriptor:e(i.key)}:{...i,key:s};return t!=null&&(o.finisher=function(c){t(c,s)}),o}{const s=i.constructor;e!==void 0&&Object.defineProperty(i,r,e(r)),t==null||t(s,r)}};function ci(t,e){return rn({descriptor:i=>{const r={get(){var n,s;return(s=(n=this.renderRoot)===null||n===void 0?void 0:n.querySelector(t))!==null&&s!==void 0?s:null},enumerable:!0,configurable:!0};if(e){const n=typeof i=="symbol"?Symbol():"__"+i;r.get=function(){var s,o;return this[n]===void 0&&(this[n]=(o=(s=this.renderRoot)===null||s===void 0?void 0:s.querySelector(t))!==null&&o!==void 0?o:null),this[n]}}return r}})}var Fe;((Fe=window.HTMLSlotElement)===null||Fe===void 0?void 0:Fe.prototype.assignedElements)!=null;function pt(){let t=window.location.pathname;return t.endsWith("/")?t.slice(0,-1):t}window.apiBasePath=pt();function li(t,e){fetch(`${window.apiBasePath}/${t.domain}/${t.id}/${e}`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"}})}function Ke(t){li(t,"press")}function ui(t,e){li(t,`set?value=${encodeURIComponent(e)}`)}var di=S`.tab-header{display:inline-flex;max-width:90%;font-weight:400;padding-inline:1.5em;padding-top:.5em;padding-bottom:.5em;align-items:center;border-radius:12px 12px 0 0;background-color:rgba(127,127,127,.3);margin-top:1em;user-select:none}.tab-container{border:2px solid rgba(127,127,127,.3);border-radius:0 12px 12px 12px}`,sn=Object.defineProperty,on=Object.getOwnPropertyDescriptor,Oe=(t,e,i,r)=>{for(var n=r>1?void 0:r?on(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&sn(e,i,n),n};let le=class extends E{constructor(){super(),this.rows=10,this.scheme="",this.logs=[],this.handleLog=t=>{const i=t.data,n={"\x1B[1;31m":"e","\x1B[0;33m":"w","\x1B[0;32m":"i","\x1B[0;35m":"c","\x1B[0;36m":"d","\x1B[0;37m":"v"}[i.slice(0,7)];if(!n)return;const o=i.slice(7,i.length-4).split(` `),c=o[0],l=c.slice(3).split(":").slice(0,2).join(":"),u=c.slice(5+l.length),d=c.slice(0,3),p=new Date().toTimeString().split(" ")[0];o.forEach((f,y)=>{console.log("ESP_LOG",f);const _={type:n,level:d,tag:l,detail:y===0?u:f,when:p};this.logs.push(_)}),this.logs=this.logs.slice(-this.rows)}}connectedCallback(){var t;super.connectedCallback(),(t=window.source)==null||t.addEventListener("log",this.handleLog)}disconnectedCallback(){var t;(t=window.source)==null||t.removeEventListener("log",this.handleLog),super.disconnectedCallback()}render(){return h`
Debug Log
Time
Level
Tag
Message
${this.logs.map(t=>h`
${t.when}
${t.level}
${t.tag}
${t.detail}
`)}
`}_handleTabHeaderDblClick(t){var i;const e=new CustomEvent("log-tab-header-double-clicked",{bubbles:!0,composed:!0});(i=t.target)==null||i.dispatchEvent(e)}static get styles(){return[di,S`.tbody .trow:nth-child(2n),.thead{background-color:rgba(127,127,127,.05)}.trow div{font-family:monospace;width:100%;line-height:1.2rem}.trow{display:flex}.thead{line-height:1rem}.thead .trow{text-align:left;padding:.25rem .5rem}.trow{display:flex}.trow>div{align-self:flex-start;padding-right:.25em;flex:2 0;min-width:70px}.trow>div:nth-child(2){flex:1 0;overflow:hidden;text-overflow:ellipsis;max-width:40px}.trow>div:nth-child(3){flex:3 0;overflow:hidden;text-overflow:ellipsis}.trow>div:last-child{flex:15 0;padding-right:0;overflow:hidden;text-overflow:ellipsis}pre{margin:0}.v{color:#888}.d{color:#0dd}.c{color:#ff00ff}.i{color:#32cd32}.w{color:#ff0}.e{color:red;font-weight:700}.logs[color-scheme=light]{font-weight:700}.logs[color-scheme=light] .w{color:#cc0}.logs[color-scheme=dark] .d{color:#0aa}.logs{overflow-x:auto;border-radius:12px;border-width:1px;border-style:solid;border-color:rgba(127,127,127,.12);transition:all .3s ease-out 0s;font-size:14px;padding:16px}@media (max-width:1024px){.trow>div:nth-child(2){display:none!important}}`]}};Oe([b({type:Number})],le.prototype,"rows",2);Oe([b({type:String})],le.prototype,"scheme",2);Oe([x()],le.prototype,"logs",2);le=Oe([k("esp-log")],le);var me=S`:host,button,input,select{font-family:ui-monospace,system-ui,Helvetica,Roboto,Oxygen,Ubuntu,sans-serif;--primary-color:#03a9f4;transition:all 350ms!important}`;class an{constructor(){this.entities=new Map,this.listeners=new Set}set(e){this.entities.set(e.unique_id,e),this.notify(e)}get(e){return this.entities.get(e)}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}notify(e){for(const i of this.listeners)i(e)}}const Q=new an;var cn=Object.defineProperty,ln=Object.getOwnPropertyDescriptor,q=(t,e,i,r)=>{for(var n=r>1?void 0:r?ln(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&cn(e,i,n),n};let I=class extends E{constructor(){super(...arguments),this.click="",this.press="",this.release="",this.icon="",this.name="",this.entities={}}connectedCallback(){super.connectedCallback(),this.unsubscribe=Q.subscribe(t=>{t.unique_id===this.click&&(this.entities.click=t),t.unique_id===this.press&&(this.entities.press=t),t.unique_id===this.release&&(this.entities.release=t),this.requestUpdate()})}disconnectedCallback(){var t;(t=this.unsubscribe)==null||t.call(this),super.disconnectedCallback()}get entityIds(){let t=[];return this.click&&t.push(this.click),this.press&&t.push(this.press),this.release&&t.push(this.release),t}onClick(){this.entities.click&&Ke(this.entities.click)}onMouseDown(){this.entities.press&&Ke(this.entities.press)}onMouseUp(){this.entities.release&&Ke(this.entities.release)}updated(t){!this.customButtom||(this.press&&(this.customButtom.removeEventListener("touchstart",this.onMouseDown.bind(this)),this.customButtom.addEventListener("touchstart",this.onMouseDown.bind(this))),this.release&&(this.customButtom.removeEventListener("touchend",this.onMouseUp.bind(this)),this.customButtom.addEventListener("touchend",this.onMouseUp.bind(this))))}render(){var t,e,i,r,n,s;return Object.keys(this.entities).length!==this.entityIds.length?h`
loading…
`:h`
${this.name||((r=this.entities.click)==null?void 0:r.name)||((n=this.entities.press)==null?void 0:n.name)||((s=this.entities.release)==null?void 0:s.name)}
`}static get styles(){return[me,S`.cb{border:.1rem solid gray;border-radius:.5rem;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;padding:1rem;width:6rem}.cb span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.cb:hover{background-color:rgba(255,255,255,.1);cursor:pointer}@media (max-width:1024px){.cb{font-size:.8rem;width:5.3rem}}`]}};q([b({type:String})],I.prototype,"click",2);q([b({type:String})],I.prototype,"press",2);q([b({type:String})],I.prototype,"release",2);q([b({type:String})],I.prototype,"icon",2);q([b({type:String})],I.prototype,"name",2);q([ci("#custom-button")],I.prototype,"customButtom",2);I=q([k("custom-button")],I);var un=S`:host{position:relative}select{background-color:inherit;color:inherit;width:100%;border-radius:4px}option{color:currentColor;background-color:var(--primary-color,currentColor)}input[type=range],input[type=text]{width:calc(100% - 3rem);height:.75rem}.range{text-align:center}.entity-row{display:flex;align-items:center;flex-direction:row;transition:all .3s ease-out 0s;min-height:40px;position:relative}.entity-row.expanded{min-height:240px}.entity-row:nth-child(2n){background-color:rgba(90,90,90,.1)}.entity-row iconify-icon{vertical-align:middle}.entity-row>:nth-child(1){flex:0 0 40px;color:#44739e;line-height:40px;text-align:center}.entity-row>:nth-child(2){flex:1 1 40%;margin-left:16px;margin-right:8px;text-wrap:nowrap;overflow:hidden;text-overflow:ellipsis}.entity-row>:nth-child(3){flex:1 1 50%;margin-right:8px;margin-left:20px;text-align:right;display:flex;justify-content:space-between;white-space:normal;overflow-wrap:anywhere;word-break:break-word}.entity-row>:nth-child(3)>:only-child{margin-left:auto}.binary_sensor_off{color:rgba(127,127,127,.7)}.singlebutton-row button{margin:auto;display:flex}.climate-wrap{width:100%;margin:10px 0 10px 0}.climate-row{width:100%;display:inline-flex;flex-wrap:wrap;text-align:left}.climate-row>select{width:50%}.climate-row>label{align-content:center;width:150px}input[type=color]::-webkit-color-swatch-wrapper{padding:0!important}`,dn=Object.defineProperty,hn=Object.getOwnPropertyDescriptor,Le=(t,e,i,r)=>{for(var n=r>1?void 0:r?hn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&dn(e,i,n),n};const hi="ON",Pt="OFF";let ue=class extends E{constructor(){super(...arguments),this.entityIds=[],this.customNames={},this.customValues={},this.entities=[],this._actionRenderer=new pi}connectedCallback(){super.connectedCallback(),this.unsubscribe=Q.subscribe(t=>{this.entityIds.some(e=>e===t.unique_id)&&(this.entities.push(t),this.entities.sort((e,i)=>this.entityIds.indexOf(e.unique_id)-this.entityIds.indexOf(i.unique_id)),this.requestUpdate())})}disconnectedCallback(){var t;(t=this.unsubscribe)==null||t.call(this),super.disconnectedCallback()}render(){return this.entities.length?h`
${this.entities.map(t=>{var e,i,r;return h`
${t.icon?h``:g}
${((e=this.customNames)==null?void 0:e[t.unique_id])||t.name}
${t.has_action?this.control(t):h`
${(i=this.customValues)!=null&&i[t.unique_id]?(r=this.customValues)==null?void 0:r[t.unique_id](t.state):t.state}
`}
`})}
`:h`loading…`}hasAction(t){return`render_${t.domain}`in this._actionRenderer}control(t){return this._actionRenderer.entity=t,this._actionRenderer.actioner=this,this._actionRenderer.exec(`render_${t.domain}`)}restAction(t,e){fetch(`${window.apiBasePath}/${t.domain}/${t.id}/${e}`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"}}).then(i=>{console.log(i)})}static get styles(){return[me,un]}};Le([b({type:String})],ue.prototype,"entityIds",2);Le([b()],ue.prototype,"customNames",2);Le([b()],ue.prototype,"customValues",2);ue=Le([k("custom-table")],ue);class pi{exec(e){if(!this[e]||typeof this[e]!="function"){console.log(`ActionRenderer.${e} is not callable`);return}return this[e]()}_actionButton(e,i,r,n=!1){if(!e)return;let s=r||i.toLowerCase();return h``}_datetime(e,i,r,n,s){return h``}_switch(e){return h``}_select(e,i,r,n,s){return h``}_range(e,i,r,n,s,o,c=1){return e.mode==1?h`
`:h``}_textinput(e,i,r,n,s,o,c){return h``}_colorpicker(e,i,r){function n(o){return Number(o).toString(16).padStart(2,"0")}function s(o){var a;const c=((a=o.match(/[0-9a-f]{2}/gi))==null?void 0:a.map(l=>parseInt(l,16)))||[0,0,0];return`r=${c[0]}&g=${c[1]}&b=${c[2]}`}return h`
`}render_binary_sensor(){var i;if(!this.entity)return;const e=this.entity.state==hi;return h``}render_date(){if(!!this.entity)return h`${this._datetime(this.entity,"date","set","value",this.entity.value)}`}render_time(){if(!!this.entity)return h`${this._datetime(this.entity,"time","set","value",this.entity.value)}`}render_datetime(){if(!!this.entity)return h`${this._datetime(this.entity,"datetime-local","set","value",this.entity.value)}`}render_switch(){if(!!this.entity)return this.entity.assumed_state?h`${this._actionButton(this.entity,"\u274C","turn_off")} ${this._actionButton(this.entity,"\u2714\uFE0F","turn_on")}`:this._switch(this.entity)}render_fan(){if(!!this.entity)return[this.entity.speed," ",this.entity.speed_level,this._switch(this.entity),this.entity.speed_count?this._range(this.entity,`turn_${this.entity.state.toLowerCase()}`,"speed_level",this.entity.speed_level?this.entity.speed_level:0,0,this.entity.speed_count,1):""]}render_light(){var e,i;if(!!this.entity)return[h`
${this._switch(this.entity)} ${this.entity.brightness?this._range(this.entity,"turn_on","brightness",this.entity.brightness,0,255,1):""} ${this.entity.color_mode==="rgb"||this.entity.color_mode==="rgbw"?this._colorpicker(this.entity,"turn_on",(e=this.entity)==null?void 0:e.color):""} ${(i=this.entity.effects)!=null&&i.filter(r=>r!="None").length?this._select(this.entity,"turn_on","effect",this.entity.effects||[],this.entity.effect):""}
`]}render_lock(){if(!!this.entity)return h`${this._actionButton(this.entity,"\u{1F510}","lock",this.entity.state==="LOCKED")} ${this._actionButton(this.entity,"\u{1F513}","unlock",this.entity.state==="UNLOCKED")} ${this._actionButton(this.entity,"\u2191","open")}`}render_cover(){if(!!this.entity)return h`${this._actionButton(this.entity,"\u2191","open",this.entity.state==="OPEN")} ${this._actionButton(this.entity,"\u2610","stop")} ${this._actionButton(this.entity,"\u2193","close",this.entity.state==="CLOSED")}`}render_button(){if(!!this.entity)return h`${this._actionButton(this.entity,"PRESS","press")}`}render_select(){if(!!this.entity)return this._select(this.entity,"set","option",this.entity.option||[],this.entity.value)}render_number(){if(!!this.entity)return h`${this._range(this.entity,"set","value",this.entity.value,this.entity.min_value,this.entity.max_value,this.entity.step)} ${this.entity.uom}`}render_text(){if(!!this.entity)return this._textinput(this.entity,"set","value",this.entity.value,this.entity.min_length,this.entity.max_length,this.entity.pattern)}render_climate(){if(!this.entity)return;let e,i=h`
`;this.entity.target_temperature_low!==void 0&&this.entity.target_temperature_high!==void 0?e=h`
${this._range(this.entity,"set","target_temperature_low",this.entity.target_temperature_low,this.entity.min_temp,this.entity.max_temp,this.entity.step)}
${this._range(this.entity,"set","target_temperature_high",this.entity.target_temperature_high,this.entity.min_temp,this.entity.max_temp,this.entity.step)}
`:e=h`
${this._range(this.entity,"set","target_temperature",this.entity.target_temperature,this.entity.min_temp,this.entity.max_temp,this.entity.step)}
`;let r=h``;return(this.entity.modes?this.entity.modes.length:0)>0&&(r=h`
${this._select(this.entity,"set","mode",this.entity.modes||[],this.entity.mode||"")}
`),h`
${i} ${e} ${r}
`}render_valve(){if(!!this.entity)return h`${this._actionButton(this.entity,"OPEN","open",this.entity.state==="OPEN")} ${this._actionButton(this.entity,"\u2610","stop")} ${this._actionButton(this.entity,"CLOSE","close",this.entity.state==="CLOSED")}`}}var pn=Object.defineProperty,fn=Object.getOwnPropertyDescriptor,Z=(t,e,i,r)=>{for(var n=r>1?void 0:r?fn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&pn(e,i,n),n};const It="checkbox-lever";let G=class extends E{constructor(){super(...arguments),this.checkbox=null,this.stateOn=hi,this.stateOff=Pt,this.state=Pt,this.color="currentColor",this.disabled=!1}firstUpdated(t){var e;this.checkbox=(e=this.shadowRoot)==null?void 0:e.getElementById(It)}isOn(){return this.state===this.stateOn}toggle(t){const e=this.isOn()?this.stateOff:this.stateOn;let i=new CustomEvent("state",{detail:{state:e,id:this.id}});this.dispatchEvent(i)}render(){return h`
`}static get styles(){return[me,S`.sw,.sw *{-webkit-tap-highlight-color:transparent;user-select:none;cursor:pointer}input[type=checkbox]{opacity:0;width:0;height:0}input[type=checkbox]:checked+.lever{background-color:currentColor;background-image:linear-gradient(0deg,rgba(255,255,255,.5) 0,rgba(255,255,255,.5) 100%)}input[type=checkbox]:checked+.lever:after,input[type=checkbox]:checked+.lever:before{left:18px}input[type=checkbox]:checked+.lever:after{background-color:currentColor}input[type=checkbox]:not(:checked)+.lever:after{background-color:rgba(127,127,127,.5)}.lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-image:linear-gradient(0deg,rgba(127,127,127,.5) 0,rgba(127,127,127,.5) 100%);background-color:inherit;border-radius:15px;transition:background .3s ease;vertical-align:middle}.lever:after,.lever:before{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;transition:left .3s ease,background .3s ease,box-shadow .1s ease,transform .1s ease}.lever:before{background-color:currentColor;background-image:linear-gradient(0deg,rgba(255,255,255,.9) 0,rgba(255,255,255,.9) 100%)}.lever:after{background-color:#f1f1f1;box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12)}input[type=checkbox]:checked:not(:disabled).tabbed:focus~.lever::before,input[type=checkbox]:checked:not(:disabled)~.lever:active::before{transform:scale(2.4);background-color:rgba(255,255,255,.9) 0;background-image:linear-gradient(0deg,rgba(255,255,255,.9) 0,rgba(255,255,255,.9) 100%)}input[type=checkbox]:not(:disabled).tabbed:focus~.lever::before,input[type=checkbox]:not(:disabled)~.lever:active:before{transform:scale(2.4);background-color:rgba(0,0,0,.08)}input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,.12)}input[type=checkbox][disabled]+.lever:after,input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}`]}};Z([b({type:String})],G.prototype,"stateOn",2);Z([b({type:String})],G.prototype,"stateOff",2);Z([b({type:String})],G.prototype,"state",2);Z([b({type:String})],G.prototype,"color",2);Z([b({type:Boolean})],G.prototype,"disabled",2);G=Z([k("esp-switch")],G);var mn=Object.defineProperty,gn=Object.getOwnPropertyDescriptor,Y=(t,e,i,r)=>{for(var n=r>1?void 0:r?gn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&mn(e,i,n),n};const Dt="range",bn="rangeValue",Ot=500;let H=class extends E{constructor(){super(...arguments),this.inputRange=null,this.currentValue=null,this.longPressTimer=null,this.isPopupInputVisible=!1,this.value=0,this.min=0,this.max=0,this.step=0,this.name=""}firstUpdated(t){var e,i;this.inputRange=(e=this.shadowRoot)==null?void 0:e.getElementById(Dt),this.currentValue=(i=this.shadowRoot)==null?void 0:i.getElementById(bn),document.addEventListener("mousedown",r=>{var s;if(!document.querySelector(".popup-number-input"))return;!((s=document.querySelector(".popup-number-input"))!=null&&s.contains(r.target))&&this.isPopupInputVisible&&this.deletePopupInput()})}updated(){this.updateCurrentValueOverlay()}onMouseDownCurrentValue(t){this.longPressTimer=setTimeout(()=>{this.showPopupInput(t.pageX,t.pageY)},Ot)}onMouseUpCurrentValue(t){this.longPressTimer&&!this.isPopupInputVisible&&(clearTimeout(this.longPressTimer),this.longPressTimer=null)}onTouchStartCurrentValue(t){this.longPressTimer=setTimeout(()=>{this.showPopupInput(t.touches[0].pageX,t.touches[0].pageY)},Ot)}onTouchEndCurrentValue(t){this.longPressTimer&&!this.isPopupInputVisible&&(clearTimeout(this.longPressTimer),this.longPressTimer=null)}deletePopupInput(){const t=document.querySelector(".popup-number-input");t&&t.remove(),this.isPopupInputVisible=!1}showPopupInput(t,e){const i=document.createElement("input");i.type="number",i.value=this.inputRange.value,i.min=this.inputRange.min,i.max=this.inputRange.max,i.step=this.inputRange.step,i.classList.add("popup-number-input");const r=` position: absolute; left: ${t}px; top: ${e}px; width: 50px; -webkit-appearance: none; margin: 0; `;i.setAttribute("style",r),document.body.appendChild(i),i.addEventListener("contextmenu",n=>{n.preventDefault()}),i.addEventListener("change",n=>{var c,a;let s=n.target;this.inputRange.value=s==null?void 0:s.value;var o=new Event("input");(c=this.inputRange)==null||c.dispatchEvent(o);var o=new Event("change");(a=this.inputRange)==null||a.dispatchEvent(o)}),i.addEventListener("keydown",n=>{n.key==="Enter"&&this.deletePopupInput()}),i.focus(),this.isPopupInputVisible=!0}updateCurrentValueOverlay(){var r,n;const t=Number((this.inputRange.value-this.inputRange.min)*100/(this.inputRange.max-this.inputRange.min)),e=10-t*.2;this.currentValue.innerHTML=`${(r=this.inputRange)==null?void 0:r.value}`,this.currentValue.style.left=`calc(${t}% + (${e}px))`;const i=(n=this.currentValue)==null?void 0:n.querySelector("span");i==null||i.addEventListener("mousedown",this.onMouseDownCurrentValue.bind(this)),i==null||i.addEventListener("mouseup",this.onMouseUpCurrentValue.bind(this)),i==null||i.addEventListener("touchstart",this.onTouchStartCurrentValue.bind(this)),i==null||i.addEventListener("touchend",this.onTouchEndCurrentValue.bind(this)),i==null||i.addEventListener("contextmenu",s=>{s.preventDefault()})}onInputEvent(t){this.updateCurrentValueOverlay()}onInputChangeEvent(t){var e;this.sendState((e=this.inputRange)==null?void 0:e.value)}sendState(t){let e=new CustomEvent("state",{detail:{state:t,id:this.id}});this.dispatchEvent(e)}render(){return h`
`}static get styles(){return[me,S`:host{min-width:150px;flex:1}input[type=range]{background:0 0;-webkit-appearance:none;appearance:none;margin:20px 0;width:100%;touch-action:none}input[type=range]:focus{outline:0}input[type=range]::-webkit-slider-runnable-track{width:100%;height:4px;cursor:pointer;animate:.2s;background:#03a9f4;border-radius:25px}input[type=range]::-moz-range-track{width:100%;height:4px;cursor:pointer;animate:.2s;background:#03a9f4;border-radius:25px}input[type=range]::-ms-track{background:0 0;width:100%;height:4px;cursor:pointer;animate:.2s;background:0 0;border-color:transparent;color:transparent}input[type=range]::-ms-fill-lower{background:#03a9f4;border-radius:25px}input[type=range]::-ms-fill-upper{background:#03a9f4;border-radius:25px}input[type=range]::-webkit-slider-thumb{height:20px;width:20px;border-radius:50%;background:#fff;box-shadow:0 0 4px 0 #000;cursor:pointer;-webkit-appearance:none;margin-top:-8px}input[type=range]::-moz-range-thumb{height:20px;width:20px;border-radius:50%;background:#fff;box-shadow:0 0 4px 0 #000;cursor:pointer;border:none}input[type=range]::-ms-thumb{height:20px;width:20px;border-radius:50%;background:#fff;box-shadow:0 0 4px 0 #000;cursor:pointer;border:none}input[type=range]:focus::-webkit-slider-runnable-track{background:#03a9f4}input[type=range]:focus::-moz-range-track{background:#03a9f4}input[type=range]:focus::-ms-fill-lower{background:#03a9f4}input[type=range]:focus::-ms-fill-upper{background:#03a9f4}.range-wrap{display:flex;align-items:center}.slider-wrap{flex-grow:1;margin:0 15px;position:relative}.range-value{position:absolute;top:-50%}.range-value span{padding:0 3px 0 3px;height:19px;line-height:18px;text-align:center;background:#03a9f4;color:#fff;font-size:11px;display:block;position:absolute;left:50%;transform:translate(-50%,+80%);border-radius:6px}@-moz-document url-prefix(){.range-value span{transform:translate(-50%,+150%)}}.range-value span:before{content:"";position:absolute;width:0;height:0;border-top:10px solid #03a9f4;border-left:5px solid transparent;border-right:5px solid transparent;top:100%;left:50%;margin-left:-5px;margin-top:-1px;pointer-events:none}`]}};Y([b({type:String})],H.prototype,"value",2);Y([b({type:String})],H.prototype,"min",2);Y([b({type:String})],H.prototype,"max",2);Y([b({type:String})],H.prototype,"step",2);Y([b({type:String})],H.prototype,"name",2);H=Y([k("esp-range-slider")],H);var _n=Object.defineProperty,yn=Object.getOwnPropertyDescriptor,ge=(t,e,i,r)=>{for(var n=r>1?void 0:r?yn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&_n(e,i,n),n};let V=class extends E{constructor(){super(...arguments),this.entityId="text-scheduleset",this.days=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],this.globalEnabled=!0,this.dayEnabled=Array(7).fill(!0),this.values=Array(7).fill(0)}connectedCallback(){super.connectedCallback(),this.unsubscribe=Q.subscribe(()=>{const t=Q.get(this.entityId);t&&(this.entity=t,this.setFromString(this.entity.value))})}disconnectedCallback(){var t;(t=this.unsubscribe)==null||t.call(this),super.disconnectedCallback()}buildFlags(){let t=this.globalEnabled?1:0;return this.dayEnabled.forEach((e,i)=>{e&&(t|=1<Number(r.trim()));if(e.length!==8||e.some(isNaN))return;const i=e[0];this.globalEnabled=(i&1)===1;for(let r=0;r<7;r++)this.dayEnabled[r]=(i>>r+1&1)===1,this.values[r]=this.clampValue(e[r+1]);this.requestUpdate()}clampValue(t){return isNaN(t)||t<0?0:t>143?143:t}valueToHM(t){return{h:Math.floor(t/6),m:t%6*10}}pad2(t){return String(t).padStart(2,"0")}roundMinute(t){return t=Math.round(t/10)*10,t<0?0:t>50?50:t}hmToValue(t,e){return t<0||t>23?null:(e=this.roundMinute(e),t*6+e/10)}adjustMinute(t,e){const{h:i,m:r}=this.valueToHM(this.values[t]);let n=r+e;n>50&&(n=50),n<0&&(n=0);const s=this.hmToValue(i,n);s!==null&&(this.values[t]=s,this.emit())}adjustHour(t,e){const{h:i,m:r}=this.valueToHM(this.values[t]);let n=i+e;n<0&&(n=23),n>23&&(n=0);const s=this.hmToValue(n,r);s!==null&&(this.values[t]=s,this.emit())}render(){return this.entity?h`
${this.days.map((t,e)=>h`
:
`)}`:h`loading...`}};V.styles=S`:host{display:block}.row{display:grid;grid-template-columns:44px 1fr auto;align-items:center;gap:10px;padding:6px 0}.row.global{grid-template-columns:1fr auto;padding-bottom:10px;border-bottom:1px solid rgba(255,255,255,.1);margin-bottom:8px}label{font-size:.95rem;font-weight:700}.day-disabled label{opacity:.5}input[type=text]{background:#2b2b2b;color:#fff;border:1px solid #444;border-radius:8px;padding:6px 8px;font-size:.9rem;width:70px;text-align:center}input[type=text]:disabled{opacity:.4}.time{display:flex;align-items:center;gap:6px}.time input{width:36px;padding:6px 4px;text-align:center;font-size:.9rem}.hour,.minute{display:flex;flex-direction:column;align-items:center}.sep{opacity:.6}.arrow{border:none;background:0 0;color:#aaa;font-size:10px;line-height:10px;cursor:pointer}.arrow:hover{color:#6aa9ff}.arrow:disabled{opacity:.3;cursor:default}.switch{position:relative;width:44px;height:24px;border-radius:999px;background:#555;cursor:pointer;transition:background .2s ease}.switch::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff;transition:transform .2s ease}.switch.on{background:#3b82f6}.switch.on::after{transform:translateX(20px)}`;ge([b({type:String})],V.prototype,"entityId",2);ge([x()],V.prototype,"globalEnabled",2);ge([x()],V.prototype,"dayEnabled",2);ge([x()],V.prototype,"values",2);V=ge([k("esp-schedule")],V);var vn=Object.defineProperty,An=Object.getOwnPropertyDescriptor,X=(t,e,i,r)=>{for(var n=r>1?void 0:r?An(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&vn(e,i,n),n};let D=class extends E{constructor(){super(...arguments),this.value="",this.query="",this.filtered=[],this.selected="",this.entityId="text-timezone",this.zones={"Africa/Abidjan":"GMT0","Africa/Accra":"GMT0","Africa/Addis_Ababa":"EAT-3","Africa/Algiers":"CET-1","Africa/Asmara":"EAT-3","Africa/Bamako":"GMT0","Africa/Bangui":"WAT-1","Africa/Banjul":"GMT0","Africa/Bissau":"GMT0","Africa/Blantyre":"CAT-2","Africa/Brazzaville":"WAT-1","Africa/Bujumbura":"CAT-2","Africa/Cairo":"EET-2EEST,M4.5.5/0,M10.5.4/24","Africa/Casablanca":"<+01>-1","Africa/Ceuta":"CET-1CEST,M3.5.0,M10.5.0/3","Africa/Conakry":"GMT0","Africa/Dakar":"GMT0","Africa/Dar_es_Salaam":"EAT-3","Africa/Djibouti":"EAT-3","Africa/Douala":"WAT-1","Africa/El_Aaiun":"<+01>-1","Africa/Freetown":"GMT0","Africa/Gaborone":"CAT-2","Africa/Harare":"CAT-2","Africa/Johannesburg":"SAST-2","Africa/Juba":"CAT-2","Africa/Kampala":"EAT-3","Africa/Khartoum":"CAT-2","Africa/Kigali":"CAT-2","Africa/Kinshasa":"WAT-1","Africa/Lagos":"WAT-1","Africa/Libreville":"WAT-1","Africa/Lome":"GMT0","Africa/Luanda":"WAT-1","Africa/Lubumbashi":"CAT-2","Africa/Lusaka":"CAT-2","Africa/Malabo":"WAT-1","Africa/Maputo":"CAT-2","Africa/Maseru":"SAST-2","Africa/Mbabane":"SAST-2","Africa/Mogadishu":"EAT-3","Africa/Monrovia":"GMT0","Africa/Nairobi":"EAT-3","Africa/Ndjamena":"WAT-1","Africa/Niamey":"WAT-1","Africa/Nouakchott":"GMT0","Africa/Ouagadougou":"GMT0","Africa/Porto-Novo":"WAT-1","Africa/Sao_Tome":"GMT0","Africa/Tripoli":"EET-2","Africa/Tunis":"CET-1","Africa/Windhoek":"CAT-2","America/Adak":"HST10HDT,M3.2.0,M11.1.0","America/Anchorage":"AKST9AKDT,M3.2.0,M11.1.0","America/Anguilla":"AST4","America/Antigua":"AST4","America/Araguaina":"<-03>3","America/Argentina/Buenos_Aires":"<-03>3","America/Argentina/Catamarca":"<-03>3","America/Argentina/Cordoba":"<-03>3","America/Argentina/Jujuy":"<-03>3","America/Argentina/La_Rioja":"<-03>3","America/Argentina/Mendoza":"<-03>3","America/Argentina/Rio_Gallegos":"<-03>3","America/Argentina/Salta":"<-03>3","America/Argentina/San_Juan":"<-03>3","America/Argentina/San_Luis":"<-03>3","America/Argentina/Tucuman":"<-03>3","America/Argentina/Ushuaia":"<-03>3","America/Aruba":"AST4","America/Asuncion":"<-03>3","America/Atikokan":"EST5","America/Bahia":"<-03>3","America/Bahia_Banderas":"CST6","America/Barbados":"AST4","America/Belem":"<-03>3","America/Belize":"CST6","America/Blanc-Sablon":"AST4","America/Boa_Vista":"<-04>4","America/Bogota":"<-05>5","America/Boise":"MST7MDT,M3.2.0,M11.1.0","America/Cambridge_Bay":"MST7MDT,M3.2.0,M11.1.0","America/Campo_Grande":"<-04>4","America/Cancun":"EST5","America/Caracas":"<-04>4","America/Cayenne":"<-03>3","America/Cayman":"EST5","America/Chicago":"CST6CDT,M3.2.0,M11.1.0","America/Chihuahua":"CST6","America/Costa_Rica":"CST6","America/Creston":"MST7","America/Cuiaba":"<-04>4","America/Curacao":"AST4","America/Danmarkshavn":"GMT0","America/Dawson":"MST7","America/Dawson_Creek":"MST7","America/Denver":"MST7MDT,M3.2.0,M11.1.0","America/Detroit":"EST5EDT,M3.2.0,M11.1.0","America/Dominica":"AST4","America/Edmonton":"MST7MDT,M3.2.0,M11.1.0","America/Eirunepe":"<-05>5","America/El_Salvador":"CST6","America/Fort_Nelson":"MST7","America/Fortaleza":"<-03>3","America/Glace_Bay":"AST4ADT,M3.2.0,M11.1.0","America/Godthab":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0","America/Goose_Bay":"AST4ADT,M3.2.0,M11.1.0","America/Grand_Turk":"EST5EDT,M3.2.0,M11.1.0","America/Grenada":"AST4","America/Guadeloupe":"AST4","America/Guatemala":"CST6","America/Guayaquil":"<-05>5","America/Guyana":"<-04>4","America/Halifax":"AST4ADT,M3.2.0,M11.1.0","America/Havana":"CST5CDT,M3.2.0/0,M11.1.0/1","America/Hermosillo":"MST7","America/Indiana/Indianapolis":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Knox":"CST6CDT,M3.2.0,M11.1.0","America/Indiana/Marengo":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Petersburg":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Tell_City":"CST6CDT,M3.2.0,M11.1.0","America/Indiana/Vevay":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Vincennes":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Winamac":"EST5EDT,M3.2.0,M11.1.0","America/Inuvik":"MST7MDT,M3.2.0,M11.1.0","America/Iqaluit":"EST5EDT,M3.2.0,M11.1.0","America/Jamaica":"EST5","America/Juneau":"AKST9AKDT,M3.2.0,M11.1.0","America/Kentucky/Louisville":"EST5EDT,M3.2.0,M11.1.0","America/Kentucky/Monticello":"EST5EDT,M3.2.0,M11.1.0","America/Kralendijk":"AST4","America/La_Paz":"<-04>4","America/Lima":"<-05>5","America/Los_Angeles":"PST8PDT,M3.2.0,M11.1.0","America/Lower_Princes":"AST4","America/Maceio":"<-03>3","America/Managua":"CST6","America/Manaus":"<-04>4","America/Marigot":"AST4","America/Martinique":"AST4","America/Matamoros":"CST6CDT,M3.2.0,M11.1.0","America/Mazatlan":"MST7","America/Menominee":"CST6CDT,M3.2.0,M11.1.0","America/Merida":"CST6","America/Metlakatla":"AKST9AKDT,M3.2.0,M11.1.0","America/Mexico_City":"CST6","America/Miquelon":"<-03>3<-02>,M3.2.0,M11.1.0","America/Moncton":"AST4ADT,M3.2.0,M11.1.0","America/Monterrey":"CST6","America/Montevideo":"<-03>3","America/Montreal":"EST5EDT,M3.2.0,M11.1.0","America/Montserrat":"AST4","America/Nassau":"EST5EDT,M3.2.0,M11.1.0","America/New_York":"EST5EDT,M3.2.0,M11.1.0","America/Nipigon":"EST5EDT,M3.2.0,M11.1.0","America/Nome":"AKST9AKDT,M3.2.0,M11.1.0","America/Noronha":"<-02>2","America/North_Dakota/Beulah":"CST6CDT,M3.2.0,M11.1.0","America/North_Dakota/Center":"CST6CDT,M3.2.0,M11.1.0","America/North_Dakota/New_Salem":"CST6CDT,M3.2.0,M11.1.0","America/Nuuk":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0","America/Ojinaga":"CST6CDT,M3.2.0,M11.1.0","America/Panama":"EST5","America/Pangnirtung":"EST5EDT,M3.2.0,M11.1.0","America/Paramaribo":"<-03>3","America/Phoenix":"MST7","America/Port-au-Prince":"EST5EDT,M3.2.0,M11.1.0","America/Port_of_Spain":"AST4","America/Porto_Velho":"<-04>4","America/Puerto_Rico":"AST4","America/Punta_Arenas":"<-03>3","America/Rainy_River":"CST6CDT,M3.2.0,M11.1.0","America/Rankin_Inlet":"CST6CDT,M3.2.0,M11.1.0","America/Recife":"<-03>3","America/Regina":"CST6","America/Resolute":"CST6CDT,M3.2.0,M11.1.0","America/Rio_Branco":"<-05>5","America/Santarem":"<-03>3","America/Santiago":"<-04>4<-03>,M9.1.6/24,M4.1.6/24","America/Santo_Domingo":"AST4","America/Sao_Paulo":"<-03>3","America/Scoresbysund":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0","America/Sitka":"AKST9AKDT,M3.2.0,M11.1.0","America/St_Barthelemy":"AST4","America/St_Johns":"NST3:30NDT,M3.2.0,M11.1.0","America/St_Kitts":"AST4","America/St_Lucia":"AST4","America/St_Thomas":"AST4","America/St_Vincent":"AST4","America/Swift_Current":"CST6","America/Tegucigalpa":"CST6","America/Thule":"AST4ADT,M3.2.0,M11.1.0","America/Thunder_Bay":"EST5EDT,M3.2.0,M11.1.0","America/Tijuana":"PST8PDT,M3.2.0,M11.1.0","America/Toronto":"EST5EDT,M3.2.0,M11.1.0","America/Tortola":"AST4","America/Vancouver":"PST8PDT,M3.2.0,M11.1.0","America/Whitehorse":"MST7","America/Winnipeg":"CST6CDT,M3.2.0,M11.1.0","America/Yakutat":"AKST9AKDT,M3.2.0,M11.1.0","America/Yellowknife":"MST7MDT,M3.2.0,M11.1.0","Antarctica/Casey":"<+08>-8","Antarctica/Davis":"<+07>-7","Antarctica/DumontDUrville":"<+10>-10","Antarctica/Macquarie":"AEST-10AEDT,M10.1.0,M4.1.0/3","Antarctica/Mawson":"<+05>-5","Antarctica/McMurdo":"NZST-12NZDT,M9.5.0,M4.1.0/3","Antarctica/Palmer":"<-03>3","Antarctica/Rothera":"<-03>3","Antarctica/Syowa":"<+03>-3","Antarctica/Troll":"<+00>0<+02>-2,M3.5.0/1,M10.5.0/3","Antarctica/Vostok":"<+05>-5","Arctic/Longyearbyen":"CET-1CEST,M3.5.0,M10.5.0/3","Asia/Aden":"<+03>-3","Asia/Almaty":"<+05>-5","Asia/Amman":"<+03>-3","Asia/Anadyr":"<+12>-12","Asia/Aqtau":"<+05>-5","Asia/Aqtobe":"<+05>-5","Asia/Ashgabat":"<+05>-5","Asia/Atyrau":"<+05>-5","Asia/Baghdad":"<+03>-3","Asia/Bahrain":"<+03>-3","Asia/Baku":"<+04>-4","Asia/Bangkok":"<+07>-7","Asia/Barnaul":"<+07>-7","Asia/Beirut":"EET-2EEST,M3.5.0/0,M10.5.0/0","Asia/Bishkek":"<+06>-6","Asia/Brunei":"<+08>-8","Asia/Chita":"<+09>-9","Asia/Choibalsan":"<+08>-8","Asia/Colombo":"<+0530>-5:30","Asia/Damascus":"<+03>-3","Asia/Dhaka":"<+06>-6","Asia/Dili":"<+09>-9","Asia/Dubai":"<+04>-4","Asia/Dushanbe":"<+05>-5","Asia/Famagusta":"EET-2EEST,M3.5.0/3,M10.5.0/4","Asia/Gaza":"EET-2EEST,M3.4.4/50,M10.4.4/50","Asia/Hebron":"EET-2EEST,M3.4.4/50,M10.4.4/50","Asia/Ho_Chi_Minh":"<+07>-7","Asia/Hong_Kong":"HKT-8","Asia/Hovd":"<+07>-7","Asia/Irkutsk":"<+08>-8","Asia/Jakarta":"WIB-7","Asia/Jayapura":"WIT-9","Asia/Jerusalem":"IST-2IDT,M3.4.4/26,M10.5.0","Asia/Kabul":"<+0430>-4:30","Asia/Kamchatka":"<+12>-12","Asia/Karachi":"PKT-5","Asia/Kathmandu":"<+0545>-5:45","Asia/Khandyga":"<+09>-9","Asia/Kolkata":"IST-5:30","Asia/Krasnoyarsk":"<+07>-7","Asia/Kuala_Lumpur":"<+08>-8","Asia/Kuching":"<+08>-8","Asia/Kuwait":"<+03>-3","Asia/Macau":"CST-8","Asia/Magadan":"<+11>-11","Asia/Makassar":"WITA-8","Asia/Manila":"PST-8","Asia/Muscat":"<+04>-4","Asia/Nicosia":"EET-2EEST,M3.5.0/3,M10.5.0/4","Asia/Novokuznetsk":"<+07>-7","Asia/Novosibirsk":"<+07>-7","Asia/Omsk":"<+06>-6","Asia/Oral":"<+05>-5","Asia/Phnom_Penh":"<+07>-7","Asia/Pontianak":"WIB-7","Asia/Pyongyang":"KST-9","Asia/Qatar":"<+03>-3","Asia/Qyzylorda":"<+05>-5","Asia/Riyadh":"<+03>-3","Asia/Sakhalin":"<+11>-11","Asia/Samarkand":"<+05>-5","Asia/Seoul":"KST-9","Asia/Shanghai":"CST-8","Asia/Singapore":"<+08>-8","Asia/Srednekolymsk":"<+11>-11","Asia/Taipei":"CST-8","Asia/Tashkent":"<+05>-5","Asia/Tbilisi":"<+04>-4","Asia/Tehran":"<+0330>-3:30","Asia/Thimphu":"<+06>-6","Asia/Tokyo":"JST-9","Asia/Tomsk":"<+07>-7","Asia/Ulaanbaatar":"<+08>-8","Asia/Urumqi":"<+06>-6","Asia/Ust-Nera":"<+10>-10","Asia/Vientiane":"<+07>-7","Asia/Vladivostok":"<+10>-10","Asia/Yakutsk":"<+09>-9","Asia/Yangon":"<+0630>-6:30","Asia/Yekaterinburg":"<+05>-5","Asia/Yerevan":"<+04>-4","Atlantic/Azores":"<-01>1<+00>,M3.5.0/0,M10.5.0/1","Atlantic/Bermuda":"AST4ADT,M3.2.0,M11.1.0","Atlantic/Canary":"WET0WEST,M3.5.0/1,M10.5.0","Atlantic/Cape_Verde":"<-01>1","Atlantic/Faroe":"WET0WEST,M3.5.0/1,M10.5.0","Atlantic/Madeira":"WET0WEST,M3.5.0/1,M10.5.0","Atlantic/Reykjavik":"GMT0","Atlantic/South_Georgia":"<-02>2","Atlantic/St_Helena":"GMT0","Atlantic/Stanley":"<-03>3","Australia/Adelaide":"ACST-9:30ACDT,M10.1.0,M4.1.0/3","Australia/Brisbane":"AEST-10","Australia/Broken_Hill":"ACST-9:30ACDT,M10.1.0,M4.1.0/3","Australia/Currie":"AEST-10AEDT,M10.1.0,M4.1.0/3","Australia/Darwin":"ACST-9:30","Australia/Eucla":"<+0845>-8:45","Australia/Hobart":"AEST-10AEDT,M10.1.0,M4.1.0/3","Australia/Lindeman":"AEST-10","Australia/Lord_Howe":"<+1030>-10:30<+11>-11,M10.1.0,M4.1.0","Australia/Melbourne":"AEST-10AEDT,M10.1.0,M4.1.0/3","Australia/Perth":"AWST-8","Australia/Sydney":"AEST-10AEDT,M10.1.0,M4.1.0/3","Etc/GMT":"GMT0","Etc/GMT+0":"GMT0","Etc/GMT+1":"<-01>1","Etc/GMT+10":"<-10>10","Etc/GMT+11":"<-11>11","Etc/GMT+12":"<-12>12","Etc/GMT+2":"<-02>2","Etc/GMT+3":"<-03>3","Etc/GMT+4":"<-04>4","Etc/GMT+5":"<-05>5","Etc/GMT+6":"<-06>6","Etc/GMT+7":"<-07>7","Etc/GMT+8":"<-08>8","Etc/GMT+9":"<-09>9","Etc/GMT-0":"GMT0","Etc/GMT-1":"<+01>-1","Etc/GMT-10":"<+10>-10","Etc/GMT-11":"<+11>-11","Etc/GMT-12":"<+12>-12","Etc/GMT-13":"<+13>-13","Etc/GMT-14":"<+14>-14","Etc/GMT-2":"<+02>-2","Etc/GMT-3":"<+03>-3","Etc/GMT-4":"<+04>-4","Etc/GMT-5":"<+05>-5","Etc/GMT-6":"<+06>-6","Etc/GMT-7":"<+07>-7","Etc/GMT-8":"<+08>-8","Etc/GMT-9":"<+09>-9","Etc/GMT0":"GMT0","Etc/Greenwich":"GMT0","Etc/UCT":"UTC0","Etc/UTC":"UTC0","Etc/Universal":"UTC0","Etc/Zulu":"UTC0","Europe/Amsterdam":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Andorra":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Astrakhan":"<+04>-4","Europe/Athens":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Belgrade":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Berlin":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Bratislava":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Brussels":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Bucharest":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Budapest":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Busingen":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Chisinau":"EET-2EEST,M3.5.0,M10.5.0/3","Europe/Copenhagen":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Dublin":"IST-1GMT0,M10.5.0,M3.5.0/1","Europe/Gibraltar":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Guernsey":"GMT0BST,M3.5.0/1,M10.5.0","Europe/Helsinki":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Isle_of_Man":"GMT0BST,M3.5.0/1,M10.5.0","Europe/Istanbul":"<+03>-3","Europe/Jersey":"GMT0BST,M3.5.0/1,M10.5.0","Europe/Kaliningrad":"EET-2","Europe/Kiev":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Kirov":"MSK-3","Europe/Lisbon":"WET0WEST,M3.5.0/1,M10.5.0","Europe/Ljubljana":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/London":"GMT0BST,M3.5.0/1,M10.5.0","Europe/Luxembourg":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Madrid":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Malta":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Mariehamn":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Minsk":"<+03>-3","Europe/Monaco":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Moscow":"MSK-3","Europe/Oslo":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Paris":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Podgorica":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Prague":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Riga":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Rome":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Samara":"<+04>-4","Europe/San_Marino":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Sarajevo":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Saratov":"<+04>-4","Europe/Simferopol":"MSK-3","Europe/Skopje":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Sofia":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Stockholm":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Tallinn":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Tirane":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Ulyanovsk":"<+04>-4","Europe/Uzhgorod":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Vaduz":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Vatican":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Vienna":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Vilnius":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Volgograd":"MSK-3","Europe/Warsaw":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Zagreb":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Zaporozhye":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Zurich":"CET-1CEST,M3.5.0,M10.5.0/3","Indian/Antananarivo":"EAT-3","Indian/Chagos":"<+06>-6","Indian/Christmas":"<+07>-7","Indian/Cocos":"<+0630>-6:30","Indian/Comoro":"EAT-3","Indian/Kerguelen":"<+05>-5","Indian/Mahe":"<+04>-4","Indian/Maldives":"<+05>-5","Indian/Mauritius":"<+04>-4","Indian/Mayotte":"EAT-3","Indian/Reunion":"<+04>-4","Pacific/Apia":"<+13>-13","Pacific/Auckland":"NZST-12NZDT,M9.5.0,M4.1.0/3","Pacific/Bougainville":"<+11>-11","Pacific/Chatham":"<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45","Pacific/Chuuk":"<+10>-10","Pacific/Easter":"<-06>6<-05>,M9.1.6/22,M4.1.6/22","Pacific/Efate":"<+11>-11","Pacific/Enderbury":"<+13>-13","Pacific/Fakaofo":"<+13>-13","Pacific/Fiji":"<+12>-12","Pacific/Funafuti":"<+12>-12","Pacific/Galapagos":"<-06>6","Pacific/Gambier":"<-09>9","Pacific/Guadalcanal":"<+11>-11","Pacific/Guam":"ChST-10","Pacific/Honolulu":"HST10","Pacific/Kiritimati":"<+14>-14","Pacific/Kosrae":"<+11>-11","Pacific/Kwajalein":"<+12>-12","Pacific/Majuro":"<+12>-12","Pacific/Marquesas":"<-0930>9:30","Pacific/Midway":"SST11","Pacific/Nauru":"<+12>-12","Pacific/Niue":"<-11>11","Pacific/Norfolk":"<+11>-11<+12>,M10.1.0,M4.1.0/3","Pacific/Noumea":"<+11>-11","Pacific/Pago_Pago":"SST11","Pacific/Palau":"<+09>-9","Pacific/Pitcairn":"<-08>8","Pacific/Pohnpei":"<+11>-11","Pacific/Port_Moresby":"<+10>-10","Pacific/Rarotonga":"<-10>10","Pacific/Saipan":"ChST-10","Pacific/Tahiti":"<-10>10","Pacific/Tarawa":"<+12>-12","Pacific/Tongatapu":"<+13>-13","Pacific/Wake":"<+12>-12","Pacific/Wallis":"<+12>-12"}}connectedCallback(){super.connectedCallback(),this.unsubscribe=Q.subscribe(t=>{t.unique_id===this.entityId&&(this.entity=t,this.requestUpdate())})}disconnectedCallback(){var t;(t=this.unsubscribe)==null||t.call(this),super.disconnectedCallback()}firstUpdated(){this.filtered=Object.keys(this.zones),this.value&&this.select(this.value)}updated(){this.value&&this.value!==this.selected&&this.select(this.value)}filter(){const t=this.query.toLowerCase();this.filtered=Object.keys(this.zones).filter(e=>e.toLowerCase().includes(t))}select(t){this.selected=t,this.query=t,this.dispatchEvent(new CustomEvent("timezone-changed",{detail:t})),this.entity&&ui(this.entity,this.zones[t])}render(){return this.entity?h`In case the "NBS Time" is not the correct timezone, select your timezone here.
${this.filtered.slice(0,50).map(t=>h`
${t} ${this.zones[t]}
`)}
`:h`loading...`}};D.styles=S`.wrap{margin-top:1rem;display:flex;flex-direction:column;gap:8px}input{padding:10px;border-radius:8px;border:1px solid #444}.list{max-height:240px;overflow:auto;border:1px solid #444;border-radius:8px}.item{display:flex;justify-content:space-between;padding:8px;cursor:pointer}.item:hover{background:rgba(127,127,127,.7)}.item.sel{background:rgba(82,82,82,.7)}.code{opacity:.6;font-size:.8rem}`;X([b({type:String})],D.prototype,"value",2);X([x()],D.prototype,"query",2);X([x()],D.prototype,"filtered",2);X([x()],D.prototype,"selected",2);X([b({type:String})],D.prototype,"entityId",2);D=X([k("timezone-selector")],D);const w={house_clean:"button-house_clean",spot_clean:"button-spot_clean",spot_clean__height___width_:"button-spot_clean__height___width_",stop_cleaning:"button-stop_cleaning",pause_cleaning:"button-pause_cleaning",resume_cleaning:"button-resume_cleaning",locate_robot:"button-locate_robot",update_status:"button-update_status",clear_errors:"button-clear_errors",shutdown:"button-shutdown",powercycle:"button-powercycle",reboot_esp:"button-reboot_esp"},v={...w,send_to_base:"button-send_to_base",start_manual_cleaning:"button-start_manual_cleaning",manual_drive_forward_up:"button-manual_drive_forward_up",manual_drive_backwards_up:"button-manual_drive_backwards_up",manual_drive_turn_left_up:"button-manual_drive_turn_left_up",manual_drive_turn_right_up:"button-manual_drive_turn_right_up",manual_drive_arc_left_up:"button-manual_drive_arc_left_up",manual_drive_arc_right_up:"button-manual_drive_arc_right_up",manual_drive_forward_down:"button-manual_drive_forward_down",manual_drive_backwards_down:"button-manual_drive_backwards_down",manual_drive_turn_left_down:"button-manual_drive_turn_left_down",manual_drive_turn_right_down:"button-manual_drive_turn_right_down",manual_drive_arc_left_down:"button-manual_drive_arc_left_down",manual_drive_arc_right_down:"button-manual_drive_arc_right_down",manual_drive_button_timeout:"button-manual_drive_button_timeout"},Tn={...w,send_to_start:"button-send_to_start"},fi={logger:"select-logger_select"},wn={...fi,navigation_mode:"select-navigation_mode"},Te={usb_connected:"binary_sensor-usb_connected",battery_over_temp:"binary_sensor-battery_over_temp",charging_active:"binary_sensor-charging_active",charging_enabled:"binary_sensor-charging_enabled",confident_on_fuel:"binary_sensor-confident_on_fuel",on_reserved_fuel:"binary_sensor-on_reserved_fuel",empty_fuel:"binary_sensor-empty_fuel",battery_failure:"binary_sensor-battery_failure",ext_power_present:"binary_sensor-ext_power_present",thermistor_present:"binary_sensor-thermistor_present"},ie={fuel_percent:"sensor-fuel_percent",battery_temp_c_avg:"sensor-battery_temp_c_avg",battery_voltage_v:"sensor-battery_voltage_v",external_voltage_v:"sensor-external_voltage_v",charger_mah:"sensor-charger_mah",discharge_mah:"sensor-discharge_mah",filter_change_time:"sensor-filter_change_time",brush_change_time:"sensor-brush_change_time",dirt_bin_alert_reminder:"sensor-dirt_bin_alert_reminder",current_dirt_bin_runtime:"sensor-current_dirt_bin_runtime",number_of_full_dust_bin_cleanings:"sensor-number_of_full_dust_bin_cleanings",battery_cycles:"sensor-battery_cycles",last_cleaning_duration:"sensor-last_cleaning_duration"},Lt={spot_clean_width:"number-spot_clean_width",spot_clean_height:"number-spot_clean_height"},Nt={timezone:"text-timezone",schedule:"text-scheduleset"},$={test_mode:"switch-test_mode",play_extra_sounds:"switch-play_extra_sounds",click_sounds:"switch-click_sounds",led:"switch-led",wall_enable:"switch-wall_enable",eco_mode:"switch-eco_mode",intenseclean:"switch-intenseclean",wifi:"switch-wifi",melody_sounds:"switch-melody_sounds",warning_sounds:"switch-warning_sounds",bin_full_detect:"switch-bin_full_detect"},We={...$,stealthled:"switch-stealthled",autoshutdown:"switch-autoshutdown",robot_schedule:"switch-robot_schedule"},C={last_cleaning_time:"text_sensor-last_cleaning_time",last_cleaning_type:"text_sensor-last_cleaning_type",robot_error:"text_sensor-robot_error",robot_alert:"text_sensor-robot_alert",serial_number:"text_sensor-serial_number",model:"text_sensor-model",software:"text_sensor-software",ui_state:"text_sensor-ui_state",nbs_time:"text_sensor-nbs_time"};var En=Object.defineProperty,Mn=Object.getOwnPropertyDescriptor,$n=(t,e,i,r)=>{for(var n=r>1?void 0:r?Mn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&En(e,i,n),n};let it=class extends E{render(){return h`
You need to turn manual cleaning on by pressing "Start", once the vacuum is in manual cleaning mode you can hold down the different actions! Timeout will stop the current action.
`}};it.styles=S`.manual-driving{display:flex;justify-content:center;align-items:center;flex-direction:column}.manual-driving span{width:75%;padding:1rem;margin:1.5rem 0;background-color:rgba(127,127,127,.3);border-radius:.5rem}`;it=$n([k("manual-driving")],it);const mi=Object.freeze({left:0,top:0,width:16,height:16}),ke=Object.freeze({rotate:0,vFlip:!1,hFlip:!1}),be=Object.freeze({...mi,...ke}),nt=Object.freeze({...be,body:"",hidden:!1}),Sn=Object.freeze({width:null,height:null}),gi=Object.freeze({...Sn,...ke});function xn(t,e=0){const i=t.replace(/^-?[0-9.]*/,"");function r(n){for(;n<0;)n+=4;return n%4}if(i===""){const n=parseInt(t);return isNaN(n)?0:r(n)}else if(i!==t){let n=0;switch(i){case"%":n=25;break;case"deg":n=90}if(n){let s=parseFloat(t.slice(0,t.length-i.length));return isNaN(s)?0:(s=s/n,s%1===0?r(s):0)}}return e}const Cn=/[\s,]+/;function kn(t,e){e.split(Cn).forEach(i=>{switch(i.trim()){case"horizontal":t.hFlip=!0;break;case"vertical":t.vFlip=!0;break}})}const bi={...gi,preserveAspectRatio:""};function jt(t){const e={...bi},i=(r,n)=>t.getAttribute(r)||n;return e.width=i("width",null),e.height=i("height",null),e.rotate=xn(i("rotate","")),kn(e,i("flip","")),e.preserveAspectRatio=i("preserveAspectRatio",i("preserveaspectratio","")),e}function Pn(t,e){for(const i in bi)if(t[i]!==e[i])return!0;return!1}const re=/^[a-z0-9]+(-[a-z0-9]+)*$/,_e=(t,e,i,r="")=>{const n=t.split(":");if(t.slice(0,1)==="@"){if(n.length<2||n.length>3)return null;r=n.shift().slice(1)}if(n.length>3||!n.length)return null;if(n.length>1){const c=n.pop(),a=n.pop(),l={provider:n.length>0?n[0]:r,prefix:a,name:c};return e&&!$e(l)?null:l}const s=n[0],o=s.split("-");if(o.length>1){const c={provider:r,prefix:o.shift(),name:o.join("-")};return e&&!$e(c)?null:c}if(i&&r===""){const c={provider:r,prefix:"",name:s};return e&&!$e(c,i)?null:c}return null},$e=(t,e)=>t?!!((t.provider===""||t.provider.match(re))&&(e&&t.prefix===""||t.prefix.match(re))&&t.name.match(re)):!1;function In(t,e){const i={};!t.hFlip!=!e.hFlip&&(i.hFlip=!0),!t.vFlip!=!e.vFlip&&(i.vFlip=!0);const r=((t.rotate||0)+(e.rotate||0))%4;return r&&(i.rotate=r),i}function Bt(t,e){const i=In(t,e);for(const r in nt)r in ke?r in t&&!(r in i)&&(i[r]=ke[r]):r in e?i[r]=e[r]:r in t&&(i[r]=t[r]);return i}function Dn(t,e){const i=t.icons,r=t.aliases||Object.create(null),n=Object.create(null);function s(o){if(i[o])return n[o]=[];if(!(o in n)){n[o]=null;const c=r[o]&&r[o].parent,a=c&&s(c);a&&(n[o]=[c].concat(a))}return n[o]}return(e||Object.keys(i).concat(Object.keys(r))).forEach(s),n}function On(t,e,i){const r=t.icons,n=t.aliases||Object.create(null);let s={};function o(c){s=Bt(r[c]||n[c],s)}return o(e),i.forEach(o),Bt(t,s)}function _i(t,e){const i=[];if(typeof t!="object"||typeof t.icons!="object")return i;t.not_found instanceof Array&&t.not_found.forEach(n=>{e(n,null),i.push(n)});const r=Dn(t);for(const n in r){const s=r[n];s&&(e(n,On(t,n,s)),i.push(n))}return i}const Ln={provider:"",aliases:{},not_found:{},...mi};function Je(t,e){for(const i in e)if(i in t&&typeof t[i]!=typeof e[i])return!1;return!0}function yi(t){if(typeof t!="object"||t===null)return null;const e=t;if(typeof e.prefix!="string"||!t.icons||typeof t.icons!="object"||!Je(t,Ln))return null;const i=e.icons;for(const n in i){const s=i[n];if(!n.match(re)||typeof s.body!="string"||!Je(s,nt))return null}const r=e.aliases||Object.create(null);for(const n in r){const s=r[n],o=s.parent;if(!n.match(re)||typeof o!="string"||!i[o]&&!r[o]||!Je(s,nt))return null}return e}const Pe=Object.create(null);function Nn(t,e){return{provider:t,prefix:e,icons:Object.create(null),missing:new Set}}function O(t,e){const i=Pe[t]||(Pe[t]=Object.create(null));return i[e]||(i[e]=Nn(t,e))}function ft(t,e){return yi(e)?_i(e,(i,r)=>{r?t.icons[i]=r:t.missing.add(i)}):[]}function jn(t,e,i){try{if(typeof i.body=="string")return t.icons[e]={...i},!0}catch{}return!1}function Bn(t,e){let i=[];return(typeof t=="string"?[t]:Object.keys(Pe)).forEach(n=>{(typeof n=="string"&&typeof e=="string"?[e]:Object.keys(Pe[n]||{})).forEach(o=>{const c=O(n,o);i=i.concat(Object.keys(c.icons).map(a=>(n!==""?"@"+n+":":"")+o+":"+a))})}),i}let de=!1;function vi(t){return typeof t=="boolean"&&(de=t),de}function he(t){const e=typeof t=="string"?_e(t,!0,de):t;if(e){const i=O(e.provider,e.prefix),r=e.name;return i.icons[r]||(i.missing.has(r)?null:void 0)}}function Ai(t,e){const i=_e(t,!0,de);if(!i)return!1;const r=O(i.provider,i.prefix);return jn(r,i.name,e)}function Rt(t,e){if(typeof t!="object")return!1;if(typeof e!="string"&&(e=t.provider||""),de&&!e&&!t.prefix){let n=!1;return yi(t)&&(t.prefix="",_i(t,(s,o)=>{o&&Ai(s,o)&&(n=!0)})),n}const i=t.prefix;if(!$e({provider:e,prefix:i,name:"a"}))return!1;const r=O(e,i);return!!ft(r,t)}function Rn(t){return!!he(t)}function Un(t){const e=he(t);return e?{...be,...e}:null}function Gn(t){const e={loaded:[],missing:[],pending:[]},i=Object.create(null);t.sort((n,s)=>n.provider!==s.provider?n.provider.localeCompare(s.provider):n.prefix!==s.prefix?n.prefix.localeCompare(s.prefix):n.name.localeCompare(s.name));let r={provider:"",prefix:"",name:""};return t.forEach(n=>{if(r.name===n.name&&r.prefix===n.prefix&&r.provider===n.provider)return;r=n;const s=n.provider,o=n.prefix,c=n.name,a=i[s]||(i[s]=Object.create(null)),l=a[o]||(a[o]=O(s,o));let u;c in l.icons?u=e.loaded:o===""||l.missing.has(c)?u=e.missing:u=e.pending;const d={provider:s,prefix:o,name:c};u.push(d)}),e}function Ti(t,e){t.forEach(i=>{const r=i.loaderCallbacks;r&&(i.loaderCallbacks=r.filter(n=>n.id!==e))})}function Hn(t){t.pendingCallbacksFlag||(t.pendingCallbacksFlag=!0,setTimeout(()=>{t.pendingCallbacksFlag=!1;const e=t.loaderCallbacks?t.loaderCallbacks.slice(0):[];if(!e.length)return;let i=!1;const r=t.provider,n=t.prefix;e.forEach(s=>{const o=s.icons,c=o.pending.length;o.pending=o.pending.filter(a=>{if(a.prefix!==n)return!0;const l=a.name;if(t.icons[l])o.loaded.push({provider:r,prefix:n,name:l});else if(t.missing.has(l))o.missing.push({provider:r,prefix:n,name:l});else return i=!0,!0;return!1}),o.pending.length!==c&&(i||Ti([t],s.id),s.callback(o.loaded.slice(0),o.missing.slice(0),o.pending.slice(0),s.abort))})}))}let Vn=0;function qn(t,e,i){const r=Vn++,n=Ti.bind(null,i,r);if(!e.pending.length)return n;const s={id:r,icons:e,callback:t,abort:n};return i.forEach(o=>{(o.loaderCallbacks||(o.loaderCallbacks=[])).push(s)}),n}const rt=Object.create(null);function Ut(t,e){rt[t]=e}function st(t){return rt[t]||rt[""]}function zn(t,e=!0,i=!1){const r=[];return t.forEach(n=>{const s=typeof n=="string"?_e(n,e,i):n;s&&r.push(s)}),r}var Fn={resources:[],index:0,timeout:2e3,rotate:750,random:!1,dataAfterTimeout:!1};function Kn(t,e,i,r){const n=t.resources.length,s=t.random?Math.floor(Math.random()*n):t.index;let o;if(t.random){let m=t.resources.slice(0);for(o=[];m.length>1;){const M=Math.floor(Math.random()*m.length);o.push(m[M]),m=m.slice(0,M).concat(m.slice(M+1))}o=o.concat(m)}else o=t.resources.slice(s).concat(t.resources.slice(0,s));const c=Date.now();let a="pending",l=0,u,d=null,p=[],f=[];typeof r=="function"&&f.push(r);function y(){d&&(clearTimeout(d),d=null)}function _(){a==="pending"&&(a="aborted"),y(),p.forEach(m=>{m.status==="pending"&&(m.status="aborted")}),p=[]}function Be(m,M){M&&(f=[]),typeof m=="function"&&f.push(m)}function ye(){return{startTime:c,payload:e,status:a,queriesSent:l,queriesPending:p.length,subscribe:Be,abort:_}}function A(){a="failed",f.forEach(m=>{m(void 0,u)})}function T(){p.forEach(m=>{m.status==="pending"&&(m.status="aborted")}),p=[]}function ji(m,M,ee){const ve=M!=="success";switch(p=p.filter(N=>N!==m),a){case"pending":break;case"failed":if(ve||!t.dataAfterTimeout)return;break;default:return}if(M==="abort"){u=ee,A();return}if(ve){u=ee,p.length||(o.length?Re():A());return}if(y(),T(),!t.random){const N=t.resources.indexOf(m.resource);N!==-1&&N!==t.index&&(t.index=N)}a="completed",f.forEach(N=>{N(ee)})}function Re(){if(a!=="pending")return;y();const m=o.shift();if(m===void 0){if(p.length){d=setTimeout(()=>{y(),a==="pending"&&(T(),A())},t.timeout);return}A();return}const M={status:"pending",resource:m,callback:(ee,ve)=>{ji(M,ee,ve)}};p.push(M),l++,d=setTimeout(Re,t.rotate),i(m,e,M.callback)}return setTimeout(Re),ye}function wi(t){const e={...Fn,...t};let i=[];function r(){i=i.filter(c=>c().status==="pending")}function n(c,a,l){const u=Kn(e,c,a,(d,p)=>{r(),l&&l(d,p)});return i.push(u),u}function s(c){return i.find(a=>c(a))||null}return{query:n,find:s,setIndex:c=>{e.index=c},getIndex:()=>e.index,cleanup:r}}function mt(t){let e;if(typeof t.resources=="string")e=[t.resources];else if(e=t.resources,!(e instanceof Array)||!e.length)return null;return{resources:e,path:t.path||"/",maxURL:t.maxURL||500,rotate:t.rotate||750,timeout:t.timeout||5e3,random:t.random===!0,index:t.index||0,dataAfterTimeout:t.dataAfterTimeout!==!1}}const Ne=Object.create(null),ne=["https://api.simplesvg.com","https://api.unisvg.com"],Se=[];for(;ne.length>0;)ne.length===1||Math.random()>.5?Se.push(ne.shift()):Se.push(ne.pop());Ne[""]=mt({resources:["https://api.iconify.design"].concat(Se)});function Gt(t,e){const i=mt(e);return i===null?!1:(Ne[t]=i,!0)}function je(t){return Ne[t]}function Wn(){return Object.keys(Ne)}function Ht(){}const Qe=Object.create(null);function Jn(t){if(!Qe[t]){const e=je(t);if(!e)return;const i=wi(e),r={config:e,redundancy:i};Qe[t]=r}return Qe[t]}function Ei(t,e,i){let r,n;if(typeof t=="string"){const s=st(t);if(!s)return i(void 0,424),Ht;n=s.send;const o=Jn(t);o&&(r=o.redundancy)}else{const s=mt(t);if(s){r=wi(s);const o=t.resources?t.resources[0]:"",c=st(o);c&&(n=c.send)}}return!r||!n?(i(void 0,424),Ht):r.query(e,n,i)().abort}const Vt="iconify2",pe="iconify",Mi=pe+"-count",qt=pe+"-version",$i=36e5,Qn=168;function ot(t,e){try{return t.getItem(e)}catch{}}function gt(t,e,i){try{return t.setItem(e,i),!0}catch{}}function zt(t,e){try{t.removeItem(e)}catch{}}function at(t,e){return gt(t,Mi,e.toString())}function ct(t){return parseInt(ot(t,Mi))||0}const R={local:!0,session:!0},Si={local:new Set,session:new Set};let bt=!1;function Zn(t){bt=t}let we=typeof window=="undefined"?{}:window;function xi(t){const e=t+"Storage";try{if(we&&we[e]&&typeof we[e].length=="number")return we[e]}catch{}R[t]=!1}function Ci(t,e){const i=xi(t);if(!i)return;const r=ot(i,qt);if(r!==Vt){if(r){const c=ct(i);for(let a=0;a{const a=pe+c.toString(),l=ot(i,a);if(typeof l=="string"){try{const u=JSON.parse(l);if(typeof u=="object"&&typeof u.cached=="number"&&u.cached>n&&typeof u.provider=="string"&&typeof u.data=="object"&&typeof u.data.prefix=="string"&&e(u,c))return!0}catch{}zt(i,a)}};let o=ct(i);for(let c=o-1;c>=0;c--)s(c)||(c===o-1?(o--,at(i,o)):Si[t].add(c))}function ki(){if(!bt){Zn(!0);for(const t in R)Ci(t,e=>{const i=e.data,r=e.provider,n=i.prefix,s=O(r,n);if(!ft(s,i).length)return!1;const o=i.lastModified||-1;return s.lastModifiedCached=s.lastModifiedCached?Math.min(s.lastModifiedCached,o):o,!0})}}function Yn(t,e){const i=t.lastModifiedCached;if(i&&i>=e)return i===e;if(t.lastModifiedCached=e,i)for(const r in R)Ci(r,n=>{const s=n.data;return n.provider!==t.provider||s.prefix!==t.prefix||s.lastModified===e});return!0}function Xn(t,e){bt||ki();function i(r){let n;if(!R[r]||!(n=xi(r)))return;const s=Si[r];let o;if(s.size)s.delete(o=Array.from(s).shift());else if(o=ct(n),!at(n,o+1))return;const c={cached:Math.floor(Date.now()/$i),provider:t.provider,data:e};return gt(n,pe+o.toString(),JSON.stringify(c))}e.lastModified&&!Yn(t,e.lastModified)||!Object.keys(e.icons).length||(e.not_found&&(e=Object.assign({},e),delete e.not_found),i("local")||i("session"))}function Ft(){}function er(t){t.iconsLoaderFlag||(t.iconsLoaderFlag=!0,setTimeout(()=>{t.iconsLoaderFlag=!1,Hn(t)}))}function tr(t,e){t.iconsToLoad?t.iconsToLoad=t.iconsToLoad.concat(e).sort():t.iconsToLoad=e,t.iconsQueueFlag||(t.iconsQueueFlag=!0,setTimeout(()=>{t.iconsQueueFlag=!1;const{provider:i,prefix:r}=t,n=t.iconsToLoad;delete t.iconsToLoad;let s;if(!n||!(s=st(i)))return;s.prepare(i,r,n).forEach(c=>{Ei(i,c,a=>{if(typeof a!="object")c.icons.forEach(l=>{t.missing.add(l)});else try{const l=ft(t,a);if(!l.length)return;const u=t.pendingIcons;u&&l.forEach(d=>{u.delete(d)}),Xn(t,a)}catch(l){console.error(l)}er(t)})})}))}const _t=(t,e)=>{const i=zn(t,!0,vi()),r=Gn(i);if(!r.pending.length){let a=!0;return e&&setTimeout(()=>{a&&e(r.loaded,r.missing,r.pending,Ft)}),()=>{a=!1}}const n=Object.create(null),s=[];let o,c;return r.pending.forEach(a=>{const{provider:l,prefix:u}=a;if(u===c&&l===o)return;o=l,c=u,s.push(O(l,u));const d=n[l]||(n[l]=Object.create(null));d[u]||(d[u]=[])}),r.pending.forEach(a=>{const{provider:l,prefix:u,name:d}=a,p=O(l,u),f=p.pendingIcons||(p.pendingIcons=new Set);f.has(d)||(f.add(d),n[l][u].push(d))}),s.forEach(a=>{const{provider:l,prefix:u}=a;n[l][u].length&&tr(a,n[l][u])}),e?qn(e,r,s):Ft},ir=t=>new Promise((e,i)=>{const r=typeof t=="string"?_e(t,!0):t;if(!r){i(t);return}_t([r||t],n=>{if(n.length&&r){const s=he(r);if(s){e({...be,...s});return}}i(t)})});function nr(t){try{const e=typeof t=="string"?JSON.parse(t):t;if(typeof e.body=="string")return{...e}}catch{}}function rr(t,e){const i=typeof t=="string"?_e(t,!0,!0):null;if(!i){const s=nr(t);return{value:t,data:s}}const r=he(i);if(r!==void 0||!i.prefix)return{value:t,name:i,data:r};const n=_t([i],()=>e(t,i,he(i)));return{value:t,name:i,loading:n}}function Ze(t){return t.hasAttribute("inline")}let Pi=!1;try{Pi=navigator.vendor.indexOf("Apple")===0}catch{}function sr(t,e){switch(e){case"svg":case"bg":case"mask":return e}return e!=="style"&&(Pi||t.indexOf("t==="unset"||t==="undefined"||t==="none";function Ii(t,e){const i={...be,...t},r={...gi,...e},n={left:i.left,top:i.top,width:i.width,height:i.height};let s=i.body;[i,r].forEach(y=>{const _=[],Be=y.hFlip,ye=y.vFlip;let A=y.rotate;Be?ye?A+=2:(_.push("translate("+(n.width+n.left).toString()+" "+(0-n.top).toString()+")"),_.push("scale(-1 1)"),n.top=n.left=0):ye&&(_.push("translate("+(0-n.left).toString()+" "+(n.height+n.top).toString()+")"),_.push("scale(1 -1)"),n.top=n.left=0);let T;switch(A<0&&(A-=Math.floor(A/4)*4),A=A%4,A){case 1:T=n.height/2+n.top,_.unshift("rotate(90 "+T.toString()+" "+T.toString()+")");break;case 2:_.unshift("rotate(180 "+(n.width/2+n.left).toString()+" "+(n.height/2+n.top).toString()+")");break;case 3:T=n.width/2+n.left,_.unshift("rotate(-90 "+T.toString()+" "+T.toString()+")");break}A%2===1&&(n.left!==n.top&&(T=n.left,n.left=n.top,n.top=T),n.width!==n.height&&(T=n.width,n.width=n.height,n.height=T)),_.length&&(s=''+s+"")});const o=r.width,c=r.height,a=n.width,l=n.height;let u,d;o===null?(d=c===null?"1em":c==="auto"?l:c,u=lt(d,a/l)):(u=o==="auto"?a:o,d=c===null?lt(u,l/a):c==="auto"?l:c);const p={},f=(y,_)=>{cr(_)||(p[y]=_.toString())};return f("width",u),f("height",d),p.viewBox=n.left.toString()+" "+n.top.toString()+" "+a.toString()+" "+l.toString(),{attributes:p,body:s}}const lr=()=>{let t;try{if(t=fetch,typeof t=="function")return t}catch{}};let Ie=lr();function ur(t){Ie=t}function dr(){return Ie}function hr(t,e){const i=je(t);if(!i)return 0;let r;if(!i.maxURL)r=0;else{let n=0;i.resources.forEach(o=>{n=Math.max(n,o.length)});const s=e+".json?icons=";r=i.maxURL-n-i.path.length-s.length}return r}function pr(t){return t===404}const fr=(t,e,i)=>{const r=[],n=hr(t,e),s="icons";let o={type:s,provider:t,prefix:e,icons:[]},c=0;return i.forEach((a,l)=>{c+=a.length+1,c>=n&&l>0&&(r.push(o),o={type:s,provider:t,prefix:e,icons:[]},c=a.length),o.icons.push(a)}),r.push(o),r};function mr(t){if(typeof t=="string"){const e=je(t);if(e)return e.path}return"/"}const gr=(t,e,i)=>{if(!Ie){i("abort",424);return}let r=mr(e.provider);switch(e.type){case"icons":{const s=e.prefix,c=e.icons.join(","),a=new URLSearchParams({icons:c});r+=s+".json?"+a.toString();break}case"custom":{const s=e.uri;r+=s.slice(0,1)==="/"?s.slice(1):s;break}default:i("abort",400);return}let n=503;Ie(t+r).then(s=>{const o=s.status;if(o!==200){setTimeout(()=>{i(pr(o)?"abort":"next",o)});return}return n=501,s.json()}).then(s=>{if(typeof s!="object"||s===null){setTimeout(()=>{s===404?i("abort",s):i("next",n)});return}setTimeout(()=>{i("success",s)})}).catch(()=>{i("next",n)})},br={prepare:fr,send:gr};function Kt(t,e){switch(t){case"local":case"session":R[t]=e;break;case"all":for(const i in R)R[i]=e;break}}const Ye="data-style";let Di="";function _r(t){Di=t}function Wt(t,e){let i=Array.from(t.childNodes).find(r=>r.hasAttribute&&r.hasAttribute(Ye));i||(i=document.createElement("style"),i.setAttribute(Ye,Ye),t.appendChild(i)),i.textContent=":host{display:inline-block;vertical-align:"+(e?"-0.125em":"0")+"}span,svg{display:block}"+Di}function Oi(){Ut("",br),vi(!0);let t;try{t=window}catch{}if(t){if(ki(),t.IconifyPreload!==void 0){const i=t.IconifyPreload,r="Invalid IconifyPreload syntax.";typeof i=="object"&&i!==null&&(i instanceof Array?i:[i]).forEach(n=>{try{(typeof n!="object"||n===null||n instanceof Array||typeof n.icons!="object"||typeof n.prefix!="string"||!Rt(n))&&console.error(r)}catch{console.error(r)}})}if(t.IconifyProviders!==void 0){const i=t.IconifyProviders;if(typeof i=="object"&&i!==null)for(const r in i){const n="IconifyProviders["+r+"] is invalid.";try{const s=i[r];if(typeof s!="object"||!s||s.resources===void 0)continue;Gt(r,s)||console.error(n)}catch{console.error(n)}}}}return{enableCache:i=>Kt(i,!0),disableCache:i=>Kt(i,!1),iconExists:Rn,getIcon:Un,listIcons:Bn,addIcon:Ai,addCollection:Rt,calculateSize:lt,buildIcon:Ii,loadIcons:_t,loadIcon:ir,addAPIProvider:Gt,appendCustomStyle:_r,_api:{getAPIConfig:je,setAPIModule:Ut,sendAPIQuery:Ei,setFetch:ur,getFetch:dr,listAPIProviders:Wn}}}function Li(t,e){let i=t.indexOf("xlink:")===-1?"":' xmlns:xlink="http://www.w3.org/1999/xlink"';for(const r in e)i+=" "+r+'="'+e[r]+'"';return'"+t+""}function yr(t){return t.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(//g,"%3E").replace(/\s+/g," ")}function vr(t){return"data:image/svg+xml,"+yr(t)}function Ar(t){return'url("'+vr(t)+'")'}const ut={"background-color":"currentColor"},Ni={"background-color":"transparent"},Jt={image:"var(--svg)",repeat:"no-repeat",size:"100% 100%"},Qt={"-webkit-mask":ut,mask:ut,background:Ni};for(const t in Qt){const e=Qt[t];for(const i in Jt)e[t+"-"+i]=Jt[i]}function Zt(t){return t?t+(t.match(/^[-0-9.]+$/)?"px":""):"inherit"}function Tr(t,e,i){const r=document.createElement("span");let n=t.body;n.indexOf("");const s=t.attributes,o=Li(n,{...s,width:e.width+"",height:e.height+""}),c=Ar(o),a=r.style,l={"--svg":c,width:Zt(s.width),height:Zt(s.height),...i?ut:Ni};for(const u in l)a.setProperty(u,l[u]);return r}let se;function wr(){try{se=window.trustedTypes.createPolicy("iconify",{createHTML:t=>t})}catch{se=null}}function Er(t){return se===void 0&&wr(),se?se.createHTML(t):t}function Mr(t){const e=document.createElement("span"),i=t.attributes;let r="";i.width||(r="width: inherit;"),i.height||(r+="height: inherit;"),r&&(i.style=r);const n=Li(t.body,i);return e.innerHTML=Er(n),e.firstChild}function Yt(t,e){const i=e.icon.data,r=e.customisations,n=Ii(i,r);r.preserveAspectRatio&&(n.attributes.preserveAspectRatio=r.preserveAspectRatio);const s=e.renderedMode;let o;switch(s){case"svg":o=Mr(n);break;default:o=Tr(n,{...be,...i},s==="mask")}const c=Array.from(t.childNodes).find(a=>{const l=a.tagName&&a.tagName.toUpperCase();return l==="SPAN"||l==="SVG"});c?o.tagName==="SPAN"&&c.tagName===o.tagName?c.setAttribute("style",o.getAttribute("style")):t.replaceChild(o,c):t.appendChild(o)}function Xt(t,e,i){const r=i&&(i.rendered?i:i.lastRender);return{rendered:!1,inline:e,icon:t,lastRender:r}}function $r(t="iconify-icon"){let e,i;try{e=window.customElements,i=window.HTMLElement}catch{return}if(!e||!i)return;const r=e.get(t);if(r)return r;const n=["icon","mode","inline","width","height","rotate","flip"],s=class extends i{constructor(){super();Ae(this,"_shadowRoot");Ae(this,"_state");Ae(this,"_checkQueued",!1);const a=this._shadowRoot=this.attachShadow({mode:"open"}),l=Ze(this);Wt(a,l),this._state=Xt({value:""},l),this._queueCheck()}static get observedAttributes(){return n.slice(0)}attributeChangedCallback(a){if(a==="inline"){const l=Ze(this),u=this._state;l!==u.inline&&(u.inline=l,Wt(this._shadowRoot,l))}else this._queueCheck()}get icon(){const a=this.getAttribute("icon");if(a&&a.slice(0,1)==="{")try{return JSON.parse(a)}catch{}return a}set icon(a){typeof a=="object"&&(a=JSON.stringify(a)),this.setAttribute("icon",a)}get inline(){return Ze(this)}set inline(a){a?this.setAttribute("inline","true"):this.removeAttribute("inline")}restartAnimation(){const a=this._state;if(a.rendered){const l=this._shadowRoot;if(a.renderedMode==="svg")try{l.lastChild.setCurrentTime(0);return}catch{}Yt(l,a)}}get status(){const a=this._state;return a.rendered?"rendered":a.icon.data===null?"failed":"loading"}_queueCheck(){this._checkQueued||(this._checkQueued=!0,setTimeout(()=>{this._check()}))}_check(){if(!this._checkQueued)return;this._checkQueued=!1;const a=this._state,l=this.getAttribute("icon");if(l!==a.icon.value){this._iconChanged(l);return}if(!a.rendered)return;const u=this.getAttribute("mode"),d=jt(this);(a.attrMode!==u||Pn(a.customisations,d))&&this._renderIcon(a.icon,d,u)}_iconChanged(a){const l=rr(a,(u,d,p)=>{const f=this._state;if(f.rendered||this.getAttribute("icon")!==u)return;const y={value:u,name:d,data:p};y.data?this._gotIconData(y):f.icon=y});l.data?this._gotIconData(l):this._state=Xt(l,this._state.inline,this._state)}_gotIconData(a){this._checkQueued=!1,this._renderIcon(a,jt(this),this.getAttribute("mode"))}_renderIcon(a,l,u){const d=sr(a.data.body,u),p=this._state.inline;Yt(this._shadowRoot,this._state={rendered:!0,icon:a,inline:p,customisations:l,attrMode:u,renderedMode:d})}};n.forEach(c=>{c in s.prototype||Object.defineProperty(s.prototype,c,{get:function(){return this.getAttribute(c)},set:function(a){a!==null?this.setAttribute(c,a):this.removeAttribute(c)}})});const o=Oi();for(const c in o)s[c]=s.prototype[c]=o[c];return e.define(t,s),s}$r()||Oi();var Sr=S`.btn,button{cursor:pointer;border-radius:4px;color:#03a9f4;border:none;background-color:unset;padding:8px;font-weight:500;font-size:12.25px;letter-spacing:1.09375px;text-transform:uppercase;margin-right:-8px}.btn:active,button:active{background-image:rgba(127,127,127,.2);transition-duration:1s}.btn:hover,button:hover{background-color:rgba(127,127,127,.2);transition-duration:1s}.abuttonIsState{background-color:#28a745;color:#fff;border:none;padding:10px 20px;font-size:16px;border-radius:4px;transition:background-color .3s ease}`,xr=S`.main-grid{display:grid;grid-template-columns:1fr 1fr;width:100%;padding-bottom:2rem}.main-buttons{display:grid;gap:1rem;justify-content:center;grid-template-columns:auto auto auto auto;align-content:center;align-items:center;justify-content:center}@media (max-width:1024px){.main-grid{grid-template-columns:1fr}.main-buttons{gap:.5rem;grid-template-columns:auto auto auto}}.flex-grid-half{display:grid;grid-template-columns:700px 2fr}.flex-grid-half.expanded_entity,.flex-grid-half.expanded_logs{grid-template-columns:1fr}.flex-grid-half .col{margin:8px}.flex-grid-half .col:nth-child(2){overflow:hidden}.flex-grid-half.expanded_logs .col:nth-child(1){display:none}.flex-grid-half.expanded_entity .col:nth-child(2){display:none}@media (max-width:1024px){.flex-grid,.flex-grid-half{display:block}.flex-grid-half .col{width:100%!important;margin:0 0 10px 0!important;display:block!important}}*{box-sizing:border-box}.flex-grid{margin:0 0 20px 0}h1{text-align:center;width:100%;line-height:1.1em;margin-block:.25em}header div{text-align:center;width:100%}header #logo,header iconify-icon{float:right;font-size:2.5rem;color:rgba(127,127,127,.5)}header #logo{float:left;color:rgba(127,127,127,.5)}.connected{color:rgba(0,157,16,.75)}esp-logo{float:left;line-height:1em;font-size:initial}form{display:flex;justify-content:space-between;background-color:rgba(127,127,127,.05);border-radius:12px;border-width:1px;border-style:solid;border-color:rgba(127,127,127,.12)}form .btn{margin-right:0}.helper{width:75%;padding:1rem;margin:.3rem 0;background-color:rgba(127,127,127,.3);border-radius:.5rem}`,Cr=Object.defineProperty,kr=Object.getOwnPropertyDescriptor,z=(t,e,i,r)=>{for(var n=r>1?void 0:r?kr(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&Cr(e,i,n),n};window.source=new EventSource(pt()+"/events");window.entities=[];const Ee={};var ti;(ti=window.source)==null||ti.addEventListener("state",t=>{const e=t,i=JSON.parse(e.data.replace(/[\u0000-\u001F\u007F-\u009F]/g,""));let r=window.entities.findIndex(n=>n.unique_id===i.id);if(r!=-1&&i.id){if(typeof i.value=="number"){let n=[...window.entities[r].value_numeric_history];n.push(i.value),window.entities[r].value_numeric_history=n.splice(-50)}delete i.id,delete i.domain,delete i.unique_id,Object.assign(window.entities[r],i)}else if(i!=null&&i.name)ei(i);else{if(Ee[i.id]?Ee[i.id]++:Ee[i.id]=1,Ee[i.id]<1)return;let n=i.id.split("-"),s=n[0],o=n.slice(1).join("-");fetch(`${window.apiBasePath}/${s}/${o}?detail=all`,{method:"GET"}).then(c=>{if(console.log(c),!c.ok)throw new Error(`HTTP error! Status: ${c.status}`);return c.json()}).then(c=>{console.log(c),ei(c)}).catch(c=>{console.error("Fetch error:",c)})}});function ei(t){if(console.log(t),window.entities.findIndex(i=>i.unique_id===t.id)===-1&&t.id){let i=t.id.split("-"),r={...t,domain:i[0],unique_id:t.id,id:i.slice(1).join("-"),entity_category:t.entity_category,value_numeric_history:[t.value]};r.has_action=`render_${r.domain}`in pi.prototype,window.entities.push(r),Q.set(r)}}function Pr(t){const e=Math.sign(t);if(t===0)return new Intl.RelativeTimeFormat("en").format(0,"second");const i=[{type:"year",seconds:12*30*24*60*60*1e3},{type:"month",seconds:30*24*60*60*1e3},{type:"week",seconds:7*24*60*60*1e3},{type:"day",seconds:24*60*60*1e3},{type:"hour",seconds:60*60*1e3},{type:"minute",seconds:60*1e3},{type:"second",seconds:1e3}];let r="";const n=new Intl.RelativeTimeFormat("en");let s=0;for(let o of i){const c=Math.trunc(Math.abs(t/o.seconds));if(c>0){const a=n.format(c*e,o.type);if(t-=c*o.seconds*e,r+=s===0&&o.type!="second"?a.replace(" ago"," "):a,s++>=1)break}}return r}let L=class extends E{constructor(){super(),this.scheme="",this.ping=0,this.connected=!0,this.lastUpdate=0,this.showLog=localStorage.showLog==="true",this.version="3.0.0",this.config={ota:!1,log:!0,title:"",comment:"",lang:""},this.darkQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.frames=[{},{color:"rgba(0, 196, 21, 0.75)"},{}];const t=document.querySelector("script#config");console.log(t),t&&this.setConfig(JSON.parse(t.innerText))}setConfig(t){"log"in t||(t.log=this.config.log),this.config=t,document.title=t.title,document.documentElement.lang=t.lang;const[e,i,r]=t.comment.split("|");this.nbsconfig={type:e,version:i,comment:r}}firstUpdated(t){super.firstUpdated(t),document.getElementsByTagName("head")[0].innerHTML+='';const e=document.querySelector("link[rel~='icon']");e.href='data:image/svg+xml,',this.scheme=this.schemeDefault(),window.source.addEventListener("ping",i=>{var r;if((r=i.data)!=null&&r.length){const n=JSON.parse(i.data);n.title!==void 0&&(this.setConfig(n),this.requestUpdate())}this._updateUptime(i),this.lastUpdate=Date.now()}),window.source.addEventListener("log",i=>{this._updateUptime(i),this.lastUpdate=Date.now()}),window.source.addEventListener("state",i=>{this.lastUpdate=Date.now()}),window.source.addEventListener("error",i=>{console.dir(i),this.connected=!1,this.requestUpdate()}),setInterval(()=>{this.connected=!!this.ping&&Date.now()-this.lastUpdate<15e3},5e3),document.addEventListener("entity-tab-header-double-clicked",i=>{var n;const r=(n=this.shadowRoot)==null?void 0:n.querySelector("main.flex-grid-half");r==null||r.classList.toggle("expanded_entity")}),document.addEventListener("log-tab-header-double-clicked",i=>{var n;const r=(n=this.shadowRoot)==null?void 0:n.querySelector("main.flex-grid-half");r==null||r.classList.toggle("expanded_logs")})}schemeDefault(){return this.darkQuery.matches?"dark":"light"}updated(t){super.updated(t),t.has("scheme")&&document.documentElement.style.setProperty("color-scheme",this.scheme),t.has("ping")&&this.ping&&this.beat.animate(this.frames,1e3)}uptime(){return`${Pr(-this.ping|0)}`}renderOta(){if(this.config.ota){let t=pt();return h` `}}renderLog(){return!this.config.log||!this.showLog?g:h`
`}renderTitle(){var t,e;return h`

${this.config.title||h` `}

${[(t=this.nbsconfig)==null?void 0:t.comment,`started ${this.uptime()}`,(e=this.nbsconfig)==null?void 0:e.version].filter(i=>i).map(i=>`${i}`).join(" \xB7 ")}
`}render(){var t,e,i,r,n,s,o;return h`
${this.renderTitle()}
${((t=this.nbsconfig)==null?void 0:t.type)==="gen3"?h``:g} ${((e=this.nbsconfig)==null?void 0:e.type)==="gen2"?h``:g}
${((i=this.nbsconfig)==null?void 0:i.type)==="gen3"?h`
`:g}
c.startsWith("200")?"No alerts":c}}">
${((r=this.nbsconfig)==null?void 0:r.type)==="gen2"?h``:g}
${((s=this.nbsconfig)==null?void 0:s.type)==="gen3"?h``:g}
The manual for the Webserver can be found here. When new updates for Neato Brainslug is out, you can easily update your device by uploading the OTA file here! ${this.renderOta()} Type: ${(o=this.nbsconfig)==null?void 0:o.type}       Toggle the debug logs: 
${this.renderLog()}`}_updateUptime(t){t.lastEventId&&(this.ping=parseInt(t.lastEventId),this.connected=!0,this.requestUpdate())}static get styles(){return[me,Sr,xr,di]}};z([x()],L.prototype,"scheme",2);z([x()],L.prototype,"ping",2);z([x()],L.prototype,"connected",2);z([x()],L.prototype,"lastUpdate",2);z([ci("#beat")],L.prototype,"beat",2);z([x()],L.prototype,"showLog",2);L=z([k("esp-app")],L); ================================================ FILE: config/js/1.2.js ================================================ var Bi=Object.defineProperty;var Ri=(t,e,i)=>e in t?Bi(t,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):t[e]=i;var Ae=(t,e,i)=>(Ri(t,typeof e!="symbol"?e+"":e,i),i);const Ui=function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))r(n);new MutationObserver(n=>{for(const s of n)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function i(n){const s={};return n.integrity&&(s.integrity=n.integrity),n.referrerpolicy&&(s.referrerPolicy=n.referrerpolicy),n.crossorigin==="use-credentials"?s.credentials="include":n.crossorigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(n){if(n.ep)return;n.ep=!0;const s=i(n);fetch(n.href,s)}};Ui();const Me=window,dt=Me.ShadowRoot&&(Me.ShadyCSS===void 0||Me.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,ht=Symbol(),yt=new WeakMap;class ii{constructor(e,i,r){if(this._$cssResult$=!0,r!==ht)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=i}get styleSheet(){let e=this.o;const i=this.t;if(dt&&e===void 0){const r=i!==void 0&&i.length===1;r&&(e=yt.get(i)),e===void 0&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),r&&yt.set(i,e))}return e}toString(){return this.cssText}}const Gi=t=>new ii(typeof t=="string"?t:t+"",void 0,ht),S=(t,...e)=>{const i=t.length===1?t[0]:e.reduce((r,n,s)=>r+(o=>{if(o._$cssResult$===!0)return o.cssText;if(typeof o=="number")return o;throw Error("use css function "+o+". Use unsafeCSS")})(n)+t[s+1],t[0]);return new ii(i,t,ht)},Hi=(t,e)=>{dt?t.adoptedStyleSheets=e.map(i=>i instanceof CSSStyleSheet?i:i.styleSheet):e.forEach(i=>{const r=document.createElement("style"),n=Me.litNonce;n!==void 0&&r.setAttribute("nonce",n),r.textContent=i.cssText,t.appendChild(r)})},vt=dt?t=>t:t=>t instanceof CSSStyleSheet?(e=>{let i="";for(const r of e.cssRules)i+=r.cssText;return Gi(i)})(t):t;var Ue;const xe=window,At=xe.trustedTypes,Vi=At?At.emptyScript:"",Tt=xe.reactiveElementPolyfillSupport,Xe={toAttribute(t,e){switch(e){case Boolean:t=t?Vi:null;break;case Object:case Array:t=t==null?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=t!==null;break;case Number:i=t===null?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch{i=null}}return i}},ni=(t,e)=>e!==t&&(e==e||t==t),Ge={attribute:!0,type:String,converter:Xe,reflect:!1,hasChanged:ni},et="finalized";class F extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this._$Eu()}static addInitializer(e){var i;this.finalize(),((i=this.h)!==null&&i!==void 0?i:this.h=[]).push(e)}static get observedAttributes(){this.finalize();const e=[];return this.elementProperties.forEach((i,r)=>{const n=this._$Ep(r,i);n!==void 0&&(this._$Ev.set(n,r),e.push(n))}),e}static createProperty(e,i=Ge){if(i.state&&(i.attribute=!1),this.finalize(),this.elementProperties.set(e,i),!i.noAccessor&&!this.prototype.hasOwnProperty(e)){const r=typeof e=="symbol"?Symbol():"__"+e,n=this.getPropertyDescriptor(e,r,i);n!==void 0&&Object.defineProperty(this.prototype,e,n)}}static getPropertyDescriptor(e,i,r){return{get(){return this[i]},set(n){const s=this[e];this[i]=n,this.requestUpdate(e,s,r)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)||Ge}static finalize(){if(this.hasOwnProperty(et))return!1;this[et]=!0;const e=Object.getPrototypeOf(this);if(e.finalize(),e.h!==void 0&&(this.h=[...e.h]),this.elementProperties=new Map(e.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const i=this.properties,r=[...Object.getOwnPropertyNames(i),...Object.getOwnPropertySymbols(i)];for(const n of r)this.createProperty(n,i[n])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(e){const i=[];if(Array.isArray(e)){const r=new Set(e.flat(1/0).reverse());for(const n of r)i.unshift(vt(n))}else e!==void 0&&i.push(vt(e));return i}static _$Ep(e,i){const r=i.attribute;return r===!1?void 0:typeof r=="string"?r:typeof e=="string"?e.toLowerCase():void 0}_$Eu(){var e;this._$E_=new Promise(i=>this.enableUpdating=i),this._$AL=new Map,this._$Eg(),this.requestUpdate(),(e=this.constructor.h)===null||e===void 0||e.forEach(i=>i(this))}addController(e){var i,r;((i=this._$ES)!==null&&i!==void 0?i:this._$ES=[]).push(e),this.renderRoot!==void 0&&this.isConnected&&((r=e.hostConnected)===null||r===void 0||r.call(e))}removeController(e){var i;(i=this._$ES)===null||i===void 0||i.splice(this._$ES.indexOf(e)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach((e,i)=>{this.hasOwnProperty(i)&&(this._$Ei.set(i,this[i]),delete this[i])})}createRenderRoot(){var e;const i=(e=this.shadowRoot)!==null&&e!==void 0?e:this.attachShadow(this.constructor.shadowRootOptions);return Hi(i,this.constructor.elementStyles),i}connectedCallback(){var e;this.renderRoot===void 0&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),(e=this._$ES)===null||e===void 0||e.forEach(i=>{var r;return(r=i.hostConnected)===null||r===void 0?void 0:r.call(i)})}enableUpdating(e){}disconnectedCallback(){var e;(e=this._$ES)===null||e===void 0||e.forEach(i=>{var r;return(r=i.hostDisconnected)===null||r===void 0?void 0:r.call(i)})}attributeChangedCallback(e,i,r){this._$AK(e,r)}_$EO(e,i,r=Ge){var n;const s=this.constructor._$Ep(e,r);if(s!==void 0&&r.reflect===!0){const o=(((n=r.converter)===null||n===void 0?void 0:n.toAttribute)!==void 0?r.converter:Xe).toAttribute(i,r.type);this._$El=e,o==null?this.removeAttribute(s):this.setAttribute(s,o),this._$El=null}}_$AK(e,i){var r;const n=this.constructor,s=n._$Ev.get(e);if(s!==void 0&&this._$El!==s){const o=n.getPropertyOptions(s),c=typeof o.converter=="function"?{fromAttribute:o.converter}:((r=o.converter)===null||r===void 0?void 0:r.fromAttribute)!==void 0?o.converter:Xe;this._$El=s,this[s]=c.fromAttribute(i,o.type),this._$El=null}}requestUpdate(e,i,r){let n=!0;e!==void 0&&(((r=r||this.constructor.getPropertyOptions(e)).hasChanged||ni)(this[e],i)?(this._$AL.has(e)||this._$AL.set(e,i),r.reflect===!0&&this._$El!==e&&(this._$EC===void 0&&(this._$EC=new Map),this._$EC.set(e,r))):n=!1),!this.isUpdatePending&&n&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(i){Promise.reject(i)}const e=this.scheduleUpdate();return e!=null&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var e;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach((n,s)=>this[s]=n),this._$Ei=void 0);let i=!1;const r=this._$AL;try{i=this.shouldUpdate(r),i?(this.willUpdate(r),(e=this._$ES)===null||e===void 0||e.forEach(n=>{var s;return(s=n.hostUpdate)===null||s===void 0?void 0:s.call(n)}),this.update(r)):this._$Ek()}catch(n){throw i=!1,this._$Ek(),n}i&&this._$AE(r)}willUpdate(e){}_$AE(e){var i;(i=this._$ES)===null||i===void 0||i.forEach(r=>{var n;return(n=r.hostUpdated)===null||n===void 0?void 0:n.call(r)}),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(e){return!0}update(e){this._$EC!==void 0&&(this._$EC.forEach((i,r)=>this._$EO(r,this[r],i)),this._$EC=void 0),this._$Ek()}updated(e){}firstUpdated(e){}}F[et]=!0,F.elementProperties=new Map,F.elementStyles=[],F.shadowRootOptions={mode:"open"},Tt==null||Tt({ReactiveElement:F}),((Ue=xe.reactiveElementVersions)!==null&&Ue!==void 0?Ue:xe.reactiveElementVersions=[]).push("1.6.3");var He;const Ce=window,K=Ce.trustedTypes,wt=K?K.createPolicy("lit-html",{createHTML:t=>t}):void 0,tt="$lit$",P=`lit$${(Math.random()+"").slice(9)}$`,ri="?"+P,qi=`<${ri}>`,U=document,oe=()=>U.createComment(""),ae=t=>t===null||typeof t!="object"&&typeof t!="function",si=Array.isArray,zi=t=>si(t)||typeof(t==null?void 0:t[Symbol.iterator])=="function",Ve=`[ \f\r]`,te=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Et=/-->/g,Mt=/>/g,j=RegExp(`>|${Ve}(?:([^\\s"'>=/]+)(${Ve}*=${Ve}*(?:[^ \f\r"'\`<>=]|("|')|))|$)`,"g"),$t=/'/g,St=/"/g,oi=/^(?:script|style|textarea|title)$/i,Fi=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),h=Fi(1),W=Symbol.for("lit-noChange"),g=Symbol.for("lit-nothing"),xt=new WeakMap,B=U.createTreeWalker(U,129,null,!1);function ai(t,e){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return wt!==void 0?wt.createHTML(e):e}const Ki=(t,e)=>{const i=t.length-1,r=[];let n,s=e===2?"":"",o=te;for(let c=0;c"?(o=n!=null?n:te,d=-1):u[1]===void 0?d=-2:(d=o.lastIndex-u[2].length,l=u[1],o=u[3]===void 0?j:u[3]==='"'?St:$t):o===St||o===$t?o=j:o===Et||o===Mt?o=te:(o=j,n=void 0);const f=o===j&&t[c+1].startsWith("/>")?" ":"";s+=o===te?a+qi:d>=0?(r.push(l),a.slice(0,d)+tt+a.slice(d)+P+f):a+P+(d===-2?(r.push(void 0),c):f)}return[ai(t,s+(t[i]||"")+(e===2?"":"")),r]};class ce{constructor({strings:e,_$litType$:i},r){let n;this.parts=[];let s=0,o=0;const c=e.length-1,a=this.parts,[l,u]=Ki(e,i);if(this.el=ce.createElement(l,r),B.currentNode=this.el.content,i===2){const d=this.el.content,p=d.firstChild;p.remove(),d.append(...p.childNodes)}for(;(n=B.nextNode())!==null&&a.length0){n.textContent=K?K.emptyScript:"";for(let f=0;f2||r[0]!==""||r[1]!==""?(this._$AH=Array(r.length-1).fill(new String),this.strings=r):this._$AH=g}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(e,i=this,r,n){const s=this.strings;let o=!1;if(s===void 0)e=J(this,e,i,0),o=!ae(e)||e!==this._$AH&&e!==W,o&&(this._$AH=e);else{const c=e;let a,l;for(e=s[0],a=0;a{var r,n;const s=(r=i==null?void 0:i.renderBefore)!==null&&r!==void 0?r:e;let o=s._$litPart$;if(o===void 0){const c=(n=i==null?void 0:i.renderBefore)!==null&&n!==void 0?n:null;s._$litPart$=o=new fe(e.insertBefore(oe(),c),c,void 0,i!=null?i:{})}return o._$AI(t),o};var qe,ze;class E extends F{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){var e,i;const r=super.createRenderRoot();return(e=(i=this.renderOptions).renderBefore)!==null&&e!==void 0||(i.renderBefore=r.firstChild),r}update(e){const i=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=en(i,this.renderRoot,this.renderOptions)}connectedCallback(){var e;super.connectedCallback(),(e=this._$Do)===null||e===void 0||e.setConnected(!0)}disconnectedCallback(){var e;super.disconnectedCallback(),(e=this._$Do)===null||e===void 0||e.setConnected(!1)}render(){return W}}E.finalized=!0,E._$litElement$=!0,(qe=globalThis.litElementHydrateSupport)===null||qe===void 0||qe.call(globalThis,{LitElement:E});const kt=globalThis.litElementPolyfillSupport;kt==null||kt({LitElement:E});((ze=globalThis.litElementVersions)!==null&&ze!==void 0?ze:globalThis.litElementVersions=[]).push("3.3.3");const k=t=>e=>typeof e=="function"?((i,r)=>(customElements.define(i,r),r))(t,e):((i,r)=>{const{kind:n,elements:s}=r;return{kind:n,elements:s,finisher(o){customElements.define(i,o)}}})(t,e),tn=(t,e)=>e.kind==="method"&&e.descriptor&&!("value"in e.descriptor)?{...e,finisher(i){i.createProperty(e.key,t)}}:{kind:"field",key:Symbol(),placement:"own",descriptor:{},originalKey:e.key,initializer(){typeof e.initializer=="function"&&(this[e.key]=e.initializer.call(this))},finisher(i){i.createProperty(e.key,t)}},nn=(t,e,i)=>{e.constructor.createProperty(i,t)};function b(t){return(e,i)=>i!==void 0?nn(t,e,i):tn(t,e)}function x(t){return b({...t,state:!0})}const rn=({finisher:t,descriptor:e})=>(i,r)=>{var n;if(r===void 0){const s=(n=i.originalKey)!==null&&n!==void 0?n:i.key,o=e!=null?{kind:"method",placement:"prototype",key:s,descriptor:e(i.key)}:{...i,key:s};return t!=null&&(o.finisher=function(c){t(c,s)}),o}{const s=i.constructor;e!==void 0&&Object.defineProperty(i,r,e(r)),t==null||t(s,r)}};function ci(t,e){return rn({descriptor:i=>{const r={get(){var n,s;return(s=(n=this.renderRoot)===null||n===void 0?void 0:n.querySelector(t))!==null&&s!==void 0?s:null},enumerable:!0,configurable:!0};if(e){const n=typeof i=="symbol"?Symbol():"__"+i;r.get=function(){var s,o;return this[n]===void 0&&(this[n]=(o=(s=this.renderRoot)===null||s===void 0?void 0:s.querySelector(t))!==null&&o!==void 0?o:null),this[n]}}return r}})}var Fe;((Fe=window.HTMLSlotElement)===null||Fe===void 0?void 0:Fe.prototype.assignedElements)!=null;function pt(){let t=window.location.pathname;return t.endsWith("/")?t.slice(0,-1):t}window.apiBasePath=pt();function li(t,e){fetch(`${window.apiBasePath}/${t.domain}/${t.id}/${e}`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"}})}function Ke(t){li(t,"press")}function ui(t,e){li(t,`set?value=${encodeURIComponent(e)}`)}var di=S`.tab-header{display:inline-flex;max-width:90%;font-weight:400;padding-inline:1.5em;padding-top:.5em;padding-bottom:.5em;align-items:center;border-radius:12px 12px 0 0;background-color:rgba(127,127,127,.3);margin-top:1em;user-select:none}.tab-container{border:2px solid rgba(127,127,127,.3);border-radius:0 12px 12px 12px}`,sn=Object.defineProperty,on=Object.getOwnPropertyDescriptor,Oe=(t,e,i,r)=>{for(var n=r>1?void 0:r?on(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&sn(e,i,n),n};let le=class extends E{constructor(){super(),this.rows=10,this.scheme="",this.logs=[],this.handleLog=t=>{const i=t.data,n={"\x1B[1;31m":"e","\x1B[0;33m":"w","\x1B[0;32m":"i","\x1B[0;35m":"c","\x1B[0;36m":"d","\x1B[0;37m":"v"}[i.slice(0,7)];if(!n)return;const o=i.slice(7,i.length-4).split(` `),c=o[0],l=c.slice(3).split(":").slice(0,2).join(":"),u=c.slice(5+l.length),d=c.slice(0,3),p=new Date().toTimeString().split(" ")[0];o.forEach((f,y)=>{console.log("ESP_LOG",f);const _={type:n,level:d,tag:l,detail:y===0?u:f,when:p};this.logs.push(_)}),this.logs=this.logs.slice(-this.rows)}}connectedCallback(){var t;super.connectedCallback(),(t=window.source)==null||t.addEventListener("log",this.handleLog)}disconnectedCallback(){var t;(t=window.source)==null||t.removeEventListener("log",this.handleLog),super.disconnectedCallback()}render(){return h`
Debug Log
Time
Level
Tag
Message
${this.logs.map(t=>h`
${t.when}
${t.level}
${t.tag}
${t.detail}
`)}
`}_handleTabHeaderDblClick(t){var i;const e=new CustomEvent("log-tab-header-double-clicked",{bubbles:!0,composed:!0});(i=t.target)==null||i.dispatchEvent(e)}static get styles(){return[di,S`.tbody .trow:nth-child(2n),.thead{background-color:rgba(127,127,127,.05)}.trow div{font-family:monospace;width:100%;line-height:1.2rem}.trow{display:flex}.thead{line-height:1rem}.thead .trow{text-align:left;padding:.25rem .5rem}.trow{display:flex}.trow>div{align-self:flex-start;padding-right:.25em;flex:2 0;min-width:70px}.trow>div:nth-child(2){flex:1 0;overflow:hidden;text-overflow:ellipsis;max-width:40px}.trow>div:nth-child(3){flex:3 0;overflow:hidden;text-overflow:ellipsis}.trow>div:last-child{flex:15 0;padding-right:0;overflow:hidden;text-overflow:ellipsis}pre{margin:0}.v{color:#888}.d{color:#0dd}.c{color:#ff00ff}.i{color:#32cd32}.w{color:#ff0}.e{color:red;font-weight:700}.logs[color-scheme=light]{font-weight:700}.logs[color-scheme=light] .w{color:#cc0}.logs[color-scheme=dark] .d{color:#0aa}.logs{overflow-x:auto;border-radius:12px;border-width:1px;border-style:solid;border-color:rgba(127,127,127,.12);transition:all .3s ease-out 0s;font-size:14px;padding:16px}@media (max-width:1024px){.trow>div:nth-child(2){display:none!important}}`]}};Oe([b({type:Number})],le.prototype,"rows",2);Oe([b({type:String})],le.prototype,"scheme",2);Oe([x()],le.prototype,"logs",2);le=Oe([k("esp-log")],le);var me=S`:host,button,input,select{font-family:ui-monospace,system-ui,Helvetica,Roboto,Oxygen,Ubuntu,sans-serif;--primary-color:#03a9f4;transition:all 350ms!important}`;class an{constructor(){this.entities=new Map,this.listeners=new Set}set(e){this.entities.set(e.unique_id,e),this.notify(e)}get(e){return this.entities.get(e)}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}notify(e){for(const i of this.listeners)i(e)}}const Q=new an;var cn=Object.defineProperty,ln=Object.getOwnPropertyDescriptor,q=(t,e,i,r)=>{for(var n=r>1?void 0:r?ln(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&cn(e,i,n),n};let I=class extends E{constructor(){super(...arguments),this.click="",this.press="",this.release="",this.icon="",this.name="",this.entities={}}connectedCallback(){super.connectedCallback(),this.unsubscribe=Q.subscribe(t=>{t.unique_id===this.click&&(this.entities.click=t),t.unique_id===this.press&&(this.entities.press=t),t.unique_id===this.release&&(this.entities.release=t),this.requestUpdate()})}disconnectedCallback(){var t;(t=this.unsubscribe)==null||t.call(this),super.disconnectedCallback()}get entityIds(){let t=[];return this.click&&t.push(this.click),this.press&&t.push(this.press),this.release&&t.push(this.release),t}onClick(){this.entities.click&&Ke(this.entities.click)}onMouseDown(){this.entities.press&&Ke(this.entities.press)}onMouseUp(){this.entities.release&&Ke(this.entities.release)}updated(t){!this.customButtom||(this.press&&(this.customButtom.removeEventListener("touchstart",this.onMouseDown.bind(this)),this.customButtom.addEventListener("touchstart",this.onMouseDown.bind(this))),this.release&&(this.customButtom.removeEventListener("touchend",this.onMouseUp.bind(this)),this.customButtom.addEventListener("touchend",this.onMouseUp.bind(this))))}render(){var t,e,i,r,n,s;return Object.keys(this.entities).length!==this.entityIds.length?h`
loading…
`:h`
${this.name||((r=this.entities.click)==null?void 0:r.name)||((n=this.entities.press)==null?void 0:n.name)||((s=this.entities.release)==null?void 0:s.name)}
`}static get styles(){return[me,S`.cb{border:.1rem solid gray;border-radius:.5rem;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;padding:1rem;width:6rem}.cb span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.cb:hover{background-color:rgba(255,255,255,.1);cursor:pointer}@media (max-width:1024px){.cb{font-size:.8rem;width:5.3rem}}`]}};q([b({type:String})],I.prototype,"click",2);q([b({type:String})],I.prototype,"press",2);q([b({type:String})],I.prototype,"release",2);q([b({type:String})],I.prototype,"icon",2);q([b({type:String})],I.prototype,"name",2);q([ci("#custom-button")],I.prototype,"customButtom",2);I=q([k("custom-button")],I);var un=S`:host{position:relative}select{background-color:inherit;color:inherit;width:100%;border-radius:4px}option{color:currentColor;background-color:var(--primary-color,currentColor)}input[type=range],input[type=text]{width:calc(100% - 3rem);height:.75rem}.range{text-align:center}.entity-row{display:flex;align-items:center;flex-direction:row;transition:all .3s ease-out 0s;min-height:40px;position:relative}.entity-row.expanded{min-height:240px}.entity-row:nth-child(2n){background-color:rgba(90,90,90,.1)}.entity-row iconify-icon{vertical-align:middle}.entity-row>:nth-child(1){flex:0 0 40px;color:#44739e;line-height:40px;text-align:center}.entity-row>:nth-child(2){flex:1 1 40%;margin-left:16px;margin-right:8px;text-wrap:nowrap;overflow:hidden;text-overflow:ellipsis}.entity-row>:nth-child(3){flex:1 1 50%;margin-right:8px;margin-left:20px;text-align:right;display:flex;justify-content:space-between;white-space:normal;overflow-wrap:anywhere;word-break:break-word}.entity-row>:nth-child(3)>:only-child{margin-left:auto}.binary_sensor_off{color:rgba(127,127,127,.7)}.singlebutton-row button{margin:auto;display:flex}.climate-wrap{width:100%;margin:10px 0 10px 0}.climate-row{width:100%;display:inline-flex;flex-wrap:wrap;text-align:left}.climate-row>select{width:50%}.climate-row>label{align-content:center;width:150px}input[type=color]::-webkit-color-swatch-wrapper{padding:0!important}`,dn=Object.defineProperty,hn=Object.getOwnPropertyDescriptor,Le=(t,e,i,r)=>{for(var n=r>1?void 0:r?hn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&dn(e,i,n),n};const hi="ON",Pt="OFF";let ue=class extends E{constructor(){super(...arguments),this.entityIds=[],this.customNames={},this.customValues={},this.entities=[],this._actionRenderer=new pi}connectedCallback(){super.connectedCallback(),this.unsubscribe=Q.subscribe(t=>{this.entityIds.some(e=>e===t.unique_id)&&(this.entities.push(t),this.entities.sort((e,i)=>this.entityIds.indexOf(e.unique_id)-this.entityIds.indexOf(i.unique_id)),this.requestUpdate())})}disconnectedCallback(){var t;(t=this.unsubscribe)==null||t.call(this),super.disconnectedCallback()}render(){return this.entities.length?h`
${this.entities.map(t=>{var e,i,r;return h`
${t.icon?h``:g}
${((e=this.customNames)==null?void 0:e[t.unique_id])||t.name}
${t.has_action?this.control(t):h`
${(i=this.customValues)!=null&&i[t.unique_id]?(r=this.customValues)==null?void 0:r[t.unique_id](t.state):t.state}
`}
`})}
`:h`loading…`}hasAction(t){return`render_${t.domain}`in this._actionRenderer}control(t){return this._actionRenderer.entity=t,this._actionRenderer.actioner=this,this._actionRenderer.exec(`render_${t.domain}`)}restAction(t,e){fetch(`${window.apiBasePath}/${t.domain}/${t.id}/${e}`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"}}).then(i=>{console.log(i)})}static get styles(){return[me,un]}};Le([b({type:String})],ue.prototype,"entityIds",2);Le([b()],ue.prototype,"customNames",2);Le([b()],ue.prototype,"customValues",2);ue=Le([k("custom-table")],ue);class pi{exec(e){if(!this[e]||typeof this[e]!="function"){console.log(`ActionRenderer.${e} is not callable`);return}return this[e]()}_actionButton(e,i,r,n=!1){if(!e)return;let s=r||i.toLowerCase();return h``}_datetime(e,i,r,n,s){return h``}_switch(e){return h``}_select(e,i,r,n,s){return h``}_range(e,i,r,n,s,o,c=1){return e.mode==1?h`
`:h``}_textinput(e,i,r,n,s,o,c){return h``}_colorpicker(e,i,r){function n(o){return Number(o).toString(16).padStart(2,"0")}function s(o){var a;const c=((a=o.match(/[0-9a-f]{2}/gi))==null?void 0:a.map(l=>parseInt(l,16)))||[0,0,0];return`r=${c[0]}&g=${c[1]}&b=${c[2]}`}return h`
`}render_binary_sensor(){var i;if(!this.entity)return;const e=this.entity.state==hi;return h``}render_date(){if(!!this.entity)return h`${this._datetime(this.entity,"date","set","value",this.entity.value)}`}render_time(){if(!!this.entity)return h`${this._datetime(this.entity,"time","set","value",this.entity.value)}`}render_datetime(){if(!!this.entity)return h`${this._datetime(this.entity,"datetime-local","set","value",this.entity.value)}`}render_switch(){if(!!this.entity)return this.entity.assumed_state?h`${this._actionButton(this.entity,"\u274C","turn_off")} ${this._actionButton(this.entity,"\u2714\uFE0F","turn_on")}`:this._switch(this.entity)}render_fan(){if(!!this.entity)return[this.entity.speed," ",this.entity.speed_level,this._switch(this.entity),this.entity.speed_count?this._range(this.entity,`turn_${this.entity.state.toLowerCase()}`,"speed_level",this.entity.speed_level?this.entity.speed_level:0,0,this.entity.speed_count,1):""]}render_light(){var e,i;if(!!this.entity)return[h`
${this._switch(this.entity)} ${this.entity.brightness?this._range(this.entity,"turn_on","brightness",this.entity.brightness,0,255,1):""} ${this.entity.color_mode==="rgb"||this.entity.color_mode==="rgbw"?this._colorpicker(this.entity,"turn_on",(e=this.entity)==null?void 0:e.color):""} ${(i=this.entity.effects)!=null&&i.filter(r=>r!="None").length?this._select(this.entity,"turn_on","effect",this.entity.effects||[],this.entity.effect):""}
`]}render_lock(){if(!!this.entity)return h`${this._actionButton(this.entity,"\u{1F510}","lock",this.entity.state==="LOCKED")} ${this._actionButton(this.entity,"\u{1F513}","unlock",this.entity.state==="UNLOCKED")} ${this._actionButton(this.entity,"\u2191","open")}`}render_cover(){if(!!this.entity)return h`${this._actionButton(this.entity,"\u2191","open",this.entity.state==="OPEN")} ${this._actionButton(this.entity,"\u2610","stop")} ${this._actionButton(this.entity,"\u2193","close",this.entity.state==="CLOSED")}`}render_button(){if(!!this.entity)return h`${this._actionButton(this.entity,"PRESS","press")}`}render_select(){if(!!this.entity)return this._select(this.entity,"set","option",this.entity.option||[],this.entity.value)}render_number(){if(!!this.entity)return h`${this._range(this.entity,"set","value",this.entity.value,this.entity.min_value,this.entity.max_value,this.entity.step)} ${this.entity.uom}`}render_text(){if(!!this.entity)return this._textinput(this.entity,"set","value",this.entity.value,this.entity.min_length,this.entity.max_length,this.entity.pattern)}render_climate(){if(!this.entity)return;let e,i=h`
`;this.entity.target_temperature_low!==void 0&&this.entity.target_temperature_high!==void 0?e=h`
${this._range(this.entity,"set","target_temperature_low",this.entity.target_temperature_low,this.entity.min_temp,this.entity.max_temp,this.entity.step)}
${this._range(this.entity,"set","target_temperature_high",this.entity.target_temperature_high,this.entity.min_temp,this.entity.max_temp,this.entity.step)}
`:e=h`
${this._range(this.entity,"set","target_temperature",this.entity.target_temperature,this.entity.min_temp,this.entity.max_temp,this.entity.step)}
`;let r=h``;return(this.entity.modes?this.entity.modes.length:0)>0&&(r=h`
${this._select(this.entity,"set","mode",this.entity.modes||[],this.entity.mode||"")}
`),h`
${i} ${e} ${r}
`}render_valve(){if(!!this.entity)return h`${this._actionButton(this.entity,"OPEN","open",this.entity.state==="OPEN")} ${this._actionButton(this.entity,"\u2610","stop")} ${this._actionButton(this.entity,"CLOSE","close",this.entity.state==="CLOSED")}`}}var pn=Object.defineProperty,fn=Object.getOwnPropertyDescriptor,Z=(t,e,i,r)=>{for(var n=r>1?void 0:r?fn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&pn(e,i,n),n};const It="checkbox-lever";let G=class extends E{constructor(){super(...arguments),this.checkbox=null,this.stateOn=hi,this.stateOff=Pt,this.state=Pt,this.color="currentColor",this.disabled=!1}firstUpdated(t){var e;this.checkbox=(e=this.shadowRoot)==null?void 0:e.getElementById(It)}isOn(){return this.state===this.stateOn}toggle(t){const e=this.isOn()?this.stateOff:this.stateOn;let i=new CustomEvent("state",{detail:{state:e,id:this.id}});this.dispatchEvent(i)}render(){return h`
`}static get styles(){return[me,S`.sw,.sw *{-webkit-tap-highlight-color:transparent;user-select:none;cursor:pointer}input[type=checkbox]{opacity:0;width:0;height:0}input[type=checkbox]:checked+.lever{background-color:currentColor;background-image:linear-gradient(0deg,rgba(255,255,255,.5) 0,rgba(255,255,255,.5) 100%)}input[type=checkbox]:checked+.lever:after,input[type=checkbox]:checked+.lever:before{left:18px}input[type=checkbox]:checked+.lever:after{background-color:currentColor}input[type=checkbox]:not(:checked)+.lever:after{background-color:rgba(127,127,127,.5)}.lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-image:linear-gradient(0deg,rgba(127,127,127,.5) 0,rgba(127,127,127,.5) 100%);background-color:inherit;border-radius:15px;transition:background .3s ease;vertical-align:middle}.lever:after,.lever:before{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;transition:left .3s ease,background .3s ease,box-shadow .1s ease,transform .1s ease}.lever:before{background-color:currentColor;background-image:linear-gradient(0deg,rgba(255,255,255,.9) 0,rgba(255,255,255,.9) 100%)}.lever:after{background-color:#f1f1f1;box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12)}input[type=checkbox]:checked:not(:disabled).tabbed:focus~.lever::before,input[type=checkbox]:checked:not(:disabled)~.lever:active::before{transform:scale(2.4);background-color:rgba(255,255,255,.9) 0;background-image:linear-gradient(0deg,rgba(255,255,255,.9) 0,rgba(255,255,255,.9) 100%)}input[type=checkbox]:not(:disabled).tabbed:focus~.lever::before,input[type=checkbox]:not(:disabled)~.lever:active:before{transform:scale(2.4);background-color:rgba(0,0,0,.08)}input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,.12)}input[type=checkbox][disabled]+.lever:after,input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}`]}};Z([b({type:String})],G.prototype,"stateOn",2);Z([b({type:String})],G.prototype,"stateOff",2);Z([b({type:String})],G.prototype,"state",2);Z([b({type:String})],G.prototype,"color",2);Z([b({type:Boolean})],G.prototype,"disabled",2);G=Z([k("esp-switch")],G);var mn=Object.defineProperty,gn=Object.getOwnPropertyDescriptor,Y=(t,e,i,r)=>{for(var n=r>1?void 0:r?gn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&mn(e,i,n),n};const Dt="range",bn="rangeValue",Ot=500;let H=class extends E{constructor(){super(...arguments),this.inputRange=null,this.currentValue=null,this.longPressTimer=null,this.isPopupInputVisible=!1,this.value=0,this.min=0,this.max=0,this.step=0,this.name=""}firstUpdated(t){var e,i;this.inputRange=(e=this.shadowRoot)==null?void 0:e.getElementById(Dt),this.currentValue=(i=this.shadowRoot)==null?void 0:i.getElementById(bn),document.addEventListener("mousedown",r=>{var s;if(!document.querySelector(".popup-number-input"))return;!((s=document.querySelector(".popup-number-input"))!=null&&s.contains(r.target))&&this.isPopupInputVisible&&this.deletePopupInput()})}updated(){this.updateCurrentValueOverlay()}onMouseDownCurrentValue(t){this.longPressTimer=setTimeout(()=>{this.showPopupInput(t.pageX,t.pageY)},Ot)}onMouseUpCurrentValue(t){this.longPressTimer&&!this.isPopupInputVisible&&(clearTimeout(this.longPressTimer),this.longPressTimer=null)}onTouchStartCurrentValue(t){this.longPressTimer=setTimeout(()=>{this.showPopupInput(t.touches[0].pageX,t.touches[0].pageY)},Ot)}onTouchEndCurrentValue(t){this.longPressTimer&&!this.isPopupInputVisible&&(clearTimeout(this.longPressTimer),this.longPressTimer=null)}deletePopupInput(){const t=document.querySelector(".popup-number-input");t&&t.remove(),this.isPopupInputVisible=!1}showPopupInput(t,e){const i=document.createElement("input");i.type="number",i.value=this.inputRange.value,i.min=this.inputRange.min,i.max=this.inputRange.max,i.step=this.inputRange.step,i.classList.add("popup-number-input");const r=` position: absolute; left: ${t}px; top: ${e}px; width: 50px; -webkit-appearance: none; margin: 0; `;i.setAttribute("style",r),document.body.appendChild(i),i.addEventListener("contextmenu",n=>{n.preventDefault()}),i.addEventListener("change",n=>{var c,a;let s=n.target;this.inputRange.value=s==null?void 0:s.value;var o=new Event("input");(c=this.inputRange)==null||c.dispatchEvent(o);var o=new Event("change");(a=this.inputRange)==null||a.dispatchEvent(o)}),i.addEventListener("keydown",n=>{n.key==="Enter"&&this.deletePopupInput()}),i.focus(),this.isPopupInputVisible=!0}updateCurrentValueOverlay(){var r,n;const t=Number((this.inputRange.value-this.inputRange.min)*100/(this.inputRange.max-this.inputRange.min)),e=10-t*.2;this.currentValue.innerHTML=`${(r=this.inputRange)==null?void 0:r.value}`,this.currentValue.style.left=`calc(${t}% + (${e}px))`;const i=(n=this.currentValue)==null?void 0:n.querySelector("span");i==null||i.addEventListener("mousedown",this.onMouseDownCurrentValue.bind(this)),i==null||i.addEventListener("mouseup",this.onMouseUpCurrentValue.bind(this)),i==null||i.addEventListener("touchstart",this.onTouchStartCurrentValue.bind(this)),i==null||i.addEventListener("touchend",this.onTouchEndCurrentValue.bind(this)),i==null||i.addEventListener("contextmenu",s=>{s.preventDefault()})}onInputEvent(t){this.updateCurrentValueOverlay()}onInputChangeEvent(t){var e;this.sendState((e=this.inputRange)==null?void 0:e.value)}sendState(t){let e=new CustomEvent("state",{detail:{state:t,id:this.id}});this.dispatchEvent(e)}render(){return h`
`}static get styles(){return[me,S`:host{min-width:150px;flex:1}input[type=range]{background:0 0;-webkit-appearance:none;appearance:none;margin:20px 0;width:100%;touch-action:none}input[type=range]:focus{outline:0}input[type=range]::-webkit-slider-runnable-track{width:100%;height:4px;cursor:pointer;animate:.2s;background:#03a9f4;border-radius:25px}input[type=range]::-moz-range-track{width:100%;height:4px;cursor:pointer;animate:.2s;background:#03a9f4;border-radius:25px}input[type=range]::-ms-track{background:0 0;width:100%;height:4px;cursor:pointer;animate:.2s;background:0 0;border-color:transparent;color:transparent}input[type=range]::-ms-fill-lower{background:#03a9f4;border-radius:25px}input[type=range]::-ms-fill-upper{background:#03a9f4;border-radius:25px}input[type=range]::-webkit-slider-thumb{height:20px;width:20px;border-radius:50%;background:#fff;box-shadow:0 0 4px 0 #000;cursor:pointer;-webkit-appearance:none;margin-top:-8px}input[type=range]::-moz-range-thumb{height:20px;width:20px;border-radius:50%;background:#fff;box-shadow:0 0 4px 0 #000;cursor:pointer;border:none}input[type=range]::-ms-thumb{height:20px;width:20px;border-radius:50%;background:#fff;box-shadow:0 0 4px 0 #000;cursor:pointer;border:none}input[type=range]:focus::-webkit-slider-runnable-track{background:#03a9f4}input[type=range]:focus::-moz-range-track{background:#03a9f4}input[type=range]:focus::-ms-fill-lower{background:#03a9f4}input[type=range]:focus::-ms-fill-upper{background:#03a9f4}.range-wrap{display:flex;align-items:center}.slider-wrap{flex-grow:1;margin:0 15px;position:relative}.range-value{position:absolute;top:-50%}.range-value span{padding:0 3px 0 3px;height:19px;line-height:18px;text-align:center;background:#03a9f4;color:#fff;font-size:11px;display:block;position:absolute;left:50%;transform:translate(-50%,+80%);border-radius:6px}@-moz-document url-prefix(){.range-value span{transform:translate(-50%,+150%)}}.range-value span:before{content:"";position:absolute;width:0;height:0;border-top:10px solid #03a9f4;border-left:5px solid transparent;border-right:5px solid transparent;top:100%;left:50%;margin-left:-5px;margin-top:-1px;pointer-events:none}`]}};Y([b({type:String})],H.prototype,"value",2);Y([b({type:String})],H.prototype,"min",2);Y([b({type:String})],H.prototype,"max",2);Y([b({type:String})],H.prototype,"step",2);Y([b({type:String})],H.prototype,"name",2);H=Y([k("esp-range-slider")],H);var _n=Object.defineProperty,yn=Object.getOwnPropertyDescriptor,ge=(t,e,i,r)=>{for(var n=r>1?void 0:r?yn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&_n(e,i,n),n};let V=class extends E{constructor(){super(...arguments),this.entityId="text-scheduleset",this.days=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],this.globalEnabled=!0,this.dayEnabled=Array(7).fill(!0),this.values=Array(7).fill(0)}connectedCallback(){super.connectedCallback(),this.unsubscribe=Q.subscribe(()=>{const t=Q.get(this.entityId);t&&(this.entity=t,this.setFromString(this.entity.value))})}disconnectedCallback(){var t;(t=this.unsubscribe)==null||t.call(this),super.disconnectedCallback()}buildFlags(){let t=this.globalEnabled?1:0;return this.dayEnabled.forEach((e,i)=>{e&&(t|=1<Number(r.trim()));if(e.length!==8||e.some(isNaN))return;const i=e[0];this.globalEnabled=(i&1)===1;for(let r=0;r<7;r++)this.dayEnabled[r]=(i>>r+1&1)===1,this.values[r]=this.clampValue(e[r+1]);this.requestUpdate()}clampValue(t){return isNaN(t)||t<0?0:t>143?143:t}valueToHM(t){return{h:Math.floor(t/6),m:t%6*10}}pad2(t){return String(t).padStart(2,"0")}roundMinute(t){return t=Math.round(t/10)*10,t<0?0:t>50?50:t}hmToValue(t,e){return t<0||t>23?null:(e=this.roundMinute(e),t*6+e/10)}adjustMinute(t,e){const{h:i,m:r}=this.valueToHM(this.values[t]);let n=r+e;n>50&&(n=50),n<0&&(n=0);const s=this.hmToValue(i,n);s!==null&&(this.values[t]=s,this.emit())}adjustHour(t,e){const{h:i,m:r}=this.valueToHM(this.values[t]);let n=i+e;n<0&&(n=23),n>23&&(n=0);const s=this.hmToValue(n,r);s!==null&&(this.values[t]=s,this.emit())}render(){return this.entity?h`
${this.days.map((t,e)=>h`
:
`)}`:h`loading...`}};V.styles=S`:host{display:block}.row{display:grid;grid-template-columns:44px 1fr auto;align-items:center;gap:10px;padding:6px 0}.row.global{grid-template-columns:1fr auto;padding-bottom:10px;border-bottom:1px solid rgba(255,255,255,.1);margin-bottom:8px}label{font-size:.95rem;font-weight:700}.day-disabled label{opacity:.5}input[type=text]{background:#2b2b2b;color:#fff;border:1px solid #444;border-radius:8px;padding:6px 8px;font-size:.9rem;width:70px;text-align:center}input[type=text]:disabled{opacity:.4}.time{display:flex;align-items:center;gap:6px}.time input{width:36px;padding:6px 4px;text-align:center;font-size:.9rem}.hour,.minute{display:flex;flex-direction:column;align-items:center}.sep{opacity:.6}.arrow{border:none;background:0 0;color:#aaa;font-size:10px;line-height:10px;cursor:pointer}.arrow:hover{color:#6aa9ff}.arrow:disabled{opacity:.3;cursor:default}.switch{position:relative;width:44px;height:24px;border-radius:999px;background:#555;cursor:pointer;transition:background .2s ease}.switch::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff;transition:transform .2s ease}.switch.on{background:#3b82f6}.switch.on::after{transform:translateX(20px)}`;ge([b({type:String})],V.prototype,"entityId",2);ge([x()],V.prototype,"globalEnabled",2);ge([x()],V.prototype,"dayEnabled",2);ge([x()],V.prototype,"values",2);V=ge([k("esp-schedule")],V);var vn=Object.defineProperty,An=Object.getOwnPropertyDescriptor,X=(t,e,i,r)=>{for(var n=r>1?void 0:r?An(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&vn(e,i,n),n};let D=class extends E{constructor(){super(...arguments),this.value="",this.query="",this.filtered=[],this.selected="",this.entityId="text-timezone",this.zones={"Africa/Abidjan":"GMT0","Africa/Accra":"GMT0","Africa/Addis_Ababa":"EAT-3","Africa/Algiers":"CET-1","Africa/Asmara":"EAT-3","Africa/Bamako":"GMT0","Africa/Bangui":"WAT-1","Africa/Banjul":"GMT0","Africa/Bissau":"GMT0","Africa/Blantyre":"CAT-2","Africa/Brazzaville":"WAT-1","Africa/Bujumbura":"CAT-2","Africa/Cairo":"EET-2EEST,M4.5.5/0,M10.5.4/24","Africa/Casablanca":"<+01>-1","Africa/Ceuta":"CET-1CEST,M3.5.0,M10.5.0/3","Africa/Conakry":"GMT0","Africa/Dakar":"GMT0","Africa/Dar_es_Salaam":"EAT-3","Africa/Djibouti":"EAT-3","Africa/Douala":"WAT-1","Africa/El_Aaiun":"<+01>-1","Africa/Freetown":"GMT0","Africa/Gaborone":"CAT-2","Africa/Harare":"CAT-2","Africa/Johannesburg":"SAST-2","Africa/Juba":"CAT-2","Africa/Kampala":"EAT-3","Africa/Khartoum":"CAT-2","Africa/Kigali":"CAT-2","Africa/Kinshasa":"WAT-1","Africa/Lagos":"WAT-1","Africa/Libreville":"WAT-1","Africa/Lome":"GMT0","Africa/Luanda":"WAT-1","Africa/Lubumbashi":"CAT-2","Africa/Lusaka":"CAT-2","Africa/Malabo":"WAT-1","Africa/Maputo":"CAT-2","Africa/Maseru":"SAST-2","Africa/Mbabane":"SAST-2","Africa/Mogadishu":"EAT-3","Africa/Monrovia":"GMT0","Africa/Nairobi":"EAT-3","Africa/Ndjamena":"WAT-1","Africa/Niamey":"WAT-1","Africa/Nouakchott":"GMT0","Africa/Ouagadougou":"GMT0","Africa/Porto-Novo":"WAT-1","Africa/Sao_Tome":"GMT0","Africa/Tripoli":"EET-2","Africa/Tunis":"CET-1","Africa/Windhoek":"CAT-2","America/Adak":"HST10HDT,M3.2.0,M11.1.0","America/Anchorage":"AKST9AKDT,M3.2.0,M11.1.0","America/Anguilla":"AST4","America/Antigua":"AST4","America/Araguaina":"<-03>3","America/Argentina/Buenos_Aires":"<-03>3","America/Argentina/Catamarca":"<-03>3","America/Argentina/Cordoba":"<-03>3","America/Argentina/Jujuy":"<-03>3","America/Argentina/La_Rioja":"<-03>3","America/Argentina/Mendoza":"<-03>3","America/Argentina/Rio_Gallegos":"<-03>3","America/Argentina/Salta":"<-03>3","America/Argentina/San_Juan":"<-03>3","America/Argentina/San_Luis":"<-03>3","America/Argentina/Tucuman":"<-03>3","America/Argentina/Ushuaia":"<-03>3","America/Aruba":"AST4","America/Asuncion":"<-03>3","America/Atikokan":"EST5","America/Bahia":"<-03>3","America/Bahia_Banderas":"CST6","America/Barbados":"AST4","America/Belem":"<-03>3","America/Belize":"CST6","America/Blanc-Sablon":"AST4","America/Boa_Vista":"<-04>4","America/Bogota":"<-05>5","America/Boise":"MST7MDT,M3.2.0,M11.1.0","America/Cambridge_Bay":"MST7MDT,M3.2.0,M11.1.0","America/Campo_Grande":"<-04>4","America/Cancun":"EST5","America/Caracas":"<-04>4","America/Cayenne":"<-03>3","America/Cayman":"EST5","America/Chicago":"CST6CDT,M3.2.0,M11.1.0","America/Chihuahua":"CST6","America/Costa_Rica":"CST6","America/Creston":"MST7","America/Cuiaba":"<-04>4","America/Curacao":"AST4","America/Danmarkshavn":"GMT0","America/Dawson":"MST7","America/Dawson_Creek":"MST7","America/Denver":"MST7MDT,M3.2.0,M11.1.0","America/Detroit":"EST5EDT,M3.2.0,M11.1.0","America/Dominica":"AST4","America/Edmonton":"MST7MDT,M3.2.0,M11.1.0","America/Eirunepe":"<-05>5","America/El_Salvador":"CST6","America/Fort_Nelson":"MST7","America/Fortaleza":"<-03>3","America/Glace_Bay":"AST4ADT,M3.2.0,M11.1.0","America/Godthab":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0","America/Goose_Bay":"AST4ADT,M3.2.0,M11.1.0","America/Grand_Turk":"EST5EDT,M3.2.0,M11.1.0","America/Grenada":"AST4","America/Guadeloupe":"AST4","America/Guatemala":"CST6","America/Guayaquil":"<-05>5","America/Guyana":"<-04>4","America/Halifax":"AST4ADT,M3.2.0,M11.1.0","America/Havana":"CST5CDT,M3.2.0/0,M11.1.0/1","America/Hermosillo":"MST7","America/Indiana/Indianapolis":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Knox":"CST6CDT,M3.2.0,M11.1.0","America/Indiana/Marengo":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Petersburg":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Tell_City":"CST6CDT,M3.2.0,M11.1.0","America/Indiana/Vevay":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Vincennes":"EST5EDT,M3.2.0,M11.1.0","America/Indiana/Winamac":"EST5EDT,M3.2.0,M11.1.0","America/Inuvik":"MST7MDT,M3.2.0,M11.1.0","America/Iqaluit":"EST5EDT,M3.2.0,M11.1.0","America/Jamaica":"EST5","America/Juneau":"AKST9AKDT,M3.2.0,M11.1.0","America/Kentucky/Louisville":"EST5EDT,M3.2.0,M11.1.0","America/Kentucky/Monticello":"EST5EDT,M3.2.0,M11.1.0","America/Kralendijk":"AST4","America/La_Paz":"<-04>4","America/Lima":"<-05>5","America/Los_Angeles":"PST8PDT,M3.2.0,M11.1.0","America/Lower_Princes":"AST4","America/Maceio":"<-03>3","America/Managua":"CST6","America/Manaus":"<-04>4","America/Marigot":"AST4","America/Martinique":"AST4","America/Matamoros":"CST6CDT,M3.2.0,M11.1.0","America/Mazatlan":"MST7","America/Menominee":"CST6CDT,M3.2.0,M11.1.0","America/Merida":"CST6","America/Metlakatla":"AKST9AKDT,M3.2.0,M11.1.0","America/Mexico_City":"CST6","America/Miquelon":"<-03>3<-02>,M3.2.0,M11.1.0","America/Moncton":"AST4ADT,M3.2.0,M11.1.0","America/Monterrey":"CST6","America/Montevideo":"<-03>3","America/Montreal":"EST5EDT,M3.2.0,M11.1.0","America/Montserrat":"AST4","America/Nassau":"EST5EDT,M3.2.0,M11.1.0","America/New_York":"EST5EDT,M3.2.0,M11.1.0","America/Nipigon":"EST5EDT,M3.2.0,M11.1.0","America/Nome":"AKST9AKDT,M3.2.0,M11.1.0","America/Noronha":"<-02>2","America/North_Dakota/Beulah":"CST6CDT,M3.2.0,M11.1.0","America/North_Dakota/Center":"CST6CDT,M3.2.0,M11.1.0","America/North_Dakota/New_Salem":"CST6CDT,M3.2.0,M11.1.0","America/Nuuk":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0","America/Ojinaga":"CST6CDT,M3.2.0,M11.1.0","America/Panama":"EST5","America/Pangnirtung":"EST5EDT,M3.2.0,M11.1.0","America/Paramaribo":"<-03>3","America/Phoenix":"MST7","America/Port-au-Prince":"EST5EDT,M3.2.0,M11.1.0","America/Port_of_Spain":"AST4","America/Porto_Velho":"<-04>4","America/Puerto_Rico":"AST4","America/Punta_Arenas":"<-03>3","America/Rainy_River":"CST6CDT,M3.2.0,M11.1.0","America/Rankin_Inlet":"CST6CDT,M3.2.0,M11.1.0","America/Recife":"<-03>3","America/Regina":"CST6","America/Resolute":"CST6CDT,M3.2.0,M11.1.0","America/Rio_Branco":"<-05>5","America/Santarem":"<-03>3","America/Santiago":"<-04>4<-03>,M9.1.6/24,M4.1.6/24","America/Santo_Domingo":"AST4","America/Sao_Paulo":"<-03>3","America/Scoresbysund":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0","America/Sitka":"AKST9AKDT,M3.2.0,M11.1.0","America/St_Barthelemy":"AST4","America/St_Johns":"NST3:30NDT,M3.2.0,M11.1.0","America/St_Kitts":"AST4","America/St_Lucia":"AST4","America/St_Thomas":"AST4","America/St_Vincent":"AST4","America/Swift_Current":"CST6","America/Tegucigalpa":"CST6","America/Thule":"AST4ADT,M3.2.0,M11.1.0","America/Thunder_Bay":"EST5EDT,M3.2.0,M11.1.0","America/Tijuana":"PST8PDT,M3.2.0,M11.1.0","America/Toronto":"EST5EDT,M3.2.0,M11.1.0","America/Tortola":"AST4","America/Vancouver":"PST8PDT,M3.2.0,M11.1.0","America/Whitehorse":"MST7","America/Winnipeg":"CST6CDT,M3.2.0,M11.1.0","America/Yakutat":"AKST9AKDT,M3.2.0,M11.1.0","America/Yellowknife":"MST7MDT,M3.2.0,M11.1.0","Antarctica/Casey":"<+08>-8","Antarctica/Davis":"<+07>-7","Antarctica/DumontDUrville":"<+10>-10","Antarctica/Macquarie":"AEST-10AEDT,M10.1.0,M4.1.0/3","Antarctica/Mawson":"<+05>-5","Antarctica/McMurdo":"NZST-12NZDT,M9.5.0,M4.1.0/3","Antarctica/Palmer":"<-03>3","Antarctica/Rothera":"<-03>3","Antarctica/Syowa":"<+03>-3","Antarctica/Troll":"<+00>0<+02>-2,M3.5.0/1,M10.5.0/3","Antarctica/Vostok":"<+05>-5","Arctic/Longyearbyen":"CET-1CEST,M3.5.0,M10.5.0/3","Asia/Aden":"<+03>-3","Asia/Almaty":"<+05>-5","Asia/Amman":"<+03>-3","Asia/Anadyr":"<+12>-12","Asia/Aqtau":"<+05>-5","Asia/Aqtobe":"<+05>-5","Asia/Ashgabat":"<+05>-5","Asia/Atyrau":"<+05>-5","Asia/Baghdad":"<+03>-3","Asia/Bahrain":"<+03>-3","Asia/Baku":"<+04>-4","Asia/Bangkok":"<+07>-7","Asia/Barnaul":"<+07>-7","Asia/Beirut":"EET-2EEST,M3.5.0/0,M10.5.0/0","Asia/Bishkek":"<+06>-6","Asia/Brunei":"<+08>-8","Asia/Chita":"<+09>-9","Asia/Choibalsan":"<+08>-8","Asia/Colombo":"<+0530>-5:30","Asia/Damascus":"<+03>-3","Asia/Dhaka":"<+06>-6","Asia/Dili":"<+09>-9","Asia/Dubai":"<+04>-4","Asia/Dushanbe":"<+05>-5","Asia/Famagusta":"EET-2EEST,M3.5.0/3,M10.5.0/4","Asia/Gaza":"EET-2EEST,M3.4.4/50,M10.4.4/50","Asia/Hebron":"EET-2EEST,M3.4.4/50,M10.4.4/50","Asia/Ho_Chi_Minh":"<+07>-7","Asia/Hong_Kong":"HKT-8","Asia/Hovd":"<+07>-7","Asia/Irkutsk":"<+08>-8","Asia/Jakarta":"WIB-7","Asia/Jayapura":"WIT-9","Asia/Jerusalem":"IST-2IDT,M3.4.4/26,M10.5.0","Asia/Kabul":"<+0430>-4:30","Asia/Kamchatka":"<+12>-12","Asia/Karachi":"PKT-5","Asia/Kathmandu":"<+0545>-5:45","Asia/Khandyga":"<+09>-9","Asia/Kolkata":"IST-5:30","Asia/Krasnoyarsk":"<+07>-7","Asia/Kuala_Lumpur":"<+08>-8","Asia/Kuching":"<+08>-8","Asia/Kuwait":"<+03>-3","Asia/Macau":"CST-8","Asia/Magadan":"<+11>-11","Asia/Makassar":"WITA-8","Asia/Manila":"PST-8","Asia/Muscat":"<+04>-4","Asia/Nicosia":"EET-2EEST,M3.5.0/3,M10.5.0/4","Asia/Novokuznetsk":"<+07>-7","Asia/Novosibirsk":"<+07>-7","Asia/Omsk":"<+06>-6","Asia/Oral":"<+05>-5","Asia/Phnom_Penh":"<+07>-7","Asia/Pontianak":"WIB-7","Asia/Pyongyang":"KST-9","Asia/Qatar":"<+03>-3","Asia/Qyzylorda":"<+05>-5","Asia/Riyadh":"<+03>-3","Asia/Sakhalin":"<+11>-11","Asia/Samarkand":"<+05>-5","Asia/Seoul":"KST-9","Asia/Shanghai":"CST-8","Asia/Singapore":"<+08>-8","Asia/Srednekolymsk":"<+11>-11","Asia/Taipei":"CST-8","Asia/Tashkent":"<+05>-5","Asia/Tbilisi":"<+04>-4","Asia/Tehran":"<+0330>-3:30","Asia/Thimphu":"<+06>-6","Asia/Tokyo":"JST-9","Asia/Tomsk":"<+07>-7","Asia/Ulaanbaatar":"<+08>-8","Asia/Urumqi":"<+06>-6","Asia/Ust-Nera":"<+10>-10","Asia/Vientiane":"<+07>-7","Asia/Vladivostok":"<+10>-10","Asia/Yakutsk":"<+09>-9","Asia/Yangon":"<+0630>-6:30","Asia/Yekaterinburg":"<+05>-5","Asia/Yerevan":"<+04>-4","Atlantic/Azores":"<-01>1<+00>,M3.5.0/0,M10.5.0/1","Atlantic/Bermuda":"AST4ADT,M3.2.0,M11.1.0","Atlantic/Canary":"WET0WEST,M3.5.0/1,M10.5.0","Atlantic/Cape_Verde":"<-01>1","Atlantic/Faroe":"WET0WEST,M3.5.0/1,M10.5.0","Atlantic/Madeira":"WET0WEST,M3.5.0/1,M10.5.0","Atlantic/Reykjavik":"GMT0","Atlantic/South_Georgia":"<-02>2","Atlantic/St_Helena":"GMT0","Atlantic/Stanley":"<-03>3","Australia/Adelaide":"ACST-9:30ACDT,M10.1.0,M4.1.0/3","Australia/Brisbane":"AEST-10","Australia/Broken_Hill":"ACST-9:30ACDT,M10.1.0,M4.1.0/3","Australia/Currie":"AEST-10AEDT,M10.1.0,M4.1.0/3","Australia/Darwin":"ACST-9:30","Australia/Eucla":"<+0845>-8:45","Australia/Hobart":"AEST-10AEDT,M10.1.0,M4.1.0/3","Australia/Lindeman":"AEST-10","Australia/Lord_Howe":"<+1030>-10:30<+11>-11,M10.1.0,M4.1.0","Australia/Melbourne":"AEST-10AEDT,M10.1.0,M4.1.0/3","Australia/Perth":"AWST-8","Australia/Sydney":"AEST-10AEDT,M10.1.0,M4.1.0/3","Etc/GMT":"GMT0","Etc/GMT+0":"GMT0","Etc/GMT+1":"<-01>1","Etc/GMT+10":"<-10>10","Etc/GMT+11":"<-11>11","Etc/GMT+12":"<-12>12","Etc/GMT+2":"<-02>2","Etc/GMT+3":"<-03>3","Etc/GMT+4":"<-04>4","Etc/GMT+5":"<-05>5","Etc/GMT+6":"<-06>6","Etc/GMT+7":"<-07>7","Etc/GMT+8":"<-08>8","Etc/GMT+9":"<-09>9","Etc/GMT-0":"GMT0","Etc/GMT-1":"<+01>-1","Etc/GMT-10":"<+10>-10","Etc/GMT-11":"<+11>-11","Etc/GMT-12":"<+12>-12","Etc/GMT-13":"<+13>-13","Etc/GMT-14":"<+14>-14","Etc/GMT-2":"<+02>-2","Etc/GMT-3":"<+03>-3","Etc/GMT-4":"<+04>-4","Etc/GMT-5":"<+05>-5","Etc/GMT-6":"<+06>-6","Etc/GMT-7":"<+07>-7","Etc/GMT-8":"<+08>-8","Etc/GMT-9":"<+09>-9","Etc/GMT0":"GMT0","Etc/Greenwich":"GMT0","Etc/UCT":"UTC0","Etc/UTC":"UTC0","Etc/Universal":"UTC0","Etc/Zulu":"UTC0","Europe/Amsterdam":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Andorra":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Astrakhan":"<+04>-4","Europe/Athens":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Belgrade":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Berlin":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Bratislava":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Brussels":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Bucharest":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Budapest":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Busingen":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Chisinau":"EET-2EEST,M3.5.0,M10.5.0/3","Europe/Copenhagen":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Dublin":"IST-1GMT0,M10.5.0,M3.5.0/1","Europe/Gibraltar":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Guernsey":"GMT0BST,M3.5.0/1,M10.5.0","Europe/Helsinki":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Isle_of_Man":"GMT0BST,M3.5.0/1,M10.5.0","Europe/Istanbul":"<+03>-3","Europe/Jersey":"GMT0BST,M3.5.0/1,M10.5.0","Europe/Kaliningrad":"EET-2","Europe/Kiev":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Kirov":"MSK-3","Europe/Lisbon":"WET0WEST,M3.5.0/1,M10.5.0","Europe/Ljubljana":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/London":"GMT0BST,M3.5.0/1,M10.5.0","Europe/Luxembourg":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Madrid":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Malta":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Mariehamn":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Minsk":"<+03>-3","Europe/Monaco":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Moscow":"MSK-3","Europe/Oslo":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Paris":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Podgorica":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Prague":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Riga":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Rome":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Samara":"<+04>-4","Europe/San_Marino":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Sarajevo":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Saratov":"<+04>-4","Europe/Simferopol":"MSK-3","Europe/Skopje":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Sofia":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Stockholm":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Tallinn":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Tirane":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Ulyanovsk":"<+04>-4","Europe/Uzhgorod":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Vaduz":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Vatican":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Vienna":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Vilnius":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Volgograd":"MSK-3","Europe/Warsaw":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Zagreb":"CET-1CEST,M3.5.0,M10.5.0/3","Europe/Zaporozhye":"EET-2EEST,M3.5.0/3,M10.5.0/4","Europe/Zurich":"CET-1CEST,M3.5.0,M10.5.0/3","Indian/Antananarivo":"EAT-3","Indian/Chagos":"<+06>-6","Indian/Christmas":"<+07>-7","Indian/Cocos":"<+0630>-6:30","Indian/Comoro":"EAT-3","Indian/Kerguelen":"<+05>-5","Indian/Mahe":"<+04>-4","Indian/Maldives":"<+05>-5","Indian/Mauritius":"<+04>-4","Indian/Mayotte":"EAT-3","Indian/Reunion":"<+04>-4","Pacific/Apia":"<+13>-13","Pacific/Auckland":"NZST-12NZDT,M9.5.0,M4.1.0/3","Pacific/Bougainville":"<+11>-11","Pacific/Chatham":"<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45","Pacific/Chuuk":"<+10>-10","Pacific/Easter":"<-06>6<-05>,M9.1.6/22,M4.1.6/22","Pacific/Efate":"<+11>-11","Pacific/Enderbury":"<+13>-13","Pacific/Fakaofo":"<+13>-13","Pacific/Fiji":"<+12>-12","Pacific/Funafuti":"<+12>-12","Pacific/Galapagos":"<-06>6","Pacific/Gambier":"<-09>9","Pacific/Guadalcanal":"<+11>-11","Pacific/Guam":"ChST-10","Pacific/Honolulu":"HST10","Pacific/Kiritimati":"<+14>-14","Pacific/Kosrae":"<+11>-11","Pacific/Kwajalein":"<+12>-12","Pacific/Majuro":"<+12>-12","Pacific/Marquesas":"<-0930>9:30","Pacific/Midway":"SST11","Pacific/Nauru":"<+12>-12","Pacific/Niue":"<-11>11","Pacific/Norfolk":"<+11>-11<+12>,M10.1.0,M4.1.0/3","Pacific/Noumea":"<+11>-11","Pacific/Pago_Pago":"SST11","Pacific/Palau":"<+09>-9","Pacific/Pitcairn":"<-08>8","Pacific/Pohnpei":"<+11>-11","Pacific/Port_Moresby":"<+10>-10","Pacific/Rarotonga":"<-10>10","Pacific/Saipan":"ChST-10","Pacific/Tahiti":"<-10>10","Pacific/Tarawa":"<+12>-12","Pacific/Tongatapu":"<+13>-13","Pacific/Wake":"<+12>-12","Pacific/Wallis":"<+12>-12"}}connectedCallback(){super.connectedCallback(),this.unsubscribe=Q.subscribe(t=>{t.unique_id===this.entityId&&(this.entity=t,this.requestUpdate())})}disconnectedCallback(){var t;(t=this.unsubscribe)==null||t.call(this),super.disconnectedCallback()}firstUpdated(){this.filtered=Object.keys(this.zones),this.value&&this.select(this.value)}updated(){this.value&&this.value!==this.selected&&this.select(this.value)}filter(){const t=this.query.toLowerCase();this.filtered=Object.keys(this.zones).filter(e=>e.toLowerCase().includes(t))}select(t){this.selected=t,this.query=t,this.dispatchEvent(new CustomEvent("timezone-changed",{detail:t})),this.entity&&ui(this.entity,this.zones[t])}render(){return this.entity?h`In case the "NBS Time" is not the correct timezone, select your timezone here.
${this.filtered.slice(0,50).map(t=>h`
${t} ${this.zones[t]}
`)}
`:h`loading...`}};D.styles=S`.wrap{margin-top:1rem;display:flex;flex-direction:column;gap:8px}input{padding:10px;border-radius:8px;border:1px solid #444}.list{max-height:240px;overflow:auto;border:1px solid #444;border-radius:8px}.item{display:flex;justify-content:space-between;padding:8px;cursor:pointer}.item:hover{background:rgba(127,127,127,.7)}.item.sel{background:rgba(82,82,82,.7)}.code{opacity:.6;font-size:.8rem}`;X([b({type:String})],D.prototype,"value",2);X([x()],D.prototype,"query",2);X([x()],D.prototype,"filtered",2);X([x()],D.prototype,"selected",2);X([b({type:String})],D.prototype,"entityId",2);D=X([k("timezone-selector")],D);const w={house_clean:"button-house_clean",spot_clean:"button-spot_clean",spot_clean__height___width_:"button-spot_clean__height___width_",stop_cleaning:"button-stop_cleaning",pause_cleaning:"button-pause_cleaning",resume_cleaning:"button-resume_cleaning",locate_robot:"button-locate_robot",update_status:"button-update_status",clear_errors:"button-clear_errors",shutdown:"button-shutdown",powercycle:"button-powercycle",reboot_esp:"button-reboot_esp"},v={...w,send_to_base:"button-send_to_base",start_manual_cleaning:"button-start_manual_cleaning",manual_drive_forward_up:"button-manual_drive_forward_up",manual_drive_backwards_up:"button-manual_drive_backwards_up",manual_drive_turn_left_up:"button-manual_drive_turn_left_up",manual_drive_turn_right_up:"button-manual_drive_turn_right_up",manual_drive_arc_left_up:"button-manual_drive_arc_left_up",manual_drive_arc_right_up:"button-manual_drive_arc_right_up",manual_drive_forward_down:"button-manual_drive_forward_down",manual_drive_backwards_down:"button-manual_drive_backwards_down",manual_drive_turn_left_down:"button-manual_drive_turn_left_down",manual_drive_turn_right_down:"button-manual_drive_turn_right_down",manual_drive_arc_left_down:"button-manual_drive_arc_left_down",manual_drive_arc_right_down:"button-manual_drive_arc_right_down",manual_drive_button_timeout:"button-manual_drive_button_timeout"},Tn={...w,send_to_start:"button-send_to_start"},fi={logger:"select-logger_select"},wn={...fi,navigation_mode:"select-navigation_mode"},Te={usb_connected:"binary_sensor-usb_connected",battery_over_temp:"binary_sensor-battery_over_temp",charging_active:"binary_sensor-charging_active",charging_enabled:"binary_sensor-charging_enabled",confident_on_fuel:"binary_sensor-confident_on_fuel",on_reserved_fuel:"binary_sensor-on_reserved_fuel",empty_fuel:"binary_sensor-empty_fuel",battery_failure:"binary_sensor-battery_failure",ext_power_present:"binary_sensor-ext_power_present",thermistor_present:"binary_sensor-thermistor_present"},ie={fuel_percent:"sensor-fuel_percent",battery_temp_c_avg:"sensor-battery_temp_c_avg",battery_voltage_v:"sensor-battery_voltage_v",external_voltage_v:"sensor-external_voltage_v",charger_mah:"sensor-charger_mah",discharge_mah:"sensor-discharge_mah",filter_change_time:"sensor-filter_change_time",brush_change_time:"sensor-brush_change_time",dirt_bin_alert_reminder:"sensor-dirt_bin_alert_reminder",current_dirt_bin_runtime:"sensor-current_dirt_bin_runtime",number_of_full_dust_bin_cleanings:"sensor-number_of_full_dust_bin_cleanings",battery_cycles:"sensor-battery_cycles",last_cleaning_duration:"sensor-last_cleaning_duration"},Lt={spot_clean_width:"number-spot_clean_width",spot_clean_height:"number-spot_clean_height"},Nt={timezone:"text-timezone",schedule:"text-scheduleset"},$={test_mode:"switch-test_mode",play_extra_sounds:"switch-play_extra_sounds",click_sounds:"switch-click_sounds",led:"switch-led",wall_enable:"switch-wall_enable",eco_mode:"switch-eco_mode",intenseclean:"switch-intenseclean",wifi:"switch-wifi",melody_sounds:"switch-melody_sounds",warning_sounds:"switch-warning_sounds",bin_full_detect:"switch-bin_full_detect"},We={...$,stealthled:"switch-stealthled",autoshutdown:"switch-autoshutdown",robot_schedule:"switch-robot_schedule"},C={last_cleaning_time:"text_sensor-last_cleaning_time",last_cleaning_type:"text_sensor-last_cleaning_type",robot_error:"text_sensor-robot_error",robot_alert:"text_sensor-robot_alert",serial_number:"text_sensor-serial_number",model:"text_sensor-model",software:"text_sensor-software",ui_state:"text_sensor-ui_state",nbs_time:"text_sensor-nbs_time"};var En=Object.defineProperty,Mn=Object.getOwnPropertyDescriptor,$n=(t,e,i,r)=>{for(var n=r>1?void 0:r?Mn(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&En(e,i,n),n};let it=class extends E{render(){return h`
You need to turn manual cleaning on by pressing "Start", once the vacuum is in manual cleaning mode you can hold down the different actions! Timeout will stop the current action.
`}};it.styles=S`.manual-driving{display:flex;justify-content:center;align-items:center;flex-direction:column}.manual-driving span{width:75%;padding:1rem;margin:1.5rem 0;background-color:rgba(127,127,127,.3);border-radius:.5rem}`;it=$n([k("manual-driving")],it);const mi=Object.freeze({left:0,top:0,width:16,height:16}),ke=Object.freeze({rotate:0,vFlip:!1,hFlip:!1}),be=Object.freeze({...mi,...ke}),nt=Object.freeze({...be,body:"",hidden:!1}),Sn=Object.freeze({width:null,height:null}),gi=Object.freeze({...Sn,...ke});function xn(t,e=0){const i=t.replace(/^-?[0-9.]*/,"");function r(n){for(;n<0;)n+=4;return n%4}if(i===""){const n=parseInt(t);return isNaN(n)?0:r(n)}else if(i!==t){let n=0;switch(i){case"%":n=25;break;case"deg":n=90}if(n){let s=parseFloat(t.slice(0,t.length-i.length));return isNaN(s)?0:(s=s/n,s%1===0?r(s):0)}}return e}const Cn=/[\s,]+/;function kn(t,e){e.split(Cn).forEach(i=>{switch(i.trim()){case"horizontal":t.hFlip=!0;break;case"vertical":t.vFlip=!0;break}})}const bi={...gi,preserveAspectRatio:""};function jt(t){const e={...bi},i=(r,n)=>t.getAttribute(r)||n;return e.width=i("width",null),e.height=i("height",null),e.rotate=xn(i("rotate","")),kn(e,i("flip","")),e.preserveAspectRatio=i("preserveAspectRatio",i("preserveaspectratio","")),e}function Pn(t,e){for(const i in bi)if(t[i]!==e[i])return!0;return!1}const re=/^[a-z0-9]+(-[a-z0-9]+)*$/,_e=(t,e,i,r="")=>{const n=t.split(":");if(t.slice(0,1)==="@"){if(n.length<2||n.length>3)return null;r=n.shift().slice(1)}if(n.length>3||!n.length)return null;if(n.length>1){const c=n.pop(),a=n.pop(),l={provider:n.length>0?n[0]:r,prefix:a,name:c};return e&&!$e(l)?null:l}const s=n[0],o=s.split("-");if(o.length>1){const c={provider:r,prefix:o.shift(),name:o.join("-")};return e&&!$e(c)?null:c}if(i&&r===""){const c={provider:r,prefix:"",name:s};return e&&!$e(c,i)?null:c}return null},$e=(t,e)=>t?!!((t.provider===""||t.provider.match(re))&&(e&&t.prefix===""||t.prefix.match(re))&&t.name.match(re)):!1;function In(t,e){const i={};!t.hFlip!=!e.hFlip&&(i.hFlip=!0),!t.vFlip!=!e.vFlip&&(i.vFlip=!0);const r=((t.rotate||0)+(e.rotate||0))%4;return r&&(i.rotate=r),i}function Bt(t,e){const i=In(t,e);for(const r in nt)r in ke?r in t&&!(r in i)&&(i[r]=ke[r]):r in e?i[r]=e[r]:r in t&&(i[r]=t[r]);return i}function Dn(t,e){const i=t.icons,r=t.aliases||Object.create(null),n=Object.create(null);function s(o){if(i[o])return n[o]=[];if(!(o in n)){n[o]=null;const c=r[o]&&r[o].parent,a=c&&s(c);a&&(n[o]=[c].concat(a))}return n[o]}return(e||Object.keys(i).concat(Object.keys(r))).forEach(s),n}function On(t,e,i){const r=t.icons,n=t.aliases||Object.create(null);let s={};function o(c){s=Bt(r[c]||n[c],s)}return o(e),i.forEach(o),Bt(t,s)}function _i(t,e){const i=[];if(typeof t!="object"||typeof t.icons!="object")return i;t.not_found instanceof Array&&t.not_found.forEach(n=>{e(n,null),i.push(n)});const r=Dn(t);for(const n in r){const s=r[n];s&&(e(n,On(t,n,s)),i.push(n))}return i}const Ln={provider:"",aliases:{},not_found:{},...mi};function Je(t,e){for(const i in e)if(i in t&&typeof t[i]!=typeof e[i])return!1;return!0}function yi(t){if(typeof t!="object"||t===null)return null;const e=t;if(typeof e.prefix!="string"||!t.icons||typeof t.icons!="object"||!Je(t,Ln))return null;const i=e.icons;for(const n in i){const s=i[n];if(!n.match(re)||typeof s.body!="string"||!Je(s,nt))return null}const r=e.aliases||Object.create(null);for(const n in r){const s=r[n],o=s.parent;if(!n.match(re)||typeof o!="string"||!i[o]&&!r[o]||!Je(s,nt))return null}return e}const Pe=Object.create(null);function Nn(t,e){return{provider:t,prefix:e,icons:Object.create(null),missing:new Set}}function O(t,e){const i=Pe[t]||(Pe[t]=Object.create(null));return i[e]||(i[e]=Nn(t,e))}function ft(t,e){return yi(e)?_i(e,(i,r)=>{r?t.icons[i]=r:t.missing.add(i)}):[]}function jn(t,e,i){try{if(typeof i.body=="string")return t.icons[e]={...i},!0}catch{}return!1}function Bn(t,e){let i=[];return(typeof t=="string"?[t]:Object.keys(Pe)).forEach(n=>{(typeof n=="string"&&typeof e=="string"?[e]:Object.keys(Pe[n]||{})).forEach(o=>{const c=O(n,o);i=i.concat(Object.keys(c.icons).map(a=>(n!==""?"@"+n+":":"")+o+":"+a))})}),i}let de=!1;function vi(t){return typeof t=="boolean"&&(de=t),de}function he(t){const e=typeof t=="string"?_e(t,!0,de):t;if(e){const i=O(e.provider,e.prefix),r=e.name;return i.icons[r]||(i.missing.has(r)?null:void 0)}}function Ai(t,e){const i=_e(t,!0,de);if(!i)return!1;const r=O(i.provider,i.prefix);return jn(r,i.name,e)}function Rt(t,e){if(typeof t!="object")return!1;if(typeof e!="string"&&(e=t.provider||""),de&&!e&&!t.prefix){let n=!1;return yi(t)&&(t.prefix="",_i(t,(s,o)=>{o&&Ai(s,o)&&(n=!0)})),n}const i=t.prefix;if(!$e({provider:e,prefix:i,name:"a"}))return!1;const r=O(e,i);return!!ft(r,t)}function Rn(t){return!!he(t)}function Un(t){const e=he(t);return e?{...be,...e}:null}function Gn(t){const e={loaded:[],missing:[],pending:[]},i=Object.create(null);t.sort((n,s)=>n.provider!==s.provider?n.provider.localeCompare(s.provider):n.prefix!==s.prefix?n.prefix.localeCompare(s.prefix):n.name.localeCompare(s.name));let r={provider:"",prefix:"",name:""};return t.forEach(n=>{if(r.name===n.name&&r.prefix===n.prefix&&r.provider===n.provider)return;r=n;const s=n.provider,o=n.prefix,c=n.name,a=i[s]||(i[s]=Object.create(null)),l=a[o]||(a[o]=O(s,o));let u;c in l.icons?u=e.loaded:o===""||l.missing.has(c)?u=e.missing:u=e.pending;const d={provider:s,prefix:o,name:c};u.push(d)}),e}function Ti(t,e){t.forEach(i=>{const r=i.loaderCallbacks;r&&(i.loaderCallbacks=r.filter(n=>n.id!==e))})}function Hn(t){t.pendingCallbacksFlag||(t.pendingCallbacksFlag=!0,setTimeout(()=>{t.pendingCallbacksFlag=!1;const e=t.loaderCallbacks?t.loaderCallbacks.slice(0):[];if(!e.length)return;let i=!1;const r=t.provider,n=t.prefix;e.forEach(s=>{const o=s.icons,c=o.pending.length;o.pending=o.pending.filter(a=>{if(a.prefix!==n)return!0;const l=a.name;if(t.icons[l])o.loaded.push({provider:r,prefix:n,name:l});else if(t.missing.has(l))o.missing.push({provider:r,prefix:n,name:l});else return i=!0,!0;return!1}),o.pending.length!==c&&(i||Ti([t],s.id),s.callback(o.loaded.slice(0),o.missing.slice(0),o.pending.slice(0),s.abort))})}))}let Vn=0;function qn(t,e,i){const r=Vn++,n=Ti.bind(null,i,r);if(!e.pending.length)return n;const s={id:r,icons:e,callback:t,abort:n};return i.forEach(o=>{(o.loaderCallbacks||(o.loaderCallbacks=[])).push(s)}),n}const rt=Object.create(null);function Ut(t,e){rt[t]=e}function st(t){return rt[t]||rt[""]}function zn(t,e=!0,i=!1){const r=[];return t.forEach(n=>{const s=typeof n=="string"?_e(n,e,i):n;s&&r.push(s)}),r}var Fn={resources:[],index:0,timeout:2e3,rotate:750,random:!1,dataAfterTimeout:!1};function Kn(t,e,i,r){const n=t.resources.length,s=t.random?Math.floor(Math.random()*n):t.index;let o;if(t.random){let m=t.resources.slice(0);for(o=[];m.length>1;){const M=Math.floor(Math.random()*m.length);o.push(m[M]),m=m.slice(0,M).concat(m.slice(M+1))}o=o.concat(m)}else o=t.resources.slice(s).concat(t.resources.slice(0,s));const c=Date.now();let a="pending",l=0,u,d=null,p=[],f=[];typeof r=="function"&&f.push(r);function y(){d&&(clearTimeout(d),d=null)}function _(){a==="pending"&&(a="aborted"),y(),p.forEach(m=>{m.status==="pending"&&(m.status="aborted")}),p=[]}function Be(m,M){M&&(f=[]),typeof m=="function"&&f.push(m)}function ye(){return{startTime:c,payload:e,status:a,queriesSent:l,queriesPending:p.length,subscribe:Be,abort:_}}function A(){a="failed",f.forEach(m=>{m(void 0,u)})}function T(){p.forEach(m=>{m.status==="pending"&&(m.status="aborted")}),p=[]}function ji(m,M,ee){const ve=M!=="success";switch(p=p.filter(N=>N!==m),a){case"pending":break;case"failed":if(ve||!t.dataAfterTimeout)return;break;default:return}if(M==="abort"){u=ee,A();return}if(ve){u=ee,p.length||(o.length?Re():A());return}if(y(),T(),!t.random){const N=t.resources.indexOf(m.resource);N!==-1&&N!==t.index&&(t.index=N)}a="completed",f.forEach(N=>{N(ee)})}function Re(){if(a!=="pending")return;y();const m=o.shift();if(m===void 0){if(p.length){d=setTimeout(()=>{y(),a==="pending"&&(T(),A())},t.timeout);return}A();return}const M={status:"pending",resource:m,callback:(ee,ve)=>{ji(M,ee,ve)}};p.push(M),l++,d=setTimeout(Re,t.rotate),i(m,e,M.callback)}return setTimeout(Re),ye}function wi(t){const e={...Fn,...t};let i=[];function r(){i=i.filter(c=>c().status==="pending")}function n(c,a,l){const u=Kn(e,c,a,(d,p)=>{r(),l&&l(d,p)});return i.push(u),u}function s(c){return i.find(a=>c(a))||null}return{query:n,find:s,setIndex:c=>{e.index=c},getIndex:()=>e.index,cleanup:r}}function mt(t){let e;if(typeof t.resources=="string")e=[t.resources];else if(e=t.resources,!(e instanceof Array)||!e.length)return null;return{resources:e,path:t.path||"/",maxURL:t.maxURL||500,rotate:t.rotate||750,timeout:t.timeout||5e3,random:t.random===!0,index:t.index||0,dataAfterTimeout:t.dataAfterTimeout!==!1}}const Ne=Object.create(null),ne=["https://api.simplesvg.com","https://api.unisvg.com"],Se=[];for(;ne.length>0;)ne.length===1||Math.random()>.5?Se.push(ne.shift()):Se.push(ne.pop());Ne[""]=mt({resources:["https://api.iconify.design"].concat(Se)});function Gt(t,e){const i=mt(e);return i===null?!1:(Ne[t]=i,!0)}function je(t){return Ne[t]}function Wn(){return Object.keys(Ne)}function Ht(){}const Qe=Object.create(null);function Jn(t){if(!Qe[t]){const e=je(t);if(!e)return;const i=wi(e),r={config:e,redundancy:i};Qe[t]=r}return Qe[t]}function Ei(t,e,i){let r,n;if(typeof t=="string"){const s=st(t);if(!s)return i(void 0,424),Ht;n=s.send;const o=Jn(t);o&&(r=o.redundancy)}else{const s=mt(t);if(s){r=wi(s);const o=t.resources?t.resources[0]:"",c=st(o);c&&(n=c.send)}}return!r||!n?(i(void 0,424),Ht):r.query(e,n,i)().abort}const Vt="iconify2",pe="iconify",Mi=pe+"-count",qt=pe+"-version",$i=36e5,Qn=168;function ot(t,e){try{return t.getItem(e)}catch{}}function gt(t,e,i){try{return t.setItem(e,i),!0}catch{}}function zt(t,e){try{t.removeItem(e)}catch{}}function at(t,e){return gt(t,Mi,e.toString())}function ct(t){return parseInt(ot(t,Mi))||0}const R={local:!0,session:!0},Si={local:new Set,session:new Set};let bt=!1;function Zn(t){bt=t}let we=typeof window=="undefined"?{}:window;function xi(t){const e=t+"Storage";try{if(we&&we[e]&&typeof we[e].length=="number")return we[e]}catch{}R[t]=!1}function Ci(t,e){const i=xi(t);if(!i)return;const r=ot(i,qt);if(r!==Vt){if(r){const c=ct(i);for(let a=0;a{const a=pe+c.toString(),l=ot(i,a);if(typeof l=="string"){try{const u=JSON.parse(l);if(typeof u=="object"&&typeof u.cached=="number"&&u.cached>n&&typeof u.provider=="string"&&typeof u.data=="object"&&typeof u.data.prefix=="string"&&e(u,c))return!0}catch{}zt(i,a)}};let o=ct(i);for(let c=o-1;c>=0;c--)s(c)||(c===o-1?(o--,at(i,o)):Si[t].add(c))}function ki(){if(!bt){Zn(!0);for(const t in R)Ci(t,e=>{const i=e.data,r=e.provider,n=i.prefix,s=O(r,n);if(!ft(s,i).length)return!1;const o=i.lastModified||-1;return s.lastModifiedCached=s.lastModifiedCached?Math.min(s.lastModifiedCached,o):o,!0})}}function Yn(t,e){const i=t.lastModifiedCached;if(i&&i>=e)return i===e;if(t.lastModifiedCached=e,i)for(const r in R)Ci(r,n=>{const s=n.data;return n.provider!==t.provider||s.prefix!==t.prefix||s.lastModified===e});return!0}function Xn(t,e){bt||ki();function i(r){let n;if(!R[r]||!(n=xi(r)))return;const s=Si[r];let o;if(s.size)s.delete(o=Array.from(s).shift());else if(o=ct(n),!at(n,o+1))return;const c={cached:Math.floor(Date.now()/$i),provider:t.provider,data:e};return gt(n,pe+o.toString(),JSON.stringify(c))}e.lastModified&&!Yn(t,e.lastModified)||!Object.keys(e.icons).length||(e.not_found&&(e=Object.assign({},e),delete e.not_found),i("local")||i("session"))}function Ft(){}function er(t){t.iconsLoaderFlag||(t.iconsLoaderFlag=!0,setTimeout(()=>{t.iconsLoaderFlag=!1,Hn(t)}))}function tr(t,e){t.iconsToLoad?t.iconsToLoad=t.iconsToLoad.concat(e).sort():t.iconsToLoad=e,t.iconsQueueFlag||(t.iconsQueueFlag=!0,setTimeout(()=>{t.iconsQueueFlag=!1;const{provider:i,prefix:r}=t,n=t.iconsToLoad;delete t.iconsToLoad;let s;if(!n||!(s=st(i)))return;s.prepare(i,r,n).forEach(c=>{Ei(i,c,a=>{if(typeof a!="object")c.icons.forEach(l=>{t.missing.add(l)});else try{const l=ft(t,a);if(!l.length)return;const u=t.pendingIcons;u&&l.forEach(d=>{u.delete(d)}),Xn(t,a)}catch(l){console.error(l)}er(t)})})}))}const _t=(t,e)=>{const i=zn(t,!0,vi()),r=Gn(i);if(!r.pending.length){let a=!0;return e&&setTimeout(()=>{a&&e(r.loaded,r.missing,r.pending,Ft)}),()=>{a=!1}}const n=Object.create(null),s=[];let o,c;return r.pending.forEach(a=>{const{provider:l,prefix:u}=a;if(u===c&&l===o)return;o=l,c=u,s.push(O(l,u));const d=n[l]||(n[l]=Object.create(null));d[u]||(d[u]=[])}),r.pending.forEach(a=>{const{provider:l,prefix:u,name:d}=a,p=O(l,u),f=p.pendingIcons||(p.pendingIcons=new Set);f.has(d)||(f.add(d),n[l][u].push(d))}),s.forEach(a=>{const{provider:l,prefix:u}=a;n[l][u].length&&tr(a,n[l][u])}),e?qn(e,r,s):Ft},ir=t=>new Promise((e,i)=>{const r=typeof t=="string"?_e(t,!0):t;if(!r){i(t);return}_t([r||t],n=>{if(n.length&&r){const s=he(r);if(s){e({...be,...s});return}}i(t)})});function nr(t){try{const e=typeof t=="string"?JSON.parse(t):t;if(typeof e.body=="string")return{...e}}catch{}}function rr(t,e){const i=typeof t=="string"?_e(t,!0,!0):null;if(!i){const s=nr(t);return{value:t,data:s}}const r=he(i);if(r!==void 0||!i.prefix)return{value:t,name:i,data:r};const n=_t([i],()=>e(t,i,he(i)));return{value:t,name:i,loading:n}}function Ze(t){return t.hasAttribute("inline")}let Pi=!1;try{Pi=navigator.vendor.indexOf("Apple")===0}catch{}function sr(t,e){switch(e){case"svg":case"bg":case"mask":return e}return e!=="style"&&(Pi||t.indexOf("t==="unset"||t==="undefined"||t==="none";function Ii(t,e){const i={...be,...t},r={...gi,...e},n={left:i.left,top:i.top,width:i.width,height:i.height};let s=i.body;[i,r].forEach(y=>{const _=[],Be=y.hFlip,ye=y.vFlip;let A=y.rotate;Be?ye?A+=2:(_.push("translate("+(n.width+n.left).toString()+" "+(0-n.top).toString()+")"),_.push("scale(-1 1)"),n.top=n.left=0):ye&&(_.push("translate("+(0-n.left).toString()+" "+(n.height+n.top).toString()+")"),_.push("scale(1 -1)"),n.top=n.left=0);let T;switch(A<0&&(A-=Math.floor(A/4)*4),A=A%4,A){case 1:T=n.height/2+n.top,_.unshift("rotate(90 "+T.toString()+" "+T.toString()+")");break;case 2:_.unshift("rotate(180 "+(n.width/2+n.left).toString()+" "+(n.height/2+n.top).toString()+")");break;case 3:T=n.width/2+n.left,_.unshift("rotate(-90 "+T.toString()+" "+T.toString()+")");break}A%2===1&&(n.left!==n.top&&(T=n.left,n.left=n.top,n.top=T),n.width!==n.height&&(T=n.width,n.width=n.height,n.height=T)),_.length&&(s=''+s+"")});const o=r.width,c=r.height,a=n.width,l=n.height;let u,d;o===null?(d=c===null?"1em":c==="auto"?l:c,u=lt(d,a/l)):(u=o==="auto"?a:o,d=c===null?lt(u,l/a):c==="auto"?l:c);const p={},f=(y,_)=>{cr(_)||(p[y]=_.toString())};return f("width",u),f("height",d),p.viewBox=n.left.toString()+" "+n.top.toString()+" "+a.toString()+" "+l.toString(),{attributes:p,body:s}}const lr=()=>{let t;try{if(t=fetch,typeof t=="function")return t}catch{}};let Ie=lr();function ur(t){Ie=t}function dr(){return Ie}function hr(t,e){const i=je(t);if(!i)return 0;let r;if(!i.maxURL)r=0;else{let n=0;i.resources.forEach(o=>{n=Math.max(n,o.length)});const s=e+".json?icons=";r=i.maxURL-n-i.path.length-s.length}return r}function pr(t){return t===404}const fr=(t,e,i)=>{const r=[],n=hr(t,e),s="icons";let o={type:s,provider:t,prefix:e,icons:[]},c=0;return i.forEach((a,l)=>{c+=a.length+1,c>=n&&l>0&&(r.push(o),o={type:s,provider:t,prefix:e,icons:[]},c=a.length),o.icons.push(a)}),r.push(o),r};function mr(t){if(typeof t=="string"){const e=je(t);if(e)return e.path}return"/"}const gr=(t,e,i)=>{if(!Ie){i("abort",424);return}let r=mr(e.provider);switch(e.type){case"icons":{const s=e.prefix,c=e.icons.join(","),a=new URLSearchParams({icons:c});r+=s+".json?"+a.toString();break}case"custom":{const s=e.uri;r+=s.slice(0,1)==="/"?s.slice(1):s;break}default:i("abort",400);return}let n=503;Ie(t+r).then(s=>{const o=s.status;if(o!==200){setTimeout(()=>{i(pr(o)?"abort":"next",o)});return}return n=501,s.json()}).then(s=>{if(typeof s!="object"||s===null){setTimeout(()=>{s===404?i("abort",s):i("next",n)});return}setTimeout(()=>{i("success",s)})}).catch(()=>{i("next",n)})},br={prepare:fr,send:gr};function Kt(t,e){switch(t){case"local":case"session":R[t]=e;break;case"all":for(const i in R)R[i]=e;break}}const Ye="data-style";let Di="";function _r(t){Di=t}function Wt(t,e){let i=Array.from(t.childNodes).find(r=>r.hasAttribute&&r.hasAttribute(Ye));i||(i=document.createElement("style"),i.setAttribute(Ye,Ye),t.appendChild(i)),i.textContent=":host{display:inline-block;vertical-align:"+(e?"-0.125em":"0")+"}span,svg{display:block}"+Di}function Oi(){Ut("",br),vi(!0);let t;try{t=window}catch{}if(t){if(ki(),t.IconifyPreload!==void 0){const i=t.IconifyPreload,r="Invalid IconifyPreload syntax.";typeof i=="object"&&i!==null&&(i instanceof Array?i:[i]).forEach(n=>{try{(typeof n!="object"||n===null||n instanceof Array||typeof n.icons!="object"||typeof n.prefix!="string"||!Rt(n))&&console.error(r)}catch{console.error(r)}})}if(t.IconifyProviders!==void 0){const i=t.IconifyProviders;if(typeof i=="object"&&i!==null)for(const r in i){const n="IconifyProviders["+r+"] is invalid.";try{const s=i[r];if(typeof s!="object"||!s||s.resources===void 0)continue;Gt(r,s)||console.error(n)}catch{console.error(n)}}}}return{enableCache:i=>Kt(i,!0),disableCache:i=>Kt(i,!1),iconExists:Rn,getIcon:Un,listIcons:Bn,addIcon:Ai,addCollection:Rt,calculateSize:lt,buildIcon:Ii,loadIcons:_t,loadIcon:ir,addAPIProvider:Gt,appendCustomStyle:_r,_api:{getAPIConfig:je,setAPIModule:Ut,sendAPIQuery:Ei,setFetch:ur,getFetch:dr,listAPIProviders:Wn}}}function Li(t,e){let i=t.indexOf("xlink:")===-1?"":' xmlns:xlink="http://www.w3.org/1999/xlink"';for(const r in e)i+=" "+r+'="'+e[r]+'"';return'"+t+""}function yr(t){return t.replace(/"/g,"'").replace(/%/g,"%25").replace(/#/g,"%23").replace(//g,"%3E").replace(/\s+/g," ")}function vr(t){return"data:image/svg+xml,"+yr(t)}function Ar(t){return'url("'+vr(t)+'")'}const ut={"background-color":"currentColor"},Ni={"background-color":"transparent"},Jt={image:"var(--svg)",repeat:"no-repeat",size:"100% 100%"},Qt={"-webkit-mask":ut,mask:ut,background:Ni};for(const t in Qt){const e=Qt[t];for(const i in Jt)e[t+"-"+i]=Jt[i]}function Zt(t){return t?t+(t.match(/^[-0-9.]+$/)?"px":""):"inherit"}function Tr(t,e,i){const r=document.createElement("span");let n=t.body;n.indexOf("");const s=t.attributes,o=Li(n,{...s,width:e.width+"",height:e.height+""}),c=Ar(o),a=r.style,l={"--svg":c,width:Zt(s.width),height:Zt(s.height),...i?ut:Ni};for(const u in l)a.setProperty(u,l[u]);return r}let se;function wr(){try{se=window.trustedTypes.createPolicy("iconify",{createHTML:t=>t})}catch{se=null}}function Er(t){return se===void 0&&wr(),se?se.createHTML(t):t}function Mr(t){const e=document.createElement("span"),i=t.attributes;let r="";i.width||(r="width: inherit;"),i.height||(r+="height: inherit;"),r&&(i.style=r);const n=Li(t.body,i);return e.innerHTML=Er(n),e.firstChild}function Yt(t,e){const i=e.icon.data,r=e.customisations,n=Ii(i,r);r.preserveAspectRatio&&(n.attributes.preserveAspectRatio=r.preserveAspectRatio);const s=e.renderedMode;let o;switch(s){case"svg":o=Mr(n);break;default:o=Tr(n,{...be,...i},s==="mask")}const c=Array.from(t.childNodes).find(a=>{const l=a.tagName&&a.tagName.toUpperCase();return l==="SPAN"||l==="SVG"});c?o.tagName==="SPAN"&&c.tagName===o.tagName?c.setAttribute("style",o.getAttribute("style")):t.replaceChild(o,c):t.appendChild(o)}function Xt(t,e,i){const r=i&&(i.rendered?i:i.lastRender);return{rendered:!1,inline:e,icon:t,lastRender:r}}function $r(t="iconify-icon"){let e,i;try{e=window.customElements,i=window.HTMLElement}catch{return}if(!e||!i)return;const r=e.get(t);if(r)return r;const n=["icon","mode","inline","width","height","rotate","flip"],s=class extends i{constructor(){super();Ae(this,"_shadowRoot");Ae(this,"_state");Ae(this,"_checkQueued",!1);const a=this._shadowRoot=this.attachShadow({mode:"open"}),l=Ze(this);Wt(a,l),this._state=Xt({value:""},l),this._queueCheck()}static get observedAttributes(){return n.slice(0)}attributeChangedCallback(a){if(a==="inline"){const l=Ze(this),u=this._state;l!==u.inline&&(u.inline=l,Wt(this._shadowRoot,l))}else this._queueCheck()}get icon(){const a=this.getAttribute("icon");if(a&&a.slice(0,1)==="{")try{return JSON.parse(a)}catch{}return a}set icon(a){typeof a=="object"&&(a=JSON.stringify(a)),this.setAttribute("icon",a)}get inline(){return Ze(this)}set inline(a){a?this.setAttribute("inline","true"):this.removeAttribute("inline")}restartAnimation(){const a=this._state;if(a.rendered){const l=this._shadowRoot;if(a.renderedMode==="svg")try{l.lastChild.setCurrentTime(0);return}catch{}Yt(l,a)}}get status(){const a=this._state;return a.rendered?"rendered":a.icon.data===null?"failed":"loading"}_queueCheck(){this._checkQueued||(this._checkQueued=!0,setTimeout(()=>{this._check()}))}_check(){if(!this._checkQueued)return;this._checkQueued=!1;const a=this._state,l=this.getAttribute("icon");if(l!==a.icon.value){this._iconChanged(l);return}if(!a.rendered)return;const u=this.getAttribute("mode"),d=jt(this);(a.attrMode!==u||Pn(a.customisations,d))&&this._renderIcon(a.icon,d,u)}_iconChanged(a){const l=rr(a,(u,d,p)=>{const f=this._state;if(f.rendered||this.getAttribute("icon")!==u)return;const y={value:u,name:d,data:p};y.data?this._gotIconData(y):f.icon=y});l.data?this._gotIconData(l):this._state=Xt(l,this._state.inline,this._state)}_gotIconData(a){this._checkQueued=!1,this._renderIcon(a,jt(this),this.getAttribute("mode"))}_renderIcon(a,l,u){const d=sr(a.data.body,u),p=this._state.inline;Yt(this._shadowRoot,this._state={rendered:!0,icon:a,inline:p,customisations:l,attrMode:u,renderedMode:d})}};n.forEach(c=>{c in s.prototype||Object.defineProperty(s.prototype,c,{get:function(){return this.getAttribute(c)},set:function(a){a!==null?this.setAttribute(c,a):this.removeAttribute(c)}})});const o=Oi();for(const c in o)s[c]=s.prototype[c]=o[c];return e.define(t,s),s}$r()||Oi();var Sr=S`.btn,button{cursor:pointer;border-radius:4px;color:#03a9f4;border:none;background-color:unset;padding:8px;font-weight:500;font-size:12.25px;letter-spacing:1.09375px;text-transform:uppercase;margin-right:-8px}.btn:active,button:active{background-image:rgba(127,127,127,.2);transition-duration:1s}.btn:hover,button:hover{background-color:rgba(127,127,127,.2);transition-duration:1s}.abuttonIsState{background-color:#28a745;color:#fff;border:none;padding:10px 20px;font-size:16px;border-radius:4px;transition:background-color .3s ease}`,xr=S`.main-grid{display:grid;grid-template-columns:1fr 1fr;width:100%;padding-bottom:2rem}.main-buttons{display:grid;gap:1rem;justify-content:center;grid-template-columns:auto auto auto auto;align-content:center;align-items:center;justify-content:center}@media (max-width:1024px){.main-grid{grid-template-columns:1fr}.main-buttons{gap:.5rem;grid-template-columns:auto auto auto}}.flex-grid-half{display:grid;grid-template-columns:700px 2fr}.flex-grid-half.expanded_entity,.flex-grid-half.expanded_logs{grid-template-columns:1fr}.flex-grid-half .col{margin:8px}.flex-grid-half .col:nth-child(2){overflow:hidden}.flex-grid-half.expanded_logs .col:nth-child(1){display:none}.flex-grid-half.expanded_entity .col:nth-child(2){display:none}@media (max-width:1024px){.flex-grid,.flex-grid-half{display:block}.flex-grid-half .col{width:100%!important;margin:0 0 10px 0!important;display:block!important}}*{box-sizing:border-box}.flex-grid{margin:0 0 20px 0}h1{text-align:center;width:100%;line-height:1.1em;margin-block:.25em}header div{text-align:center;width:100%}header #logo,header iconify-icon{float:right;font-size:2.5rem;color:rgba(127,127,127,.5)}header #logo{float:left;color:rgba(127,127,127,.5)}.connected{color:rgba(0,157,16,.75)}esp-logo{float:left;line-height:1em;font-size:initial}form{display:flex;justify-content:space-between;background-color:rgba(127,127,127,.05);border-radius:12px;border-width:1px;border-style:solid;border-color:rgba(127,127,127,.12)}form .btn{margin-right:0}.helper{width:75%;padding:1rem;margin:.3rem 0;background-color:rgba(127,127,127,.3);border-radius:.5rem}`,Cr=Object.defineProperty,kr=Object.getOwnPropertyDescriptor,z=(t,e,i,r)=>{for(var n=r>1?void 0:r?kr(e,i):e,s=t.length-1,o;s>=0;s--)(o=t[s])&&(n=(r?o(e,i,n):o(n))||n);return r&&n&&Cr(e,i,n),n};window.source=new EventSource(pt()+"/events");window.entities=[];const Ee={};var ti;(ti=window.source)==null||ti.addEventListener("state",t=>{const e=t,i=JSON.parse(e.data.replace(/[\u0000-\u001F\u007F-\u009F]/g,""));let r=window.entities.findIndex(n=>n.unique_id===i.id);if(r!=-1&&i.id){if(typeof i.value=="number"){let n=[...window.entities[r].value_numeric_history];n.push(i.value),window.entities[r].value_numeric_history=n.splice(-50)}delete i.id,delete i.domain,delete i.unique_id,Object.assign(window.entities[r],i)}else if(i!=null&&i.name)ei(i);else{if(Ee[i.id]?Ee[i.id]++:Ee[i.id]=1,Ee[i.id]<1)return;let n=i.id.split("-"),s=n[0],o=n.slice(1).join("-");fetch(`${window.apiBasePath}/${s}/${o}?detail=all`,{method:"GET"}).then(c=>{if(console.log(c),!c.ok)throw new Error(`HTTP error! Status: ${c.status}`);return c.json()}).then(c=>{console.log(c),ei(c)}).catch(c=>{console.error("Fetch error:",c)})}});function ei(t){if(console.log(t),window.entities.findIndex(i=>i.unique_id===t.id)===-1&&t.id){let i=t.id.split("-"),r={...t,domain:i[0],unique_id:t.id,id:i.slice(1).join("-"),entity_category:t.entity_category,value_numeric_history:[t.value]};r.has_action=`render_${r.domain}`in pi.prototype,window.entities.push(r),Q.set(r)}}function Pr(t){const e=Math.sign(t);if(t===0)return new Intl.RelativeTimeFormat("en").format(0,"second");const i=[{type:"year",seconds:12*30*24*60*60*1e3},{type:"month",seconds:30*24*60*60*1e3},{type:"week",seconds:7*24*60*60*1e3},{type:"day",seconds:24*60*60*1e3},{type:"hour",seconds:60*60*1e3},{type:"minute",seconds:60*1e3},{type:"second",seconds:1e3}];let r="";const n=new Intl.RelativeTimeFormat("en");let s=0;for(let o of i){const c=Math.trunc(Math.abs(t/o.seconds));if(c>0){const a=n.format(c*e,o.type);if(t-=c*o.seconds*e,r+=s===0&&o.type!="second"?a.replace(" ago"," "):a,s++>=1)break}}return r}let L=class extends E{constructor(){super(),this.scheme="",this.ping=0,this.connected=!0,this.lastUpdate=0,this.showLog=localStorage.showLog==="true",this.version="3.0.0",this.config={ota:!1,log:!0,title:"",comment:"",lang:""},this.darkQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.frames=[{},{color:"rgba(0, 196, 21, 0.75)"},{}];const t=document.querySelector("script#config");console.log(t),t&&this.setConfig(JSON.parse(t.innerText))}setConfig(t){"log"in t||(t.log=this.config.log),this.config=t,document.title=t.title,document.documentElement.lang=t.lang;const[e,i,r]=t.comment.split("|");this.nbsconfig={type:e,version:i,comment:r}}firstUpdated(t){super.firstUpdated(t),document.getElementsByTagName("head")[0].innerHTML+='';const e=document.querySelector("link[rel~='icon']");e.href='data:image/svg+xml,',this.scheme=this.schemeDefault(),window.source.addEventListener("ping",i=>{var r;(r=i.data)!=null&&r.length&&(this.setConfig(JSON.parse(i.data)),this.requestUpdate()),this._updateUptime(i),this.lastUpdate=Date.now()}),window.source.addEventListener("log",i=>{this._updateUptime(i),this.lastUpdate=Date.now()}),window.source.addEventListener("state",i=>{this.lastUpdate=Date.now()}),window.source.addEventListener("error",i=>{console.dir(i),this.connected=!1,this.requestUpdate()}),setInterval(()=>{this.connected=!!this.ping&&Date.now()-this.lastUpdate<15e3},5e3),document.addEventListener("entity-tab-header-double-clicked",i=>{var n;const r=(n=this.shadowRoot)==null?void 0:n.querySelector("main.flex-grid-half");r==null||r.classList.toggle("expanded_entity")}),document.addEventListener("log-tab-header-double-clicked",i=>{var n;const r=(n=this.shadowRoot)==null?void 0:n.querySelector("main.flex-grid-half");r==null||r.classList.toggle("expanded_logs")})}schemeDefault(){return this.darkQuery.matches?"dark":"light"}updated(t){super.updated(t),t.has("scheme")&&document.documentElement.style.setProperty("color-scheme",this.scheme),t.has("ping")&&this.ping&&this.beat.animate(this.frames,1e3)}uptime(){return`${Pr(-this.ping|0)}`}renderOta(){if(this.config.ota){let t=pt();return h`
`}}renderLog(){return!this.config.log||!this.showLog?g:h`
`}renderTitle(){var t,e;return h`

${this.config.title||h` `}

${[(t=this.nbsconfig)==null?void 0:t.comment,`started ${this.uptime()}`,(e=this.nbsconfig)==null?void 0:e.version].filter(i=>i).map(i=>`${i}`).join(" \xB7 ")}
`}render(){var t,e,i,r,n,s,o;return h`
${this.renderTitle()}
${((t=this.nbsconfig)==null?void 0:t.type)==="gen3"?h``:g} ${((e=this.nbsconfig)==null?void 0:e.type)==="gen2"?h``:g}
${((i=this.nbsconfig)==null?void 0:i.type)==="gen3"?h`
`:g}
c.startsWith("200")?"No alerts":c}}">
${((r=this.nbsconfig)==null?void 0:r.type)==="gen2"?h``:g}
${((s=this.nbsconfig)==null?void 0:s.type)==="gen3"?h``:g}
The manual for the Webserver can be found here. When new updates for Neato Brainslug is out, you can easily update your device by uploading the OTA file here! ${this.renderOta()} Type: ${(o=this.nbsconfig)==null?void 0:o.type}       Toggle the debug logs: 
${this.renderLog()}`}_updateUptime(t){t.lastEventId&&(this.ping=parseInt(t.lastEventId),this.connected=!0,this.requestUpdate())}static get styles(){return[me,Sr,xr,di]}};z([x()],L.prototype,"scheme",2);z([x()],L.prototype,"ping",2);z([x()],L.prototype,"connected",2);z([x()],L.prototype,"lastUpdate",2);z([ci("#beat")],L.prototype,"beat",2);z([x()],L.prototype,"showLog",2);L=z([k("esp-app")],L); ================================================ FILE: config/local.yaml ================================================ substitutions: name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml! comment: "" # If you want to write a comment about this vacuum, do so here. e.g. "Downstairs garage vacuum" infointerval: 2sec chargerinterval: 2min ota_password: !secret neato_vacuum_ota ### If you are using ha: wifi_ssid: !secret wifi_ssid wifi_password: !secret wifi_password ha_encryption_key: !secret neato_vacuum_api ### If you want to change the UART Pins # uart_tx: 17 # uart_rx: 16 wifi: ### If your router sets another TLD for local devices, specify that here # domain: .lan ### If you are having issues with the dns or are 100% this will be the ip, you can spesify that here # use_address: 192.168.205.199 packages: - !include boards/esp32.yaml # - !include boards/esp32c3.yaml # - !include boards/esp32s3.yaml # - !include boards/esp32s2.yaml - !include comp/ha.yaml # Remember to set wifi credentials and ha_encryption_key in the substitutions if you are using this # - !include comp/no-ha.yaml - !include comp/webserver.yaml - !include comp/gen3.yaml # - !include comp/gen2.yaml - !include comp/ros.yaml ================================================ FILE: config/prebuilt/.gitignore ================================================ # Gitignore settings for ESPHome # This is an example and may include too much for your use-case. # You can modify this file to suit your needs. /.esphome/ /secrets.yaml ================================================ FILE: config/prebuilt/gen2-esp32.yaml ================================================ substitutions: name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml! comment: "" # If you want to write a comment about this vacuum, do so here. e.g. "Downstairs garage vacuum" infointerval: 2sec chargerinterval: 2min ota_password: !secret brainslug_ota packages: - !include ../boards/esp32.yaml # - !include ../boards/esp32c3.yaml # - !include ../boards/esp32s3.yaml # - !include ../comp/ha.yaml - !include ../comp/no-ha.yaml - !include ../comp/webserver.yaml # - !include ../comp/gen3.yaml - !include ../comp/gen2.yaml ================================================ FILE: config/prebuilt/gen2-esp32c3.yaml ================================================ substitutions: name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml! comment: "" # If you want to write a comment about this vacuum, do so here. e.g. "Downstairs garage vacuum" infointerval: 2sec chargerinterval: 2min ota_password: !secret brainslug_ota packages: # - !include ../boards/esp32.yaml - !include ../boards/esp32c3.yaml # - !include ../boards/esp32s3.yaml # - !include ../comp/ha.yaml - !include ../comp/no-ha.yaml - !include ../comp/webserver.yaml # - !include ../comp/gen3.yaml - !include ../comp/gen2.yaml ================================================ FILE: config/prebuilt/gen2-esp32c6.yaml ================================================ substitutions: name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml! comment: "" # If you want to write a comment about this vacuum, do so here. e.g. "Downstairs garage vacuum" infointerval: 2sec chargerinterval: 2min ota_password: !secret brainslug_ota packages: # - !include ../boards/esp32.yaml - !include ../boards/esp32c6.yaml # - !include ../boards/esp32s3.yaml # - !include ../comp/ha.yaml - !include ../comp/no-ha.yaml - !include ../comp/webserver.yaml # - !include ../comp/gen3.yaml - !include ../comp/gen2.yaml ================================================ FILE: config/prebuilt/gen2-esp32s3.yaml ================================================ substitutions: name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml! comment: "" # If you want to write a comment about this vacuum, do so here. e.g. "Downstairs garage vacuum" infointerval: 2sec chargerinterval: 2min ota_password: !secret brainslug_ota packages: # - !include ../boards/esp32.yaml # - !include ../boards/esp32c3.yaml - !include ../boards/esp32s3.yaml # - !include ../comp/ha.yaml - !include ../comp/no-ha.yaml - !include ../comp/webserver.yaml # - !include ../comp/gen3.yaml - !include ../comp/gen2.yaml ================================================ FILE: config/prebuilt/gen3-esp32.yaml ================================================ substitutions: name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml! comment: "" # If you want to write a comment about this vacuum, do so here. e.g. "Downstairs garage vacuum" infointerval: 2sec chargerinterval: 2min ota_password: !secret brainslug_ota packages: - !include ../boards/esp32.yaml # - !include ../boards/esp32c3.yaml # - !include ../boards/esp32s3.yaml # - !include ../comp/ha.yaml - !include ../comp/no-ha.yaml - !include ../comp/webserver.yaml - !include ../comp/gen3.yaml # - !include ../comp/gen2.yaml ================================================ FILE: config/prebuilt/gen3-esp32c3.yaml ================================================ substitutions: name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml! comment: "" # If you want to write a comment about this vacuum, do so here. e.g. "Downstairs garage vacuum" infointerval: 2sec chargerinterval: 2min ota_password: !secret brainslug_ota packages: # - !include ../boards/esp32.yaml - !include ../boards/esp32c3.yaml # - !include ../boards/esp32s3.yaml # - !include ../comp/ha.yaml - !include ../comp/no-ha.yaml - !include ../comp/webserver.yaml - !include ../comp/gen3.yaml # - !include ../comp/gen2.yaml ================================================ FILE: config/prebuilt/gen3-esp32c6.yaml ================================================ substitutions: name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml! comment: "" # If you want to write a comment about this vacuum, do so here. e.g. "Downstairs garage vacuum" infointerval: 2sec chargerinterval: 2min ota_password: !secret brainslug_ota packages: # - !include ../boards/esp32.yaml - !include ../boards/esp32c6.yaml # - !include ../boards/esp32s3.yaml # - !include ../comp/ha.yaml - !include ../comp/no-ha.yaml - !include ../comp/webserver.yaml - !include ../comp/gen3.yaml # - !include ../comp/gen2.yaml ================================================ FILE: config/prebuilt/gen3-esp32s3.yaml ================================================ substitutions: name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml! comment: "" # If you want to write a comment about this vacuum, do so here. e.g. "Downstairs garage vacuum" infointerval: 2sec chargerinterval: 2min ota_password: !secret brainslug_ota packages: # - !include ../boards/esp32.yaml # - !include ../boards/esp32c3.yaml - !include ../boards/esp32s3.yaml # - !include ../comp/ha.yaml - !include ../comp/no-ha.yaml - !include ../comp/webserver.yaml - !include ../comp/gen3.yaml # - !include ../comp/gen2.yaml ================================================ FILE: config/remote.yaml ================================================ substitutions: name: neato-vacuum comment: "" infointerval: 2sec chargerinterval: 2min ota_password: !secret neato_vacuum_ota ### If you are using ha: wifi_ssid: !secret wifi_ssid wifi_password: !secret wifi_password ha_encryption_key: !secret neato_vacuum_api ### If you want to change the UART Pins # uart_tx: 17 # uart_rx: 16 wifi: ### If your router sets another TLD for local devices, specify that here # domain: .lan ### If you are having issues with the dns or are 100% this will be the ip, you can spesify that here # use_address: 192.168.205.199 packages: remote_package_files: url: https://github.com/philip2809/neato-brainslug files: # - config/boards/esp32.yaml # - config/boards/esp32c3.yaml # - config/boards/esp32s3.yaml - config/boards/esp32s2.yaml - config/comp/ha.yaml # Remember to set wifi credentials and ha_encryption_key in the substitutions if you are using this # - config/comp/no-ha.yaml - config/comp/webserver.yaml - config/comp/gen3.yaml # - config/comp/gen2.yaml ref: config ================================================ FILE: docs/404.html ================================================ Brainslug Tools - Not Found

404 - Not Found

The page you are looking for does not exist. Redirecting to the main page...

================================================ FILE: docs/CNAME ================================================ brainslug.phma.dev ================================================ FILE: docs/assets/esp32-DR_yen0A.js ================================================ import{t as e}from"./rom-D7P4LvQr.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32`,this.IMAGE_CHIP_ID=0,this.EFUSE_RD_REG_BASE=1073061888,this.DR_REG_SYSCON_BASE=1073111040,this.UART_CLKDIV_REG=1072955412,this.UART_CLKDIV_MASK=1048575,this.UART_DATE_REG_ADDR=1610612856,this.XTAL_CLK_DIVIDER=1,this.FLASH_SIZES={"1MB":0,"2MB":16,"4MB":32,"8MB":48,"16MB":64},this.FLASH_WRITE_SIZE=1024,this.BOOTLOADER_FLASH_OFFSET=4096,this.SPI_REG_BASE=1072963584,this.SPI_USR_OFFS=28,this.SPI_USR1_OFFS=32,this.SPI_USR2_OFFS=36,this.SPI_W0_OFFS=128,this.SPI_MOSI_DLEN_OFFS=40,this.SPI_MISO_DLEN_OFFS=44}async readEfuse(e,t){let n=this.EFUSE_RD_REG_BASE+4*t;return e.debug(`Read efuse `+n),await e.readReg(n)}async getPkgVersion(e){let t=await this.readEfuse(e,3),n=t>>9&7;return n+=(t>>2&1)<<3,n}async getChipRevision(e){let t=await this.readEfuse(e,3),n=await this.readEfuse(e,5),r=await e.readReg(this.DR_REG_SYSCON_BASE+124),i=t>>15&1,a=n>>20&1,o=r>>31&1;return i==0?0:a==0?1:o==0?2:3}async getChipDescription(e){let t=[`ESP32-D0WDQ6`,`ESP32-D0WD`,`ESP32-D2WD`,``,`ESP32-U4WDH`,`ESP32-PICO-D4`,`ESP32-PICO-V3-02`],n=``,r=await this.getPkgVersion(e),i=await this.getChipRevision(e),a=i==3;return await this.readEfuse(e,3)&1&&(t[0]=`ESP32-S0WDQ6`,t[1]=`ESP32-S0WD`),a&&(t[5]=`ESP32-PICO-V3`),n=r>=0&&r<=6?t[r]:`Unknown ESP32`,a&&(r===0||r===1)&&(n+=`-V3`),n+` (revision `+i+`)`}async getChipFeatures(e){let t=[`Wi-Fi`],n=await this.readEfuse(e,3);n&2||t.push(` BT`),n&1?t.push(` Single Core`):t.push(` Dual Core`),n&8192&&(n&4096?t.push(` 160MHz`):t.push(` 240MHz`));let r=await this.getPkgVersion(e);[2,4,5,6].indexOf(r)!==-1&&t.push(` Embedded Flash`),r===6&&t.push(` Embedded PSRAM`),await this.readEfuse(e,4)>>8&31&&t.push(` VRef calibration in efuse`),n>>14&1&&t.push(` BLK3 partially reserved`);let i=await this.readEfuse(e,6)&3;return t.push(` Coding Scheme `+[`None`,`3/4`,`Repeat (UNSUPPORTED)`,`Invalid`][i]),t}async getCrystalFreq(e){let t=await e.readReg(this.UART_CLKDIV_REG)&this.UART_CLKDIV_MASK,n=e.transport.baudrate*t/1e6/this.XTAL_CLK_DIVIDER,r;return r=n>33?40:26,Math.abs(r-n)>1&&e.info(`WARNING: Unsupported crystal in use`),r}_d2h(e){let t=(+e).toString(16);return t.length===1?`0`+t:t}async readMac(e){let t=await this.readEfuse(e,1);t>>>=0;let n=await this.readEfuse(e,2);n>>>=0;let r=new Uint8Array(6);return r[0]=n>>8&255,r[1]=n&255,r[2]=t>>24&255,r[3]=t>>16&255,r[4]=t>>8&255,r[5]=t&255,this._d2h(r[0])+`:`+this._d2h(r[1])+`:`+this._d2h(r[2])+`:`+this._d2h(r[3])+`:`+this._d2h(r[4])+`:`+this._d2h(r[5])}};export{t as ESP32ROM}; ================================================ FILE: docs/assets/esp32c2-CIvS3qc5.js ================================================ import{ESP32C3ROM as e}from"./esp32c3-nJJ15ppW.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32-C2`,this.IMAGE_CHIP_ID=12,this.EFUSE_BASE=1610647552,this.MAC_EFUSE_REG=this.EFUSE_BASE+64,this.UART_CLKDIV_REG=1610612756,this.UART_CLKDIV_MASK=1048575,this.UART_DATE_REG_ADDR=1610612860,this.XTAL_CLK_DIVIDER=1,this.FLASH_WRITE_SIZE=1024,this.BOOTLOADER_FLASH_OFFSET=0,this.FLASH_SIZES={"1MB":0,"2MB":16,"4MB":32,"8MB":48,"16MB":64},this.SPI_REG_BASE=1610620928,this.SPI_USR_OFFS=24,this.SPI_USR1_OFFS=28,this.SPI_USR2_OFFS=32,this.SPI_MOSI_DLEN_OFFS=36,this.SPI_MISO_DLEN_OFFS=40,this.SPI_W0_OFFS=88}async getPkgVersion(e){let t=this.EFUSE_BASE+64+4;return await e.readReg(t)>>22&7}async getChipRevision(e){let t=this.EFUSE_BASE+64+4;return(await e.readReg(t)&3<<20)>>20}async getChipDescription(e){let t,n=await this.getPkgVersion(e);t=n===0||n===1?`ESP32-C2`:`unknown ESP32-C2`;let r=await this.getChipRevision(e);return t+=` (revision `+r+`)`,t}async getChipFeatures(e){return[`Wi-Fi`,`BLE`]}async getCrystalFreq(e){let t=await e.readReg(this.UART_CLKDIV_REG)&this.UART_CLKDIV_MASK,n=e.transport.baudrate*t/1e6/this.XTAL_CLK_DIVIDER,r;return r=n>33?40:26,Math.abs(r-n)>1&&e.info(`WARNING: Unsupported crystal in use`),r}async changeBaudRate(e){await this.getCrystalFreq(e)===26&&e.changeBaud()}_d2h(e){let t=(+e).toString(16);return t.length===1?`0`+t:t}async readMac(e){let t=await e.readReg(this.MAC_EFUSE_REG);t>>>=0;let n=await e.readReg(this.MAC_EFUSE_REG+4);n=n>>>0&65535;let r=new Uint8Array(6);return r[0]=n>>8&255,r[1]=n&255,r[2]=t>>24&255,r[3]=t>>16&255,r[4]=t>>8&255,r[5]=t&255,this._d2h(r[0])+`:`+this._d2h(r[1])+`:`+this._d2h(r[2])+`:`+this._d2h(r[3])+`:`+this._d2h(r[4])+`:`+this._d2h(r[5])}getEraseSize(e,t){return t}};export{t as ESP32C2ROM}; ================================================ FILE: docs/assets/esp32c3-nJJ15ppW.js ================================================ import{t as e}from"./rom-D7P4LvQr.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32-C3`,this.IMAGE_CHIP_ID=5,this.EFUSE_BASE=1610647552,this.MAC_EFUSE_REG=this.EFUSE_BASE+68,this.UART_CLKDIV_REG=1072955412,this.UART_CLKDIV_MASK=1048575,this.UART_DATE_REG_ADDR=1610612860,this.FLASH_WRITE_SIZE=1024,this.BOOTLOADER_FLASH_OFFSET=0,this.FLASH_SIZES={"1MB":0,"2MB":16,"4MB":32,"8MB":48,"16MB":64},this.SPI_REG_BASE=1610620928,this.SPI_USR_OFFS=24,this.SPI_USR1_OFFS=28,this.SPI_USR2_OFFS=32,this.SPI_MOSI_DLEN_OFFS=36,this.SPI_MISO_DLEN_OFFS=40,this.SPI_W0_OFFS=88}async getPkgVersion(e){let t=this.EFUSE_BASE+68+12;return await e.readReg(t)>>21&7}async getChipRevision(e){let t=this.EFUSE_BASE+68+12;return(await e.readReg(t)&7<<18)>>18}async getMinorChipVersion(e){let t=this.EFUSE_BASE+68+20,n=await e.readReg(t)>>23&1,r=this.EFUSE_BASE+68+12,i=await e.readReg(r)>>18&7;return(n<<3)+i}async getMajorChipVersion(e){let t=this.EFUSE_BASE+68+20;return await e.readReg(t)>>24&3}async getChipDescription(e){let t={0:`ESP32-C3 (QFN32)`,1:`ESP8685 (QFN28)`,2:`ESP32-C3 AZ (QFN32)`,3:`ESP8686 (QFN24)`},n=await this.getPkgVersion(e),r=await this.getMajorChipVersion(e),i=await this.getMinorChipVersion(e);return`${t[n]||`Unknown ESP32-C3`} (revision v${r}.${i})`}async getFlashCap(e){let t=this.EFUSE_BASE+68+12;return await e.readReg(t)>>27&7}async getFlashVendor(e){let t=this.EFUSE_BASE+68+16,n=await e.readReg(t)>>0&7;return{1:`XMC`,2:`GD`,3:`FM`,4:`TT`,5:`ZBIT`}[n]||``}async getChipFeatures(e){let t=[`Wi-Fi`,`BLE`],n={0:null,1:`Embedded Flash 4MB`,2:`Embedded Flash 2MB`,3:`Embedded Flash 1MB`,4:`Embedded Flash 8MB`},r=await this.getFlashCap(e),i=await this.getFlashVendor(e),a=n[r],o=a===void 0?`Unknown Embedded Flash`:a;return a!==null&&t.push(`${o} (${i})`),t}async getCrystalFreq(e){return 40}_d2h(e){let t=(+e).toString(16);return t.length===1?`0`+t:t}async readMac(e){let t=await e.readReg(this.MAC_EFUSE_REG);t>>>=0;let n=await e.readReg(this.MAC_EFUSE_REG+4);n=n>>>0&65535;let r=new Uint8Array(6);return r[0]=n>>8&255,r[1]=n&255,r[2]=t>>24&255,r[3]=t>>16&255,r[4]=t>>8&255,r[5]=t&255,this._d2h(r[0])+`:`+this._d2h(r[1])+`:`+this._d2h(r[2])+`:`+this._d2h(r[3])+`:`+this._d2h(r[4])+`:`+this._d2h(r[5])}getEraseSize(e,t){return t}};export{t as ESP32C3ROM}; ================================================ FILE: docs/assets/esp32c5-DbWg37P4.js ================================================ import{ESP32C6ROM as e}from"./esp32c6-snJpA5DJ.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32-C5`,this.IMAGE_CHIP_ID=23,this.BOOTLOADER_FLASH_OFFSET=8192,this.EFUSE_BASE=1611352064,this.EFUSE_BLOCK1_ADDR=this.EFUSE_BASE+68,this.MAC_EFUSE_REG=this.EFUSE_BASE+68,this.UART_CLKDIV_REG=1610612756,this.EFUSE_RD_REG_BASE=this.EFUSE_BASE+48,this.EFUSE_PURPOSE_KEY0_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY0_SHIFT=24,this.EFUSE_PURPOSE_KEY1_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY1_SHIFT=28,this.EFUSE_PURPOSE_KEY2_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY2_SHIFT=0,this.EFUSE_PURPOSE_KEY3_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY3_SHIFT=4,this.EFUSE_PURPOSE_KEY4_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY4_SHIFT=8,this.EFUSE_PURPOSE_KEY5_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY5_SHIFT=12,this.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG=this.EFUSE_RD_REG_BASE,this.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT=1<<20,this.EFUSE_SPI_BOOT_CRYPT_CNT_REG=this.EFUSE_BASE+52,this.EFUSE_SPI_BOOT_CRYPT_CNT_MASK=7<<18,this.EFUSE_SECURE_BOOT_EN_REG=this.EFUSE_BASE+56,this.EFUSE_SECURE_BOOT_EN_MASK=1<<20,this.IROM_MAP_START=1107296256,this.IROM_MAP_END=1115684864,this.DROM_MAP_START=1115684864,this.DROM_MAP_END=1124073472,this.PCR_SYSCLK_CONF_REG=1611227408,this.PCR_SYSCLK_XTAL_FREQ_V=127<<24,this.PCR_SYSCLK_XTAL_FREQ_S=24,this.XTAL_CLK_DIVIDER=1,this.UARTDEV_BUF_NO=1082520860,this.CHIP_DETECT_MAGIC_VALUE=[285294703,1675706479,1607549039],this.FLASH_FREQUENCY={"80m":15,"40m":0,"20m":2},this.MEMORY_MAP=[[0,65536,`PADDING`],[1115684864,1124073472,`DROM`],[1082130432,1082523648,`DRAM`],[1082130432,1082523648,`BYTE_ACCESSIBLE`],[1073979392,1074003968,`DROM_MASK`],[1073741824,1073979392,`IROM_MASK`],[1107296256,1115684864,`IROM`],[1082130432,1082523648,`IRAM`],[1342177280,1342193664,`RTC_IRAM`],[1342177280,1342193664,`RTC_DRAM`],[1611653120,1611661312,`MEM_INTERNAL2`]],this.UF2_FAMILY_ID=4145808195,this.EFUSE_MAX_KEY=5,this.KEY_PURPOSES={0:`USER/EMPTY`,1:`ECDSA_KEY`,2:`XTS_AES_256_KEY_1`,3:`XTS_AES_256_KEY_2`,4:`XTS_AES_128_KEY`,5:`HMAC_DOWN_ALL`,6:`HMAC_DOWN_JTAG`,7:`HMAC_DOWN_DIGITAL_SIGNATURE`,8:`HMAC_UP`,9:`SECURE_BOOT_DIGEST0`,10:`SECURE_BOOT_DIGEST1`,11:`SECURE_BOOT_DIGEST2`,12:`KM_INIT_KEY`}}async getPkgVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+8)>>26&7}async getMinorChipVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+8)>>0&15}async getMajorChipVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+8)>>4&3}async getChipDescription(e){let t=await this.getPkgVersion(e),n;n=t===0?`ESP32-C5`:`unknown ESP32-C5`;let r=await this.getMajorChipVersion(e),i=await this.getMinorChipVersion(e);return`${n} (revision v${r}.${i})`}async getChipFeatures(e){return[`Wi-Fi 6 (dual-band)`,`BT 5 (LE)`]}async getCrystalFreq(e){let t=await e.readReg(this.UART_CLKDIV_REG)&this.UART_CLKDIV_MASK,n=e.transport.baudrate*t/1e6/this.XTAL_CLK_DIVIDER,r;return r=n>45?48:n>33?40:26,Math.abs(r-n)>1&&e.info(`WARNING: Unsupported crystal in use`),r}async getCrystalFreqRomExpect(e){return(await e.readReg(this.PCR_SYSCLK_CONF_REG)&this.PCR_SYSCLK_XTAL_FREQ_V)>>this.PCR_SYSCLK_XTAL_FREQ_S}};export{t as ESP32C5ROM}; ================================================ FILE: docs/assets/esp32c6-snJpA5DJ.js ================================================ import{t as e}from"./rom-D7P4LvQr.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32-C6`,this.IMAGE_CHIP_ID=13,this.EFUSE_BASE=1611335680,this.EFUSE_BLOCK1_ADDR=this.EFUSE_BASE+68,this.MAC_EFUSE_REG=this.EFUSE_BASE+68,this.UART_CLKDIV_REG=1072955412,this.UART_CLKDIV_MASK=1048575,this.UART_DATE_REG_ADDR=1610612860,this.FLASH_WRITE_SIZE=1024,this.BOOTLOADER_FLASH_OFFSET=0,this.FLASH_SIZES={"1MB":0,"2MB":16,"4MB":32,"8MB":48,"16MB":64},this.SPI_REG_BASE=1610620928,this.SPI_USR_OFFS=24,this.SPI_USR1_OFFS=28,this.SPI_USR2_OFFS=32,this.SPI_MOSI_DLEN_OFFS=36,this.SPI_MISO_DLEN_OFFS=40,this.SPI_W0_OFFS=88}async getPkgVersion(e){let t=this.EFUSE_BASE+68+12;return await e.readReg(t)>>21&7}async getChipRevision(e){let t=this.EFUSE_BASE+68+12;return(await e.readReg(t)&7<<18)>>18}async getChipDescription(e){let t;t=await this.getPkgVersion(e)===0?`ESP32-C6`:`unknown ESP32-C6`;let n=await this.getChipRevision(e);return t+=` (revision `+n+`)`,t}async getChipFeatures(e){return[`Wi-Fi 6`,`BT 5`,`IEEE802.15.4`]}async getCrystalFreq(e){return 40}_d2h(e){let t=(+e).toString(16);return t.length===1?`0`+t:t}async readMac(e){let t=await e.readReg(this.MAC_EFUSE_REG);t>>>=0;let n=await e.readReg(this.MAC_EFUSE_REG+4);n=n>>>0&65535;let r=new Uint8Array(6);return r[0]=n>>8&255,r[1]=n&255,r[2]=t>>24&255,r[3]=t>>16&255,r[4]=t>>8&255,r[5]=t&255,this._d2h(r[0])+`:`+this._d2h(r[1])+`:`+this._d2h(r[2])+`:`+this._d2h(r[3])+`:`+this._d2h(r[4])+`:`+this._d2h(r[5])}getEraseSize(e,t){return t}};export{t as ESP32C6ROM}; ================================================ FILE: docs/assets/esp32c61-DUe22z0I.js ================================================ import{ESP32C6ROM as e}from"./esp32c6-snJpA5DJ.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32-C61`,this.IMAGE_CHIP_ID=20,this.CHIP_DETECT_MAGIC_VALUE=[871374959,606167151],this.UART_DATE_REG_ADDR=1610612860,this.EFUSE_BASE=1611352064,this.EFUSE_BLOCK1_ADDR=this.EFUSE_BASE+68,this.MAC_EFUSE_REG=this.EFUSE_BASE+68,this.EFUSE_RD_REG_BASE=this.EFUSE_BASE+48,this.EFUSE_PURPOSE_KEY0_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY0_SHIFT=0,this.EFUSE_PURPOSE_KEY1_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY1_SHIFT=4,this.EFUSE_PURPOSE_KEY2_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY2_SHIFT=8,this.EFUSE_PURPOSE_KEY3_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY3_SHIFT=12,this.EFUSE_PURPOSE_KEY4_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY4_SHIFT=16,this.EFUSE_PURPOSE_KEY5_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY5_SHIFT=20,this.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG=this.EFUSE_RD_REG_BASE,this.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT=1<<20,this.EFUSE_SPI_BOOT_CRYPT_CNT_REG=this.EFUSE_BASE+48,this.EFUSE_SPI_BOOT_CRYPT_CNT_MASK=7<<23,this.EFUSE_SECURE_BOOT_EN_REG=this.EFUSE_BASE+52,this.EFUSE_SECURE_BOOT_EN_MASK=1<<26,this.FLASH_FREQUENCY={"80m":15,"40m":0,"20m":2},this.MEMORY_MAP=[[0,65536,`PADDING`],[1098907648,1107296256,`DROM`],[1082130432,1082523648,`DRAM`],[1082130432,1082523648,`BYTE_ACCESSIBLE`],[1074048e3,1074069504,`DROM_MASK`],[1073741824,1074048e3,`IROM_MASK`],[1090519040,1098907648,`IROM`],[1082130432,1082523648,`IRAM`],[1342177280,1342193664,`RTC_IRAM`],[1342177280,1342193664,`RTC_DRAM`],[1611653120,1611661312,`MEM_INTERNAL2`]],this.UF2_FAMILY_ID=2010665156,this.EFUSE_MAX_KEY=5,this.KEY_PURPOSES={0:`USER/EMPTY`,1:`ECDSA_KEY`,2:`XTS_AES_256_KEY_1`,3:`XTS_AES_256_KEY_2`,4:`XTS_AES_128_KEY`,5:`HMAC_DOWN_ALL`,6:`HMAC_DOWN_JTAG`,7:`HMAC_DOWN_DIGITAL_SIGNATURE`,8:`HMAC_UP`,9:`SECURE_BOOT_DIGEST0`,10:`SECURE_BOOT_DIGEST1`,11:`SECURE_BOOT_DIGEST2`,12:`KM_INIT_KEY`,13:`XTS_AES_256_KEY_1_PSRAM`,14:`XTS_AES_256_KEY_2_PSRAM`,15:`XTS_AES_128_KEY_PSRAM`}}async getPkgVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+8)>>26&7}async getMinorChipVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+8)>>0&15}async getMajorChipVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+8)>>4&3}async getChipDescription(e){let t=await this.getPkgVersion(e),n;n=t===0?`ESP32-C61`:`unknown ESP32-C61`;let r=await this.getMajorChipVersion(e),i=await this.getMinorChipVersion(e);return`${n} (revision v${r}.${i})`}async getChipFeatures(e){return[`WiFi 6`,`BT 5`]}async readMac(e){let t=await e.readReg(this.MAC_EFUSE_REG);t>>>=0;let n=await e.readReg(this.MAC_EFUSE_REG+4);n=n>>>0&65535;let r=new Uint8Array(6);return r[0]=n>>8&255,r[1]=n&255,r[2]=t>>24&255,r[3]=t>>16&255,r[4]=t>>8&255,r[5]=t&255,this._d2h(r[0])+`:`+this._d2h(r[1])+`:`+this._d2h(r[2])+`:`+this._d2h(r[3])+`:`+this._d2h(r[4])+`:`+this._d2h(r[5])}};export{t as ESP32C61ROM}; ================================================ FILE: docs/assets/esp32h2-CCGwb3vw.js ================================================ import{t as e}from"./rom-D7P4LvQr.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32-H2`,this.IMAGE_CHIP_ID=16,this.EFUSE_BASE=1611335680,this.EFUSE_BLOCK1_ADDR=this.EFUSE_BASE+68,this.MAC_EFUSE_REG=this.EFUSE_BASE+68,this.UART_CLKDIV_REG=1072955412,this.UART_CLKDIV_MASK=1048575,this.UART_DATE_REG_ADDR=1610612860,this.FLASH_WRITE_SIZE=1024,this.BOOTLOADER_FLASH_OFFSET=0,this.FLASH_SIZES={"1MB":0,"2MB":16,"4MB":32,"8MB":48,"16MB":64},this.SPI_REG_BASE=1610620928,this.SPI_USR_OFFS=24,this.SPI_USR1_OFFS=28,this.SPI_USR2_OFFS=32,this.SPI_MOSI_DLEN_OFFS=36,this.SPI_MISO_DLEN_OFFS=40,this.SPI_W0_OFFS=88,this.USB_RAM_BLOCK=2048,this.UARTDEV_BUF_NO_USB=3,this.UARTDEV_BUF_NO=1070526796}async getPkgVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+16)>>0&7}async getMinorChipVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+12)>>18&7}async getMajorChipVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+12)>>21&3}async getChipDescription(e){let t=await this.getPkgVersion(e),n;n=t===0?`ESP32-H2`:`unknown ESP32-H2`;let r=await this.getMajorChipVersion(e),i=await this.getMinorChipVersion(e);return`${n} (revision v${r}.${i})`}async getChipFeatures(e){return[`BT 5 (LE)`,`IEEE802.15.4`,`Single Core`,`96MHz`]}async getCrystalFreq(e){return 32}_d2h(e){let t=(+e).toString(16);return t.length===1?`0`+t:t}async postConnect(e){let t=await e.readReg(this.UARTDEV_BUF_NO)&255;e.debug(`In _post_connect `+t),t==this.UARTDEV_BUF_NO_USB&&(e.ESP_RAM_BLOCK=this.USB_RAM_BLOCK)}async readMac(e){let t=await e.readReg(this.MAC_EFUSE_REG);t>>>=0;let n=await e.readReg(this.MAC_EFUSE_REG+4);n=n>>>0&65535;let r=new Uint8Array(6);return r[0]=n>>8&255,r[1]=n&255,r[2]=t>>24&255,r[3]=t>>16&255,r[4]=t>>8&255,r[5]=t&255,this._d2h(r[0])+`:`+this._d2h(r[1])+`:`+this._d2h(r[2])+`:`+this._d2h(r[3])+`:`+this._d2h(r[4])+`:`+this._d2h(r[5])}getEraseSize(e,t){return t}};export{t as ESP32H2ROM}; ================================================ FILE: docs/assets/esp32p4-BE8Lllij.js ================================================ import{ESP32ROM as e}from"./esp32-DR_yen0A.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32-P4`,this.IMAGE_CHIP_ID=18,this.IROM_MAP_START=1073741824,this.IROM_MAP_END=1275068416,this.DROM_MAP_START=1073741824,this.DROM_MAP_END=1275068416,this.BOOTLOADER_FLASH_OFFSET=8192,this.CHIP_DETECT_MAGIC_VALUE=[0,182303440],this.UART_DATE_REG_ADDR=1343004812,this.EFUSE_BASE=1343410176,this.EFUSE_BLOCK1_ADDR=this.EFUSE_BASE+68,this.MAC_EFUSE_REG=this.EFUSE_BASE+68,this.SPI_REG_BASE=1342754816,this.SPI_USR_OFFS=24,this.SPI_USR1_OFFS=28,this.SPI_USR2_OFFS=32,this.SPI_MOSI_DLEN_OFFS=36,this.SPI_MISO_DLEN_OFFS=40,this.SPI_W0_OFFS=88,this.EFUSE_RD_REG_BASE=this.EFUSE_BASE+48,this.EFUSE_PURPOSE_KEY0_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY0_SHIFT=24,this.EFUSE_PURPOSE_KEY1_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY1_SHIFT=28,this.EFUSE_PURPOSE_KEY2_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY2_SHIFT=0,this.EFUSE_PURPOSE_KEY3_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY3_SHIFT=4,this.EFUSE_PURPOSE_KEY4_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY4_SHIFT=8,this.EFUSE_PURPOSE_KEY5_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY5_SHIFT=12,this.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG=this.EFUSE_RD_REG_BASE,this.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT=1<<20,this.EFUSE_SPI_BOOT_CRYPT_CNT_REG=this.EFUSE_BASE+52,this.EFUSE_SPI_BOOT_CRYPT_CNT_MASK=7<<18,this.EFUSE_SECURE_BOOT_EN_REG=this.EFUSE_BASE+56,this.EFUSE_SECURE_BOOT_EN_MASK=1<<20,this.PURPOSE_VAL_XTS_AES256_KEY_1=2,this.PURPOSE_VAL_XTS_AES256_KEY_2=3,this.PURPOSE_VAL_XTS_AES128_KEY=4,this.SUPPORTS_ENCRYPTED_FLASH=!0,this.FLASH_ENCRYPTED_WRITE_ALIGN=16,this.MEMORY_MAP=[[0,65536,`PADDING`],[1073741824,1275068416,`DROM`],[1341128704,1341784064,`DRAM`],[1341128704,1341784064,`BYTE_ACCESSIBLE`],[1337982976,1338114048,`DROM_MASK`],[1337982976,1338114048,`IROM_MASK`],[1073741824,1275068416,`IROM`],[1341128704,1341784064,`IRAM`],[1343258624,1343291392,`RTC_IRAM`],[1343258624,1343291392,`RTC_DRAM`],[1611653120,1611661312,`MEM_INTERNAL2`]],this.UF2_FAMILY_ID=1026592404,this.EFUSE_MAX_KEY=5,this.KEY_PURPOSES={0:`USER/EMPTY`,1:`ECDSA_KEY`,2:`XTS_AES_256_KEY_1`,3:`XTS_AES_256_KEY_2`,4:`XTS_AES_128_KEY`,5:`HMAC_DOWN_ALL`,6:`HMAC_DOWN_JTAG`,7:`HMAC_DOWN_DIGITAL_SIGNATURE`,8:`HMAC_UP`,9:`SECURE_BOOT_DIGEST0`,10:`SECURE_BOOT_DIGEST1`,11:`SECURE_BOOT_DIGEST2`,12:`KM_INIT_KEY`}}async getPkgVersion(e){let t=this.EFUSE_BLOCK1_ADDR+8;return await e.readReg(t)>>27&7}async getMinorChipVersion(e){let t=this.EFUSE_BLOCK1_ADDR+8;return await e.readReg(t)>>0&15}async getMajorChipVersion(e){let t=this.EFUSE_BLOCK1_ADDR+8;return await e.readReg(t)>>4&3}async getChipDescription(e){return`${await this.getPkgVersion(e)===0?`ESP32-P4`:`unknown ESP32-P4`} (revision v${await this.getMajorChipVersion(e)}.${await this.getMinorChipVersion(e)})`}async getChipFeatures(e){return[`High-Performance MCU`]}async getCrystalFreq(e){return 40}async getFlashVoltage(e){}async overrideVddsdio(e){e.debug(`VDD_SDIO overrides are not supported for ESP32-P4`)}async readMac(e){let t=await e.readReg(this.MAC_EFUSE_REG);t>>>=0;let n=await e.readReg(this.MAC_EFUSE_REG+4);n=n>>>0&65535;let r=new Uint8Array(6);return r[0]=n>>8&255,r[1]=n&255,r[2]=t>>24&255,r[3]=t>>16&255,r[4]=t>>8&255,r[5]=t&255,this._d2h(r[0])+`:`+this._d2h(r[1])+`:`+this._d2h(r[2])+`:`+this._d2h(r[3])+`:`+this._d2h(r[4])+`:`+this._d2h(r[5])}async getFlashCryptConfig(e){}async getSecureBootEnabled(e){return await e.readReg(this.EFUSE_SECURE_BOOT_EN_REG)&this.EFUSE_SECURE_BOOT_EN_MASK}async getKeyBlockPurpose(e,t){if(t<0||t>this.EFUSE_MAX_KEY){e.debug(`Valid key block numbers must be in range 0-${this.EFUSE_MAX_KEY}`);return}let[n,r]=[[this.EFUSE_PURPOSE_KEY0_REG,this.EFUSE_PURPOSE_KEY0_SHIFT],[this.EFUSE_PURPOSE_KEY1_REG,this.EFUSE_PURPOSE_KEY1_SHIFT],[this.EFUSE_PURPOSE_KEY2_REG,this.EFUSE_PURPOSE_KEY2_SHIFT],[this.EFUSE_PURPOSE_KEY3_REG,this.EFUSE_PURPOSE_KEY3_SHIFT],[this.EFUSE_PURPOSE_KEY4_REG,this.EFUSE_PURPOSE_KEY4_SHIFT],[this.EFUSE_PURPOSE_KEY5_REG,this.EFUSE_PURPOSE_KEY5_SHIFT]][t];return await e.readReg(n)>>r&15}async isFlashEncryptionKeyValid(e){let t=[];for(let n=0;n<=this.EFUSE_MAX_KEY;n++){let r=await this.getKeyBlockPurpose(e,n);t.push(r)}return t.find(e=>e===this.PURPOSE_VAL_XTS_AES128_KEY),!0}};export{t as ESP32P4ROM}; ================================================ FILE: docs/assets/esp32s2-klISvvyE.js ================================================ import{t as e}from"./rom-D7P4LvQr.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32-S2`,this.IMAGE_CHIP_ID=2,this.IROM_MAP_START=1074266112,this.IROM_MAP_END=1085800448,this.DROM_MAP_START=1056964608,this.DROM_MAP_END=1061093376,this.CHIP_DETECT_MAGIC_VALUE=[1990],this.SPI_REG_BASE=1061167104,this.SPI_USR_OFFS=24,this.SPI_USR1_OFFS=28,this.SPI_USR2_OFFS=32,this.SPI_MOSI_DLEN_OFFS=36,this.SPI_MISO_DLEN_OFFS=40,this.SPI_W0_OFFS=88,this.SPI_ADDR_REG_MSB=!1,this.MAC_EFUSE_REG=1061265476,this.UART_CLKDIV_REG=1061158932,this.SUPPORTS_ENCRYPTED_FLASH=!0,this.FLASH_ENCRYPTED_WRITE_ALIGN=16,this.EFUSE_BASE=1061265408,this.EFUSE_RD_REG_BASE=this.EFUSE_BASE+48,this.EFUSE_BLOCK1_ADDR=this.EFUSE_BASE+68,this.EFUSE_BLOCK2_ADDR=this.EFUSE_BASE+92,this.EFUSE_PURPOSE_KEY0_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY0_SHIFT=24,this.EFUSE_PURPOSE_KEY1_REG=this.EFUSE_BASE+52,this.EFUSE_PURPOSE_KEY1_SHIFT=28,this.EFUSE_PURPOSE_KEY2_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY2_SHIFT=0,this.EFUSE_PURPOSE_KEY3_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY3_SHIFT=4,this.EFUSE_PURPOSE_KEY4_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY4_SHIFT=8,this.EFUSE_PURPOSE_KEY5_REG=this.EFUSE_BASE+56,this.EFUSE_PURPOSE_KEY5_SHIFT=12,this.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG=this.EFUSE_RD_REG_BASE,this.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT=1<<19,this.EFUSE_SPI_BOOT_CRYPT_CNT_REG=this.EFUSE_BASE+52,this.EFUSE_SPI_BOOT_CRYPT_CNT_MASK=7<<18,this.EFUSE_SECURE_BOOT_EN_REG=this.EFUSE_BASE+56,this.EFUSE_SECURE_BOOT_EN_MASK=1<<20,this.EFUSE_RD_REPEAT_DATA3_REG=this.EFUSE_BASE+60,this.EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK=512,this.PURPOSE_VAL_XTS_AES256_KEY_1=2,this.PURPOSE_VAL_XTS_AES256_KEY_2=3,this.PURPOSE_VAL_XTS_AES128_KEY=4,this.UARTDEV_BUF_NO=1073741076,this.UARTDEV_BUF_NO_USB_OTG=2,this.USB_RAM_BLOCK=2048,this.GPIO_STRAP_REG=1061175352,this.GPIO_STRAP_SPI_BOOT_MASK=8,this.GPIO_STRAP_VDDSPI_MASK=16,this.RTC_CNTL_OPTION1_REG=1061191976,this.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK=1,this.RTCCNTL_BASE_REG=1061191680,this.RTC_CNTL_WDTCONFIG0_REG=this.RTCCNTL_BASE_REG+148,this.RTC_CNTL_WDTCONFIG1_REG=this.RTCCNTL_BASE_REG+152,this.RTC_CNTL_WDTWPROTECT_REG=this.RTCCNTL_BASE_REG+172,this.RTC_CNTL_WDT_WKEY=1356348065,this.MEMORY_MAP=[[0,65536,`PADDING`],[1056964608,1073217536,`DROM`],[1062207488,1073217536,`EXTRAM_DATA`],[1073340416,1073348608,`RTC_DRAM`],[1073340416,1073741824,`BYTE_ACCESSIBLE`],[1073340416,1074208768,`MEM_INTERNAL`],[1073414144,1073741824,`DRAM`],[1073741824,1073848576,`IROM_MASK`],[1073872896,1074200576,`IRAM`],[1074200576,1074208768,`RTC_IRAM`],[1074266112,1082130432,`IROM`],[1342177280,1342185472,`RTC_DATA`]],this.EFUSE_VDD_SPI_REG=this.EFUSE_BASE+52,this.VDD_SPI_XPD=16,this.VDD_SPI_TIEH=32,this.VDD_SPI_FORCE=64,this.UF2_FAMILY_ID=3218951918,this.EFUSE_MAX_KEY=5,this.KEY_PURPOSES={0:`USER/EMPTY`,1:`RESERVED`,2:`XTS_AES_256_KEY_1`,3:`XTS_AES_256_KEY_2`,4:`XTS_AES_128_KEY`,5:`HMAC_DOWN_ALL`,6:`HMAC_DOWN_JTAG`,7:`HMAC_DOWN_DIGITAL_SIGNATURE`,8:`HMAC_UP`,9:`SECURE_BOOT_DIGEST0`,10:`SECURE_BOOT_DIGEST1`,11:`SECURE_BOOT_DIGEST2`},this.UART_CLKDIV_MASK=1048575,this.UART_DATE_REG_ADDR=1610612856,this.FLASH_WRITE_SIZE=1024,this.BOOTLOADER_FLASH_OFFSET=4096,this.FLASH_SIZES={"1MB":0,"2MB":16,"4MB":32,"8MB":48,"16MB":64}}async getPkgVersion(e){let t=this.EFUSE_BLOCK1_ADDR+16;return await e.readReg(t)>>0&15}async getMinorChipVersion(e){let t=await e.readReg(this.EFUSE_BLOCK1_ADDR+12)>>20&1,n=await e.readReg(this.EFUSE_BLOCK1_ADDR+16)>>4&7;return(t<<3)+n}async getMajorChipVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+12)>>18&3}async getFlashVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+12)>>21&15}async getChipDescription(e){let t={0:`ESP32-S2`,1:`ESP32-S2FH2`,2:`ESP32-S2FH4`,102:`ESP32-S2FNR2`,100:`ESP32-S2R2`},n=await this.getFlashCap(e)+await this.getPsramCap(e)*100,r=await this.getMajorChipVersion(e),i=await this.getMinorChipVersion(e);return`${t[n]||`unknown ESP32-S2`} (revision v${r}.${i})`}async getFlashCap(e){return await this.getFlashVersion(e)}async getPsramVersion(e){let t=this.EFUSE_BLOCK1_ADDR+12;return await e.readReg(t)>>28&15}async getPsramCap(e){return await this.getPsramVersion(e)}async getBlock2Version(e){let t=this.EFUSE_BLOCK2_ADDR+16;return await e.readReg(t)>>4&7}async getChipFeatures(e){let t=[`Wi-Fi`],n={0:`No Embedded Flash`,1:`Embedded Flash 2MB`,2:`Embedded Flash 4MB`}[await this.getFlashCap(e)]||`Unknown Embedded Flash`;t.push(n);let r={0:`No Embedded Flash`,1:`Embedded PSRAM 2MB`,2:`Embedded PSRAM 4MB`}[await this.getPsramCap(e)]||`Unknown Embedded PSRAM`;t.push(r);let i={0:`No calibration in BLK2 of efuse`,1:`ADC and temperature sensor calibration in BLK2 of efuse V1`,2:`ADC and temperature sensor calibration in BLK2 of efuse V2`}[await this.getBlock2Version(e)]||`Unknown Calibration in BLK2`;return t.push(i),t}async getCrystalFreq(e){return 40}_d2h(e){let t=(+e).toString(16);return t.length===1?`0`+t:t}async readMac(e){let t=await e.readReg(this.MAC_EFUSE_REG);t>>>=0;let n=await e.readReg(this.MAC_EFUSE_REG+4);n=n>>>0&65535;let r=new Uint8Array(6);return r[0]=n>>8&255,r[1]=n&255,r[2]=t>>24&255,r[3]=t>>16&255,r[4]=t>>8&255,r[5]=t&255,this._d2h(r[0])+`:`+this._d2h(r[1])+`:`+this._d2h(r[2])+`:`+this._d2h(r[3])+`:`+this._d2h(r[4])+`:`+this._d2h(r[5])}getEraseSize(e,t){return t}async usingUsbOtg(e){return(await e.readReg(this.UARTDEV_BUF_NO)&255)===this.UARTDEV_BUF_NO_USB_OTG}async postConnect(e){let t=await this.usingUsbOtg(e);e.debug(`In _post_connect using USB OTG ?`+t),t&&(e.ESP_RAM_BLOCK=this.USB_RAM_BLOCK)}};export{t as ESP32S2ROM}; ================================================ FILE: docs/assets/esp32s3-CjJ5O_5u.js ================================================ import{t as e}from"./rom-D7P4LvQr.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP32-S3`,this.IMAGE_CHIP_ID=9,this.EFUSE_BASE=1610641408,this.MAC_EFUSE_REG=this.EFUSE_BASE+68,this.EFUSE_BLOCK1_ADDR=this.EFUSE_BASE+68,this.EFUSE_BLOCK2_ADDR=this.EFUSE_BASE+92,this.UART_CLKDIV_REG=1610612756,this.UART_CLKDIV_MASK=1048575,this.UART_DATE_REG_ADDR=1610612864,this.FLASH_WRITE_SIZE=1024,this.BOOTLOADER_FLASH_OFFSET=0,this.FLASH_SIZES={"1MB":0,"2MB":16,"4MB":32,"8MB":48,"16MB":64},this.SPI_REG_BASE=1610620928,this.SPI_USR_OFFS=24,this.SPI_USR1_OFFS=28,this.SPI_USR2_OFFS=32,this.SPI_MOSI_DLEN_OFFS=36,this.SPI_MISO_DLEN_OFFS=40,this.SPI_W0_OFFS=88,this.USB_RAM_BLOCK=2048,this.UARTDEV_BUF_NO_USB=3,this.UARTDEV_BUF_NO=1070526796}async getChipDescription(e){let t=await this.getMajorChipVersion(e),n=await this.getMinorChipVersion(e),r=await this.getPkgVersion(e);return`${{0:`ESP32-S3 (QFN56)`,1:`ESP32-S3-PICO-1 (LGA56)`}[r]||`unknown ESP32-S3`} (revision v${t}.${n})`}async getPkgVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+12)>>21&7}async getRawMinorChipVersion(e){let t=await e.readReg(this.EFUSE_BLOCK1_ADDR+20)>>23&1,n=await e.readReg(this.EFUSE_BLOCK1_ADDR+12)>>18&7;return(t<<3)+n}async getMinorChipVersion(e){let t=await this.getRawMinorChipVersion(e);return await this.isEco0(e,t)?0:this.getRawMinorChipVersion(e)}async getRawMajorChipVersion(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+20)>>24&3}async getMajorChipVersion(e){let t=await this.getRawMinorChipVersion(e);return await this.isEco0(e,t)?0:this.getRawMajorChipVersion(e)}async getBlkVersionMajor(e){return await e.readReg(this.EFUSE_BLOCK2_ADDR+16)>>0&3}async getBlkVersionMinor(e){return await e.readReg(this.EFUSE_BLOCK1_ADDR+12)>>24&7}async isEco0(e,t){return(t&7)==0&&await this.getBlkVersionMajor(e)===1&&await this.getBlkVersionMinor(e)===1}async getFlashCap(e){let t=this.EFUSE_BASE+68+12;return await e.readReg(t)>>27&7}async getFlashVendor(e){let t=this.EFUSE_BASE+68+16,n=await e.readReg(t)>>0&7;return{1:`XMC`,2:`GD`,3:`FM`,4:`TT`,5:`BY`}[n]||``}async getPsramCap(e){let t=this.EFUSE_BASE+68+16;return await e.readReg(t)>>3&3}async getPsramVendor(e){let t=this.EFUSE_BASE+68+16,n=await e.readReg(t)>>7&3;return{1:`AP_3v3`,2:`AP_1v8`}[n]||``}async getChipFeatures(e){let t=[`Wi-Fi`,`BLE`],n={0:null,1:`Embedded Flash 8MB`,2:`Embedded Flash 4MB`},r=await this.getFlashCap(e),i=await this.getFlashVendor(e),a=n[r],o=a===void 0?`Unknown Embedded Flash`:a;a!==null&&t.push(`${o} (${i})`);let s={0:null,1:`Embedded PSRAM 8MB`,2:`Embedded PSRAM 2MB`},c=await this.getPsramCap(e),l=await this.getPsramVendor(e),u=s[c],d=u===void 0?`Unknown Embedded PSRAM`:u;return u!==null&&t.push(`${d} (${l})`),t}async getCrystalFreq(e){return 40}_d2h(e){let t=(+e).toString(16);return t.length===1?`0`+t:t}async postConnect(e){let t=await e.readReg(this.UARTDEV_BUF_NO)&255;e.debug(`In _post_connect `+t),t==this.UARTDEV_BUF_NO_USB&&(e.ESP_RAM_BLOCK=this.USB_RAM_BLOCK)}async readMac(e){let t=await e.readReg(this.MAC_EFUSE_REG);t>>>=0;let n=await e.readReg(this.MAC_EFUSE_REG+4);n=n>>>0&65535;let r=new Uint8Array(6);return r[0]=n>>8&255,r[1]=n&255,r[2]=t>>24&255,r[3]=t>>16&255,r[4]=t>>8&255,r[5]=t&255,this._d2h(r[0])+`:`+this._d2h(r[1])+`:`+this._d2h(r[2])+`:`+this._d2h(r[3])+`:`+this._d2h(r[4])+`:`+this._d2h(r[5])}getEraseSize(e,t){return t}};export{t as ESP32S3ROM}; ================================================ FILE: docs/assets/esp8266-BCrKhPpV.js ================================================ import{t as e}from"./rom-D7P4LvQr.js";var t=class extends e{constructor(){super(...arguments),this.CHIP_NAME=`ESP8266`,this.CHIP_DETECT_MAGIC_VALUE=[4293968129],this.EFUSE_RD_REG_BASE=1072693328,this.UART_CLKDIV_REG=1610612756,this.UART_CLKDIV_MASK=1048575,this.XTAL_CLK_DIVIDER=2,this.FLASH_WRITE_SIZE=16384,this.BOOTLOADER_FLASH_OFFSET=0,this.UART_DATE_REG_ADDR=0,this.FLASH_SIZES={"512KB":0,"256KB":16,"1MB":32,"2MB":48,"4MB":64,"2MB-c1":80,"4MB-c1":96,"8MB":128,"16MB":144},this.SPI_REG_BASE=1610613248,this.SPI_USR_OFFS=28,this.SPI_USR1_OFFS=32,this.SPI_USR2_OFFS=36,this.SPI_MOSI_DLEN_OFFS=0,this.SPI_MISO_DLEN_OFFS=0,this.SPI_W0_OFFS=64,this.getChipFeatures=async e=>{let t=[`WiFi`];return await this.getChipDescription(e)==`ESP8285`&&t.push(`Embedded Flash`),t}}async readEfuse(e,t){let n=this.EFUSE_RD_REG_BASE+4*t;return e.debug(`Read efuse `+n),await e.readReg(n)}async getChipDescription(e){let t=await this.readEfuse(e,2);return await this.readEfuse(e,0)&16|t&65536?`ESP8285`:`ESP8266EX`}async getCrystalFreq(e){let t=await e.readReg(this.UART_CLKDIV_REG)&this.UART_CLKDIV_MASK,n=e.transport.baudrate*t/1e6/this.XTAL_CLK_DIVIDER,r;return r=n>33?40:26,Math.abs(r-n)>1&&e.info(`WARNING: Detected crystal freq `+n+`MHz is quite different to normalized freq `+r+`MHz. Unsupported crystal in use?`),r}_d2h(e){let t=(+e).toString(16);return t.length===1?`0`+t:t}async readMac(e){let t=await this.readEfuse(e,0);t>>>=0;let n=await this.readEfuse(e,1);n>>>=0;let r=await this.readEfuse(e,3);r>>>=0;let i=new Uint8Array(6);return r==0?n>>16&255?(n>>16&255)==1?(i[0]=172,i[1]=208,i[2]=116):e.error(`Unknown OUI`):(i[0]=24,i[1]=254,i[2]=52):(i[0]=r>>16&255,i[1]=r>>8&255,i[2]=r&255),i[3]=n>>8&255,i[4]=n&255,i[5]=t>>24&255,this._d2h(i[0])+`:`+this._d2h(i[1])+`:`+this._d2h(i[2])+`:`+this._d2h(i[3])+`:`+this._d2h(i[4])+`:`+this._d2h(i[5])}getEraseSize(e,t){return t}};export{t as ESP8266ROM}; ================================================ FILE: docs/assets/index-C3ZwmNhv.css ================================================ :root{--esp-tools-button-color:#5ac060;--mdc-typography-body1-line-height:2rem;--mdc-typography-body1-font-size:1.6rem;--mdc-typography-headline6-font-size:1.8rem;--mdc-typography-headline5-font-size:2rem;--mdc-typography-headline4-font-size:2.4rem;--mdc-typography-headline3-font-size:3rem;--mdc-typography-headline2-font-size:3.6rem;--mdc-typography-headline1-font-size:4.8rem;--mdc-typography-button-font-size:1.6rem;--mdc-typography-caption-font-size:1.4rem;--mdc-typography-overline-font-size:1.4rem;--md-sys-typescale-body-large-size:1.6rem;--md-sys-typescale-body-large-line-height:2.4rem;--md-sys-typescale-body-medium-size:1.4rem;--md-sys-typescale-body-medium-line-height:2rem;--md-sys-typescale-body-small-size:1.2rem;--md-sys-typescale-body-small-line-height:1.6rem;--md-sys-typescale-label-large-size:1.4rem;--md-sys-typescale-label-large-line-height:2rem;--md-sys-typescale-label-medium-size:1.2rem;--md-sys-typescale-label-medium-line-height:1.6rem;--md-sys-typescale-label-small-size:1.1rem;--md-sys-typescale-label-small-line-height:1.6rem;--md-sys-typescale-title-large-size:2.2rem;--md-sys-typescale-title-large-line-height:2.8rem;--md-sys-typescale-title-medium-size:1.6rem;--md-sys-typescale-title-medium-line-height:2.4rem;--md-sys-typescale-title-small-size:1.4rem;--md-sys-typescale-title-small-line-height:2rem;--md-sys-typescale-headline-large-size:3.2rem;--md-sys-typescale-headline-large-line-height:4rem;--md-sys-typescale-headline-medium-size:2.8rem;--md-sys-typescale-headline-medium-line-height:3.6rem;--md-sys-typescale-headline-small-size:2.4rem;--md-sys-typescale-headline-small-line-height:3.2rem}#root{text-align:center;box-sizing:border-box;flex-direction:column;width:100%;max-width:100%;min-height:100svh;margin:0 auto;font-size:1.6rem;display:flex}body{color:#e0e0e0;background:#141414;justify-content:center;align-items:center;margin:0;font-family:Trebuchet MS,sans-serif;font-size:1.6rem;display:flex}html{font-size:62.5%}*{box-sizing:border-box;margin:0;padding:0}.container{width:100%;max-width:480px}h1{color:#fff;margin-bottom:.3rem;font-size:1.3rem;font-weight:600}.page-sub{color:#555;margin-bottom:2rem;font-size:.8rem}.tools{flex-direction:column;gap:.75rem;margin-bottom:1.5rem;display:flex}a.tool-card{background:#1e1e1e;border:1px solid #2e2e2e;border-radius:10px;padding:1.1rem 1.25rem;text-decoration:none;transition:border-color .15s,background .15s;display:block}a.tool-card:hover{background:#222;border-color:#3a3a3a}.tool-title{color:#e0e0e0;margin-bottom:.25rem;font-size:.95rem;font-weight:600}.tool-desc{color:#666;font-size:.8rem}.more{color:#555;padding-left:.25rem;font-size:.78rem}.card{text-align:center;background:#1e1e1e;border:1px solid #2e2e2e;border-radius:12px;flex-direction:column;align-items:center;gap:20px;width:100%;max-width:600px;margin:auto;padding:2.5rem;display:flex}.card p{color:#aaa;font-size:1.8rem;line-height:1.7}.card strong{color:#e0e0e0}.card .warn{color:#c0605a;font-size:1.6rem;font-weight:600}.card .success{color:#5ac060;font-size:1.6rem;font-weight:600}.card a{color:#5ac060;font-weight:500;transition:color .2s ease-in-out}.card a:hover{color:#7cd080}.header{align-items:center;width:100%;margin-top:1rem;margin-bottom:2rem;font-size:2rem;display:flex}.header>*{flex:1;justify-content:center;align-items:center;display:flex}.header .nav-btn{color:#e0e0e0;cursor:pointer;background:0 0;border:2px solid #3a3a3a;border-radius:8px;margin:0 auto;padding:.8rem 1.6rem;font-family:inherit;font-size:1.6rem;font-weight:500;transition:all .2s ease-in-out}.header .nav-btn:hover{color:#fff;background:#2a2a2a;border-color:#5a9e6f;transform:translateY(-2px);box-shadow:0 4px 12px #0003}.header .nav-btn:active{box-shadow:none;transform:translateY(0)}.header a{color:#e0e0e0;align-items:center;gap:.5rem;text-decoration:none;display:flex}.header .logo{justify-content:center;align-items:center;display:flex}.header .logo .github{margin-left:4rem;transition:transform .2s ease-in-out}.header .logo .github:hover{transform:scale(1.1)}.header .logo .logo-text{flex-direction:column;gap:.5rem;display:flex}.header .logo .logo-text span{color:#ccc;font-size:2.2rem}.header .logo .logo-text span:nth-child(2){color:#666;font-size:2rem}.connect-robot{text-align:left;background:#1e1e1e;border:1px solid #2e2e2e;border-radius:12px;flex-direction:column;width:100%;max-width:480px;padding:2.5rem;display:flex}.connect-robot .subtitle{color:#ccc;font-size:1.4rem;line-height:1.5}.connect-robot .title{color:#e0e0e0;margin-bottom:.75rem;font-size:2.2rem;font-weight:600}.connect-robot .toggle-row{justify-content:center;align-items:center;gap:.8rem;display:flex}.connect-robot .toggle-row .toggle-label{color:#aaa;-webkit-user-select:none;user-select:none;cursor:pointer;font-size:1.2rem}.connect-robot .toggle-row .badge{color:#a07840;text-transform:uppercase;letter-spacing:.05em;background:#2a2218;border:1px solid #3a3020;border-radius:4px;padding:.2rem .5rem;font-size:.85rem;font-weight:600}.connect-robot .toggle-row .switch{flex-shrink:0;width:38px;height:22px;display:inline-block;position:relative}.connect-robot .toggle-row .switch input{opacity:0;width:0;height:0}.connect-robot .toggle-row .switch input:checked+.slider{background:#2a3a2a;border-color:#3a5a3a}.connect-robot .toggle-row .switch input:checked+.slider:before{background:#5a9e6f;transform:translate(16px)}.connect-robot .toggle-row .slider{cursor:pointer;background:#2e2e2e;border:1px solid #3a3a3a;border-radius:20px;transition:background .2s,border-color .2s;position:absolute;inset:0}.connect-robot .toggle-row .slider:before{content:"";background:#888;border-radius:50%;width:14px;height:14px;transition:transform .2s,background .2s;position:absolute;top:3px;left:3px}.connect-robot button.connect{color:#e0e0e0;cursor:pointer;background:#2a2a2a;border:1px solid #3a3a3a;border-radius:6px;align-self:center;padding:.8rem 1.6rem;font-family:inherit;font-size:1.6rem;font-weight:500;transition:background .15s,border-color .15s}.connect-robot button.connect:hover:not(:disabled){background:#333;border-color:#4a4a4a}.connect-robot button.connect:disabled{opacity:.6;cursor:not-allowed}.connect-robot .status{color:#888;align-self:center;font-size:1.4rem}.info-cards{flex-direction:column;gap:1.5rem;margin-top:2rem;margin-bottom:2rem;display:flex}.robot-info{box-sizing:border-box;background:#1e1e1e;border:1px solid #2e2e2e;border-radius:12px;width:100%;max-width:550px;padding:1.5rem;font-size:1.8rem}.robot-info .info-header{flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:1rem;margin-bottom:1.5rem;display:flex}.robot-info .info-header .title-group{flex-direction:column;gap:.3rem;display:flex}.robot-info .info-header .title-group h2{color:#fff;margin:0;font-size:2rem;font-weight:600}.robot-info .info-header .title-group .time-ago{color:#888;min-width:15rem;font-size:1.2rem}.robot-info .info-header .action-group{align-items:center;gap:.8rem;display:flex}.robot-info .info-header .action-group .tooltip-wrap{display:inline-flex}.robot-info .info-header .action-group .action-btn{color:#e0e0e0;cursor:pointer;background:#2a2a2a;border:1px solid #3a3a3a;border-radius:6px;padding:.5rem 1rem;font-family:inherit;font-size:1.3rem;font-weight:500;transition:background .15s,border-color .15s,opacity .15s}.robot-info .info-header .action-group .action-btn:hover:not(:disabled){background:#333;border-color:#4a4a4a}.robot-info .info-header .action-group .action-btn:disabled{opacity:.4;cursor:not-allowed}.robot-info .no-data{color:#666;margin-bottom:1rem;font-size:1.4rem;font-style:italic}.robot-info .table{flex-direction:column;width:100%;display:flex}.robot-info .table .row{background-color:#3a3a3a;justify-content:space-between;padding:.8rem;display:flex}.robot-info .table .row:nth-child(odd){background-color:#2a2a2a}.robot-info .table .row .key{color:#aaa;font-weight:500}.gen-picker .gen-pick{cursor:pointer;color:var(--esp-tools-button-text-color,#fff);background-color:var(--esp-tools-button-color,#03a9f4);border:none;border-radius:4px;padding:8px 28px;font-size:14px;position:relative;box-shadow:0 2px 2px #00000024,0 3px 1px -2px #0000001f,0 1px 5px #0003}.gen-picker{justify-content:center;gap:1rem;display:flex}.unsupported{text-align:center}.unsupported p{color:#aaa;font-size:1.8rem;line-height:1.7}.unsupported strong{color:#e0e0e0}.unsupported .warn{color:#c0605a;font-size:1.6rem;font-weight:600}.unsupported .success{color:#5ac060;font-size:1.6rem;font-weight:600}.home{text-align:center;max-width:900px}.home p{color:#aaa;font-size:1.8rem;line-height:1.7}.home strong{color:#e0e0e0}.home .success{color:#5ac060;font-size:2.2rem;font-weight:600} ================================================ FILE: docs/assets/index-CCRny9cW.js ================================================ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/install-dialog-nQsOFibo.js","assets/styles-Bnf3HqYs.js","assets/no-port-picked-DMlthFiI.js"])))=>i.map(i=>d[i]); var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ee(e,t){return E(e.type,t,e.props)}function te(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function ne(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var re=/\/+/g;function ie(e,t){return typeof e==`object`&&e&&e.key!=null?ne(``+e.key):t.toString(36)}function ae(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function oe(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,oe(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+ie(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(re,`$&/`)+`/`),oe(o,r,i,``,function(e){return e})):o!=null&&(te(o)&&(o=ee(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(re,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=l()})),d=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,te());else{var t=n(l);t!==null&&ie(x,t.startTime-e)}}var S=!1,C=-1,w=5,T=-1;function E(){return g?!0:!(e.unstable_now()-Tt&&E());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&ie(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?te():S=!1}}}var te;if(typeof y==`function`)te=function(){y(ee)};else if(typeof MessageChannel<`u`){var ne=new MessageChannel,re=ne.port2;ne.port1.onmessage=ee,te=function(){re.postMessage(null)}}else te=function(){_(ee,0)};function ie(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,ie(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,te()))),r},e.unstable_shouldYield=E,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),f=o(((e,t)=>{t.exports=d()})),p=o((e=>{var t=u();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=p()})),h=o((e=>{var t=f(),n=u(),r=m();function i(e){var t=`https://react.dev/errors/`+e;if(1de||(e.current=ue[de],ue[de]=null,de--)}function k(e,t){de++,ue[de]=e.current,e.current=t}var me=fe(null),he=fe(null),ge=fe(null),_e=fe(null);function ve(e,t){switch(k(ge,t),k(he,e),k(me,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}pe(me),k(me,e)}function ye(){pe(me),pe(he),pe(ge)}function be(e){e.memoizedState!==null&&k(_e,e);var t=me.current,n=Hd(t,e.type);t!==n&&(k(he,e),k(me,n))}function xe(e){he.current===e&&(pe(me),pe(he)),_e.current===e&&(pe(_e),Qf._currentValue=le)}var Se,Ce;function we(e){if(Se===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);Se=t&&t[1]||``,Ce=-1)`:-1i||c[r]!==l[i]){var u=` `+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{Te=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?we(n):``}function De(e,t){switch(e.tag){case 26:case 27:case 5:return we(e.type);case 16:return we(`Lazy`);case 13:return e.child!==t&&t!==null?we(`Suspense Fallback`):we(`Suspense`);case 19:return we(`SuspenseList`);case 0:case 15:return Ee(e.type,!1);case 11:return Ee(e.type.render,!1);case 1:return Ee(e.type,!0);case 31:return we(`Activity`);default:return``}}function Oe(e){try{var t=``,n=null;do t+=De(e,n),n=e,e=e.return;while(e);return t}catch(e){return` Error generating stack: `+e.message+` `+e.stack}}var ke=Object.prototype.hasOwnProperty,Ae=t.unstable_scheduleCallback,je=t.unstable_cancelCallback,Me=t.unstable_shouldYield,Ne=t.unstable_requestPaint,Pe=t.unstable_now,Fe=t.unstable_getCurrentPriorityLevel,Ie=t.unstable_ImmediatePriority,Le=t.unstable_UserBlockingPriority,Re=t.unstable_NormalPriority,ze=t.unstable_LowPriority,Be=t.unstable_IdlePriority,Ve=t.log,He=t.unstable_setDisableYieldValue,Ue=null,We=null;function Ge(e){if(typeof Ve==`function`&&He(e),We&&typeof We.setStrictMode==`function`)try{We.setStrictMode(Ue,e)}catch{}}var A=Math.clz32?Math.clz32:Je,Ke=Math.log,qe=Math.LN2;function Je(e){return e>>>=0,e===0?32:31-(Ke(e)/qe|0)|0}var Ye=256,Xe=262144,Ze=4194304;function Qe(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function $e(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Qe(n))):i=Qe(o):i=Qe(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Qe(n))):i=Qe(o)):i=Qe(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function et(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function tt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function nt(){var e=Ze;return Ze<<=1,!(Ze&62914560)&&(Ze=4194304),e}function rt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function it(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function at(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),_n=!1;if(gn)try{var vn={};Object.defineProperty(vn,`passive`,{get:function(){_n=!0}}),window.addEventListener(`test`,vn,vn),window.removeEventListener(`test`,vn,vn)}catch{_n=!1}var yn=null,bn=null,xn=null;function Sn(){if(xn)return xn;var e,t=bn,n=t.length,r,i=`value`in yn?yn.value:yn.textContent,a=i.length;for(e=0;e=$n),nr=` `,rr=!1;function ir(e,t){switch(e){case`keyup`:return Zn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function ar(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var or=!1;function sr(e,t){switch(e){case`compositionend`:return ar(t);case`keypress`:return t.which===32?(rr=!0,nr):null;case`textInput`:return e=t.data,e===nr&&rr?null:e;default:return null}}function cr(e,t){if(or)return e===`compositionend`||!Qn&&ir(e,t)?(e=Sn(),xn=bn=yn=null,or=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Ar(n)}}function Mr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Mr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Nr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Ut(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ut(e.document)}return t}function Pr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Fr=gn&&`documentMode`in document&&11>=document.documentMode,Ir=null,Lr=null,Rr=null,zr=!1;function Br(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;zr||Ir==null||Ir!==Ut(r)||(r=Ir,`selectionStart`in r&&Pr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Rr&&kr(Rr,r)||(Rr=r,r=Ed(Lr,`onSelect`),0>=o,i-=o,Mi=1<<32-A(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),P&&Pi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),P&&Pi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return P&&Pi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),P&&Pi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===te&&Ma(l)===r.type){n(e,r.sibling),c=a(r,o.props),za(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=yi(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=vi(o.type,o.key,o.props,null,e.mode,c),za(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=Si(o,e.mode,c),c.return=e,e=c}return s(e);case te:return o=Ma(o),b(e,r,o,c)}if(ce(o))return h(e,r,o,c);if(ae(o)){if(l=ae(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Ra(o),c);if(o.$$typeof===C)return b(e,r,sa(e,o),c);Ba(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=bi(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{La=0;var i=b(e,t,n,r);return Ia=null,i}catch(t){if(t===Ea||t===Oa)throw t;var a=mi(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Ha=Va(!0),Ua=Va(!1),Wa=!1;function Ga(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ka(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function qa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ja(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=di(e),ui(e,null,n),t}return si(e,r,t,n),di(e)}function Ya(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}function Xa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Za=!1;function Qa(){if(Za){var e=_a;if(e!==null)throw e}}function $a(e,t,n,r){Za=!1;var i=e.updateQueue;Wa=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(J&f)===f:(r&f)===f){f!==0&&f===ga&&(Za=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:Wa=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Gl|=o,e.lanes=o,e.memoizedState=d}}function eo(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function to(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=D.T,s={};D.T=s,Ls(e,!1,t,n);try{var c=i(),l=D.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Is(e,t,ba(c,r),pu(e)):Is(e,t,r,pu(e))}catch(n){Is(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{O.p=a,o!==null&&s.types!==null&&(o.types=s.types),D.T=o}}function Es(){}function Ds(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Os(e).queue;Ts(e,a,t,le,n===null?Es:function(){return ks(e),n(r)})}function Os(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:le,baseState:le,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ro,lastRenderedState:le},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ro,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function ks(e){var t=Os(e);t.next===null&&(t=e.alternate.memoizedState),Is(e,t.next.queue,{},pu())}function As(){return oa(Qf)}function js(){return B().memoizedState}function Ms(){return B().memoizedState}function Ns(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=qa(n);var r=Ja(t,e,n);r!==null&&(hu(r,t,n),Ya(r,t,n)),t={cache:fa()},e.payload=t;return}t=t.return}}function Ps(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Rs(e)?zs(t,n):(n=ci(e,t,n,r),n!==null&&(hu(n,e,r),Bs(n,t,r)))}function Fs(e,t,n){Is(e,t,n,pu())}function Is(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Rs(e))zs(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Or(s,o))return si(e,t,i,0),K===null&&oi(),!1}catch{}if(n=ci(e,t,i,r),n!==null)return hu(n,e,r),Bs(n,t,r),!0}return!1}function Ls(e,t,n,r){if(r={lane:2,revertLane:dd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Rs(e)){if(t)throw Error(i(479))}else t=ci(e,n,r,2),t!==null&&hu(t,e,2)}function Rs(e){var t=e.alternate;return e===L||t!==null&&t===L}function zs(e,t){yo=vo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Bs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,st(e,n)}}var Vs={readContext:oa,use:Io,useCallback:z,useContext:z,useEffect:z,useImperativeHandle:z,useLayoutEffect:z,useInsertionEffect:z,useMemo:z,useReducer:z,useRef:z,useState:z,useDebugValue:z,useDeferredValue:z,useTransition:z,useSyncExternalStore:z,useId:z,useHostTransitionStatus:z,useFormState:z,useActionState:z,useOptimistic:z,useMemoCache:z,useCacheRefresh:z};Vs.useEffectEvent=z;var Hs={readContext:oa,use:Io,useCallback:function(e,t){return No().memoizedState=[e,t===void 0?null:t],e},useContext:oa,useEffect:fs,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),us(4194308,4,vs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return us(4194308,4,e,t)},useInsertionEffect:function(e,t){us(4,2,e,t)},useMemo:function(e,t){var n=No();t=t===void 0?null:t;var r=e();if(bo){Ge(!0);try{e()}finally{Ge(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=No();if(n!==void 0){var i=n(t);if(bo){Ge(!0);try{n(t)}finally{Ge(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ps.bind(null,L,e),[r.memoizedState,e]},useRef:function(e){var t=No();return e={current:e},t.memoizedState=e},useState:function(e){e=Jo(e);var t=e.queue,n=Fs.bind(null,L,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:bs,useDeferredValue:function(e,t){return Cs(No(),e,t)},useTransition:function(){var e=Jo(!1);return e=Ts.bind(null,L,e.queue,!0,!1),No().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=L,a=No();if(P){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),K===null)throw Error(i(349));J&127||Uo(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,fs(Go.bind(null,r,o,e),[e]),r.flags|=2048,cs(9,{destroy:void 0},Wo.bind(null,r,o,n,t),null),n},useId:function(){var e=No(),t=K.identifierPrefix;if(P){var n=Ni,r=Mi;n=(r&~(1<<32-A(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=xo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[mt]=t,o[ht]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Pd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Fc(t)}}return H(t),Ic(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Fc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=ge.current,Ki(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=zi,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[mt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Md(e.nodeValue,n)),e||Ui(t,!0)}else e=Bd(e).createTextNode(r),e[mt]=t,t.stateNode=e}return H(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Ki(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[mt]=t}else qi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;H(t),e=!1}else n=Ji(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(mo(t),t):(mo(t),null);if(t.flags&128)throw Error(i(558))}return H(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Ki(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[mt]=t}else qi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;H(t),a=!1}else a=Ji(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(mo(t),t):(mo(t),null)}return mo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Rc(t,t.updateQueue),H(t),null);case 4:return ye(),e===null&&Sd(t.stateNode.containerInfo),H(t),null;case 10:return ea(t.type),H(t),null;case 19:if(pe(I),r=t.memoizedState,r===null)return H(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)zc(r,!1);else{if(X!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=ho(e),o!==null){for(t.flags|=128,zc(r,!1),e=o.updateQueue,t.updateQueue=e,Rc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)_i(n,e),n=n.sibling;return k(I,I.current&1|2),P&&Pi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Pe()>tu&&(t.flags|=128,a=!0,zc(r,!1),t.lanes=4194304)}else{if(!a)if(e=ho(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Rc(t,e),zc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!P)return H(t),null}else 2*Pe()-r.renderingStartTime>tu&&n!==536870912&&(t.flags|=128,a=!0,zc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(H(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Pe(),e.sibling=null,n=I.current,k(I,a?n&1|2:n&1),P&&Pi(t,r.treeForkCount),e);case 22:case 23:return mo(t),oo(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(H(t),t.subtreeFlags&6&&(t.flags|=8192)):H(t),n=t.updateQueue,n!==null&&Rc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&pe(Sa),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),ea(F),H(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Vc(e,t){switch(Li(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return ea(F),ye(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return xe(t),null;case 31:if(t.memoizedState!==null){if(mo(t),t.alternate===null)throw Error(i(340));qi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(mo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));qi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return pe(I),null;case 4:return ye(),null;case 10:return ea(t.type),null;case 22:case 23:return mo(t),oo(),e!==null&&pe(Sa),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return ea(F),null;case 25:return null;default:return null}}function Hc(e,t){switch(Li(t),t.tag){case 3:ea(F),ye();break;case 26:case 27:case 5:xe(t);break;case 4:ye();break;case 31:t.memoizedState!==null&&mo(t);break;case 13:mo(t);break;case 19:pe(I);break;case 10:ea(t.type);break;case 22:case 23:mo(t),oo(),e!==null&&pe(Sa);break;case 24:ea(F)}}function Uc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Wc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function Gc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{to(t,n)}catch(t){Z(e,e.return,t)}}}function Kc(e,t,n){n.props=Ys(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function qc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function Jc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}function Yc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Z(e,e.return,t)}}function Xc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[ht]=t}catch(t){Z(e,e.return,t)}}function Zc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function Qc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Zc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function $c(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=sn));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for($c(e,t,n),e=e.sibling;e!==null;)$c(e,t,n),e=e.sibling}function el(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(el(e,t,n),e=e.sibling;e!==null;)el(e,t,n),e=e.sibling}function tl(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[mt]=e,t[ht]=n}catch(t){Z(e,e.return,t)}}var nl=!1,U=!1,rl=!1,il=typeof WeakSet==`function`?WeakSet:Set,al=null;function ol(e,t){if(e=e.containerInfo,Rd=sp,e=Nr(e),Pr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,al=t;al!==null;)if(t=al,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,al=e;else for(;al!==null;){switch(t=al,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(o,r,n),o[mt]=e,j(o),r=o;break a;case`link`:var s=Vf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=jr(s,h),v=jr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,D.T=null,n=lu,lu=null;var o=au,s=su;if(iu=0,ou=au=null,su=0,G&6)throw Error(i(331));var c=G;if(G|=4,Fl(o.current),Dl(o,o.current,s,n),G=c,id(0,!1),We&&typeof We.onPostCommitFiberRoot==`function`)try{We.onPostCommitFiberRoot(Ue,o)}catch{}return!0}finally{O.p=a,D.T=r,Vu(e,t)}}function Wu(e,t,n){t=wi(n,t),t=tc(e.stateNode,t,2),e=Ja(e,t,2),e!==null&&(it(e,2),rd(e))}function Z(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(ru===null||!ru.has(r))){e=wi(n,e),n=nc(2),r=Ja(t,n,2),r!==null&&(rc(n,r,t,e),it(r,2),rd(r));break}}t=t.return}}function Gu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new zl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Ul=!0,i.add(n),e=Ku.bind(null,e,t,n),t.then(e,e))}function Ku(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,K===e&&(J&n)===n&&(X===4||X===3&&(J&62914560)===J&&300>Pe()-$l?!(G&2)&&Su(e,0):ql|=n,Yl===J&&(Yl=0)),rd(e)}function qu(e,t){t===0&&(t=nt()),e=li(e,t),e!==null&&(it(e,t),rd(e))}function Ju(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function Yu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),qu(e,n)}function Xu(e,t){return Ae(e,t)}var Zu=null,Qu=null,$u=!1,ed=!1,td=!1,nd=0;function rd(e){e!==Qu&&e.next===null&&(Qu===null?Zu=Qu=e:Qu=Qu.next=e),ed=!0,$u||($u=!0,ud())}function id(e,t){if(!td&&ed){td=!0;do for(var n=!1,r=Zu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-A(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ld(r,a))}else a=J,a=$e(r,r===K?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||et(r,a)||(n=!0,ld(r,a));r=r.next}while(n);td=!1}}function ad(){od()}function od(){ed=$u=!1;var e=0;nd!==0&&Gd()&&(e=nd);for(var t=Pe(),n=null,r=Zu;r!==null;){var i=r.next,a=sd(r,t);a===0?(r.next=null,n===null?Zu=i:n.next=i,i===null&&(Qu=n)):(n=r,(e!==0||a&3)&&(ed=!0)),r=i}iu!==0&&iu!==5||id(e,!1),nd!==0&&(nd=0)}function sd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Gt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),j(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Gt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Gt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Gt(n.imageSizes)+`"]`)):i+=`[href="`+Gt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),j(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Gt(r)+`"][href="`+Gt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),j(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=Et(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);j(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),j(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=Et(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),j(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var a=(a=ge.current)?gf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=Et(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=Et(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=Et(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Af(e){return`href="`+Gt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),j(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Gt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Gt(n.href)+`"]`);if(r)return t.instance=r,j(r),r;var a=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),j(r),Pd(r,`style`,a),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Af(n.href);var o=e.querySelector(jf(a));if(o)return t.state.loading|=4,t.instance=o,j(o),o;r=Mf(n),(a=mf.get(a))&&Rf(r,a),o=(e.ownerDocument||e).createElement(`link`),j(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(a=e.querySelector(Ff(o)))?(t.instance=a,j(a),a):(r=n,(a=mf.get(o))&&(r=h({},n),zf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),j(a),Pd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,j(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),j(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()}))(),_=c(u(),1),v=`modulepreload`,y=function(e){return`/`+e},b={},x=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=y(t,n),t in b)return;b[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:v,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},S=`popstate`;function C(e){return typeof e==`object`&&!!e&&`pathname`in e&&`search`in e&&`hash`in e&&`state`in e&&`key`in e}function w(e={}){function t(e,t){let{pathname:n=`/`,search:r=``,hash:i=``}=ie(e.location.hash.substring(1));return!n.startsWith(`/`)&&!n.startsWith(`.`)&&(n=`/`+n),ne(``,{pathname:n,search:r,hash:i},t.state&&t.state.usr||null,t.state&&t.state.key||`default`)}function n(e,t){let n=e.document.querySelector(`base`),r=``;if(n&&n.getAttribute(`href`)){let t=e.location.href,n=t.indexOf(`#`);r=n===-1?t:t.slice(0,n)}return r+`#`+(typeof t==`string`?t:re(t))}function r(e,t){E(e.pathname.charAt(0)===`/`,`relative pathnames are not supported in hash history.push(${JSON.stringify(t)})`)}return ae(t,n,r,e)}function T(e,t){if(e===!1||e==null)throw Error(t)}function E(e,t){if(!e){typeof console<`u`&&console.warn(t);try{throw Error(t)}catch{}}}function ee(){return Math.random().toString(36).substring(2,10)}function te(e,t){return{usr:e.state,key:e.key,idx:t,masked:e.unstable_mask?{pathname:e.pathname,search:e.search,hash:e.hash}:void 0}}function ne(e,t,n=null,r,i){return{pathname:typeof e==`string`?e:e.pathname,search:``,hash:``,...typeof t==`string`?ie(t):t,state:n,key:t&&t.key||r||ee(),unstable_mask:i}}function re({pathname:e=`/`,search:t=``,hash:n=``}){return t&&t!==`?`&&(e+=t.charAt(0)===`?`?t:`?`+t),n&&n!==`#`&&(e+=n.charAt(0)===`#`?n:`#`+n),e}function ie(e){let t={};if(e){let n=e.indexOf(`#`);n>=0&&(t.hash=e.substring(n),e=e.substring(0,n));let r=e.indexOf(`?`);r>=0&&(t.search=e.substring(r),e=e.substring(0,r)),e&&(t.pathname=e)}return t}function ae(e,t,n,r={}){let{window:i=document.defaultView,v5Compat:a=!1}=r,o=i.history,s=`POP`,c=null,l=u();l??(l=0,o.replaceState({...o.state,idx:l},``));function u(){return(o.state||{idx:null}).idx}function d(){s=`POP`;let e=u(),t=e==null?null:e-l;l=e,c&&c({action:s,location:h.location,delta:t})}function f(e,t){s=`PUSH`;let r=C(e)?e:ne(h.location,e,t);n&&n(r,e),l=u()+1;let d=te(r,l),f=h.createHref(r.unstable_mask||r);try{o.pushState(d,``,f)}catch(e){if(e instanceof DOMException&&e.name===`DataCloneError`)throw e;i.location.assign(f)}a&&c&&c({action:s,location:h.location,delta:1})}function p(e,t){s=`REPLACE`;let r=C(e)?e:ne(h.location,e,t);n&&n(r,e),l=u();let i=te(r,l),d=h.createHref(r.unstable_mask||r);o.replaceState(i,``,d),a&&c&&c({action:s,location:h.location,delta:0})}function m(e){return oe(e)}let h={get action(){return s},get location(){return e(i,o)},listen(e){if(c)throw Error(`A history only accepts one active listener`);return i.addEventListener(S,d),c=e,()=>{i.removeEventListener(S,d),c=null}},createHref(e){return t(i,e)},createURL:m,encodeLocation(e){let t=m(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:f,replace:p,go(e){return o.go(e)}};return h}function oe(e,t=!1){let n=`http://localhost`;typeof window<`u`&&(n=window.location.origin===`null`?window.location.href:window.location.origin),T(n,`No window.location.(origin|href) available to create URL`);let r=typeof e==`string`?e:re(e);return r=r.replace(/ $/,`%20`),!t&&r.startsWith(`//`)&&(r=n+r),new URL(r,n)}function se(e,t,n=`/`){return ce(e,t,n,!1)}function ce(e,t,n,r){let i=Ce((typeof t==`string`?ie(t):t).pathname||`/`,n);if(i==null)return null;let a=O(e);ue(a);let o=null;for(let e=0;o==null&&e{let c={relativePath:s===void 0?e.path||``:s,caseSensitive:e.caseSensitive===!0,childrenIndex:a,route:e};if(c.relativePath.startsWith(`/`)){if(!c.relativePath.startsWith(r)&&o)return;T(c.relativePath.startsWith(r),`Absolute route path "${c.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),c.relativePath=c.relativePath.slice(r.length)}let l=je([r,c.relativePath]),u=n.concat(c);e.children&&e.children.length>0&&(T(e.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${l}".`),O(e.children,t,u,l,o)),!(e.path==null&&!e.index)&&t.push({path:l,score:_e(l,e.index),routesMeta:u})};return e.forEach((e,t)=>{if(e.path===``||!e.path?.includes(`?`))a(e,t);else for(let n of le(e.path))a(e,t,!0,n)}),t}function le(e){let t=e.split(`/`);if(t.length===0)return[];let[n,...r]=t,i=n.endsWith(`?`),a=n.replace(/\?$/,``);if(r.length===0)return i?[a,``]:[a];let o=le(r.join(`/`)),s=[];return s.push(...o.map(e=>e===``?a:[a,e].join(`/`))),i&&s.push(...o),s.map(t=>e.startsWith(`/`)&&t===``?`/`:t)}function ue(e){e.sort((e,t)=>e.score===t.score?ve(e.routesMeta.map(e=>e.childrenIndex),t.routesMeta.map(e=>e.childrenIndex)):t.score-e.score)}var de=/^:[\w-]+$/,fe=3,pe=2,k=1,me=10,he=-2,ge=e=>e===`*`;function _e(e,t){let n=e.split(`/`),r=n.length;return n.some(ge)&&(r+=he),t&&(r+=pe),n.filter(e=>!ge(e)).reduce((e,t)=>e+(de.test(t)?fe:t===``?k:me),r)}function ve(e,t){return e.length===t.length&&e.slice(0,-1).every((e,n)=>e===t[n])?e[e.length-1]-t[t.length-1]:0}function ye(e,t,n=!1){let{routesMeta:r}=e,i={},a=`/`,o=[];for(let e=0;e{if(t===`*`){let e=s[r]||``;o=a.slice(0,a.length-e.length).replace(/(.)\/+$/,`$1`)}let i=s[r];return n&&!i?e[t]=void 0:e[t]=(i||``).replace(/%2F/g,`/`),e},{}),pathname:a,pathnameBase:o,pattern:e}}function xe(e,t=!1,n=!0){E(e===`*`||!e.endsWith(`*`)||e.endsWith(`/*`),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,`/*`)}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,`/*`)}".`);let r=[],i=`^`+e.replace(/\/*\*?$/,``).replace(/^\/*/,`/`).replace(/[\\.*+^${}|()[\]]/g,`\\$&`).replace(/\/:([\w-]+)(\?)?/g,(e,t,n,i,a)=>{if(r.push({paramName:t,isOptional:n!=null}),n){let t=a.charAt(i+e.length);return t&&t!==`/`?`/([^\\/]*)`:`(?:/([^\\/]*))?`}return`/([^\\/]+)`}).replace(/\/([\w-]+)\?(\/|$)/g,`(/$1)?$2`);return e.endsWith(`*`)?(r.push({paramName:`*`}),i+=e===`*`||e===`/*`?`(.*)$`:`(?:\\/(.+)|\\/*)$`):n?i+=`\\/*$`:e!==``&&e!==`/`&&(i+=`(?:(?=\\/|$))`),[new RegExp(i,t?void 0:`i`),r]}function Se(e){try{return e.split(`/`).map(e=>decodeURIComponent(e).replace(/\//g,`%2F`)).join(`/`)}catch(t){return E(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function Ce(e,t){if(t===`/`)return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith(`/`)?t.length-1:t.length,r=e.charAt(n);return r&&r!==`/`?null:e.slice(n)||`/`}var we=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function Te(e,t=`/`){let{pathname:n,search:r=``,hash:i=``}=typeof e==`string`?ie(e):e,a;return n?(n=n.replace(/\/\/+/g,`/`),a=n.startsWith(`/`)?Ee(n.substring(1),`/`):Ee(n,t)):a=t,{pathname:a,search:Ne(r),hash:Pe(i)}}function Ee(e,t){let n=t.replace(/\/+$/,``).split(`/`);return e.split(`/`).forEach(e=>{e===`..`?n.length>1&&n.pop():e!==`.`&&n.push(e)}),n.length>1?n.join(`/`):`/`}function De(e,t,n,r){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function Oe(e){return e.filter((e,t)=>t===0||e.route.path&&e.route.path.length>0)}function ke(e){let t=Oe(e);return t.map((e,n)=>n===t.length-1?e.pathname:e.pathnameBase)}function Ae(e,t,n,r=!1){let i;typeof e==`string`?i=ie(e):(i={...e},T(!i.pathname||!i.pathname.includes(`?`),De(`?`,`pathname`,`search`,i)),T(!i.pathname||!i.pathname.includes(`#`),De(`#`,`pathname`,`hash`,i)),T(!i.search||!i.search.includes(`#`),De(`#`,`search`,`hash`,i)));let a=e===``||i.pathname===``,o=a?`/`:i.pathname,s;if(o==null)s=n;else{let e=t.length-1;if(!r&&o.startsWith(`..`)){let t=o.split(`/`);for(;t[0]===`..`;)t.shift(),--e;i.pathname=t.join(`/`)}s=e>=0?t[e]:`/`}let c=Te(i,s),l=o&&o!==`/`&&o.endsWith(`/`),u=(a||o===`.`)&&n.endsWith(`/`);return!c.pathname.endsWith(`/`)&&(l||u)&&(c.pathname+=`/`),c}var je=e=>e.join(`/`).replace(/\/\/+/g,`/`),Me=e=>e.replace(/\/+$/,``).replace(/^\/*/,`/`),Ne=e=>!e||e===`?`?``:e.startsWith(`?`)?e:`?`+e,Pe=e=>!e||e===`#`?``:e.startsWith(`#`)?e:`#`+e,Fe=class{constructor(e,t,n,r=!1){this.status=e,this.statusText=t||``,this.internal=r,n instanceof Error?(this.data=n.toString(),this.error=n):this.data=n}};function Ie(e){return e!=null&&typeof e.status==`number`&&typeof e.statusText==`string`&&typeof e.internal==`boolean`&&`data`in e}function Le(e){return e.map(e=>e.route.path).filter(Boolean).join(`/`).replace(/\/\/*/g,`/`)||`/`}var Re=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;function ze(e,t){let n=e;if(typeof n!=`string`||!we.test(n))return{absoluteURL:void 0,isExternal:!1,to:n};let r=n,i=!1;if(Re)try{let e=new URL(window.location.href),r=n.startsWith(`//`)?new URL(e.protocol+n):new URL(n),a=Ce(r.pathname,t);r.origin===e.origin&&a!=null?n=a+r.search+r.hash:i=!0}catch{E(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:i,to:n}}Object.getOwnPropertyNames(Object.prototype).sort().join(`\0`);var Be=_.createContext(null);Be.displayName=`DataRouter`;var Ve=_.createContext(null);Ve.displayName=`DataRouterState`;var He=_.createContext(!1),Ue=_.createContext({isTransitioning:!1});Ue.displayName=`ViewTransition`;var We=_.createContext(new Map);We.displayName=`Fetchers`;var Ge=_.createContext(null);Ge.displayName=`Await`;var A=_.createContext(null);A.displayName=`Navigation`;var Ke=_.createContext(null);Ke.displayName=`Location`;var qe=_.createContext({outlet:null,matches:[],isDataRoute:!1});qe.displayName=`Route`;var Je=_.createContext(null);Je.displayName=`RouteError`;var Ye=`REACT_ROUTER_ERROR`,Xe=`REDIRECT`,Ze=`ROUTE_ERROR_RESPONSE`;function Qe(e){if(e.startsWith(`${Ye}:${Xe}:{`))try{let t=JSON.parse(e.slice(28));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`&&typeof t.location==`string`&&typeof t.reloadDocument==`boolean`&&typeof t.replace==`boolean`)return t}catch{}}function $e(e){if(e.startsWith(`${Ye}:${Ze}:{`))try{let t=JSON.parse(e.slice(40));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`)return new Fe(t.status,t.statusText,t.data)}catch{}}function et(e,{relative:t}={}){T(tt(),`useHref() may be used only in the context of a component.`);let{basename:n,navigator:r}=_.useContext(A),{hash:i,pathname:a,search:o}=st(e,{relative:t}),s=a;return n!==`/`&&(s=a===`/`?n:je([n,a])),r.createHref({pathname:s,search:o,hash:i})}function tt(){return _.useContext(Ke)!=null}function nt(){return T(tt(),`useLocation() may be used only in the context of a component.`),_.useContext(Ke).location}var rt=`You should call navigate() in a React.useEffect(), not when your component is first rendered.`;function it(e){_.useContext(A).static||_.useLayoutEffect(e)}function at(){let{isDataRoute:e}=_.useContext(qe);return e?Et():ot()}function ot(){T(tt(),`useNavigate() may be used only in the context of a component.`);let e=_.useContext(Be),{basename:t,navigator:n}=_.useContext(A),{matches:r}=_.useContext(qe),{pathname:i}=nt(),a=JSON.stringify(ke(r)),o=_.useRef(!1);return it(()=>{o.current=!0}),_.useCallback((r,s={})=>{if(E(o.current,rt),!o.current)return;if(typeof r==`number`){n.go(r);return}let c=Ae(r,JSON.parse(a),i,s.relative===`path`);e==null&&t!==`/`&&(c.pathname=c.pathname===`/`?t:je([t,c.pathname])),(s.replace?n.replace:n.push)(c,s.state,s)},[t,n,a,i,e])}_.createContext(null);function st(e,{relative:t}={}){let{matches:n}=_.useContext(qe),{pathname:r}=nt(),i=JSON.stringify(ke(n));return _.useMemo(()=>Ae(e,JSON.parse(i),r,t===`path`),[e,i,r,t])}function ct(e,t){return lt(e,t)}function lt(e,t,n){T(tt(),`useRoutes() may be used only in the context of a component.`);let{navigator:r}=_.useContext(A),{matches:i}=_.useContext(qe),a=i[i.length-1],o=a?a.params:{},s=a?a.pathname:`/`,c=a?a.pathnameBase:`/`,l=a&&a.route;{let e=l&&l.path||``;Dt(s,!l||e.endsWith(`*`)||e.endsWith(`*?`),`You rendered descendant (or called \`useRoutes()\`) at "${s}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. Please change the parent to .`)}let u=nt(),d;if(t){let e=typeof t==`string`?ie(t):t;T(c===`/`||e.pathname?.startsWith(c),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${c}" but pathname "${e.pathname}" was given in the \`location\` prop.`),d=e}else d=u;let f=d.pathname||`/`,p=f;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);p=`/`+f.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let m=se(e,{pathname:p});E(l||m!=null,`No routes matched location "${d.pathname}${d.search}${d.hash}" `),E(m==null||m[m.length-1].route.element!==void 0||m[m.length-1].route.Component!==void 0||m[m.length-1].route.lazy!==void 0,`Matched leaf route at location "${d.pathname}${d.search}${d.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let h=gt(m&&m.map(e=>Object.assign({},e,{params:Object.assign({},o,e.params),pathname:je([c,r.encodeLocation?r.encodeLocation(e.pathname.replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:je([c,r.encodeLocation?r.encodeLocation(e.pathnameBase.replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathnameBase])})),i,n);return t&&h?_.createElement(Ke.Provider,{value:{location:{pathname:`/`,search:``,hash:``,state:null,key:`default`,unstable_mask:void 0,...d},navigationType:`POP`}},h):h}function ut(){let e=Tt(),t=Ie(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r=`rgba(200,200,200, 0.5)`,i={padding:`0.5rem`,backgroundColor:r},a={padding:`2px 4px`,backgroundColor:r},o=null;return console.error(`Error handled by React Router default ErrorBoundary:`,e),o=_.createElement(_.Fragment,null,_.createElement(`p`,null,`💿 Hey developer 👋`),_.createElement(`p`,null,`You can provide a way better UX than this when your app throws errors by providing your own `,_.createElement(`code`,{style:a},`ErrorBoundary`),` or`,` `,_.createElement(`code`,{style:a},`errorElement`),` prop on your route.`)),_.createElement(_.Fragment,null,_.createElement(`h2`,null,`Unexpected Application Error!`),_.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?_.createElement(`pre`,{style:i},n):null,o)}var dt=_.createElement(ut,null),ft=class extends _.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error(`React Router caught the following error during render`,e)}render(){let e=this.state.error;if(this.context&&typeof e==`object`&&e&&`digest`in e&&typeof e.digest==`string`){let t=$e(e.digest);t&&(e=t)}let t=e===void 0?this.props.children:_.createElement(qe.Provider,{value:this.props.routeContext},_.createElement(Je.Provider,{value:e,children:this.props.component}));return this.context?_.createElement(mt,{error:e},t):t}};ft.contextType=He;var pt=new WeakMap;function mt({children:e,error:t}){let{basename:n}=_.useContext(A);if(typeof t==`object`&&t&&`digest`in t&&typeof t.digest==`string`){let e=Qe(t.digest);if(e){let r=pt.get(t);if(r)throw r;let i=ze(e.location,n);if(Re&&!pt.get(t))if(i.isExternal||e.reloadDocument)window.location.href=i.absoluteURL||i.to;else{let n=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(i.to,{replace:e.replace}));throw pt.set(t,n),n}return _.createElement(`meta`,{httpEquiv:`refresh`,content:`0;url=${i.absoluteURL||i.to}`})}}return e}function ht({routeContext:e,match:t,children:n}){let r=_.useContext(Be);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),_.createElement(qe.Provider,{value:e},n)}function gt(e,t=[],n){let r=n?.state;if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(t.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let i=e,a=r?.errors;if(a!=null){let e=i.findIndex(e=>e.route.id&&a?.[e.route.id]!==void 0);T(e>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(a).join(`,`)}`),i=i.slice(0,Math.min(i.length,e+1))}let o=!1,s=-1;if(n&&r){o=r.renderFallback;for(let e=0;e=0?i.slice(0,s+1):[i[0]];break}}}}let c=n?.onError,l=r&&c?(e,t)=>{c(e,{location:r.location,params:r.matches?.[0]?.params??{},unstable_pattern:Le(r.matches),errorInfo:t})}:void 0;return i.reduceRight((e,n,c)=>{let u,d=!1,f=null,p=null;r&&(u=a&&n.route.id?a[n.route.id]:void 0,f=n.route.errorElement||dt,o&&(s<0&&c===0?(Dt(`route-fallback`,!1,"No `HydrateFallback` element provided to render during initial hydration"),d=!0,p=null):s===c&&(d=!0,p=n.route.hydrateFallbackElement||null)));let m=t.concat(i.slice(0,c+1)),h=()=>{let t;return t=u?f:d?p:n.route.Component?_.createElement(n.route.Component,null):n.route.element?n.route.element:e,_.createElement(ht,{match:n,routeContext:{outlet:e,matches:m,isDataRoute:r!=null},children:t})};return r&&(n.route.ErrorBoundary||n.route.errorElement||c===0)?_.createElement(ft,{location:r.location,revalidation:r.revalidation,component:f,error:u,children:h(),routeContext:{outlet:null,matches:m,isDataRoute:!0},onError:l}):h()},null)}function _t(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function vt(e){let t=_.useContext(Be);return T(t,_t(e)),t}function yt(e){let t=_.useContext(Ve);return T(t,_t(e)),t}function bt(e){let t=_.useContext(qe);return T(t,_t(e)),t}function xt(e){let t=bt(e),n=t.matches[t.matches.length-1];return T(n.route.id,`${e} can only be used on routes that contain a unique "id"`),n.route.id}function St(){return xt(`useRouteId`)}function Ct(){return yt(`useNavigation`).navigation}function wt(){let{matches:e,loaderData:t}=yt(`useMatches`);return _.useMemo(()=>e.map(e=>D(e,t)),[e,t])}function Tt(){let e=_.useContext(Je),t=yt(`useRouteError`),n=xt(`useRouteError`);return e===void 0?t.errors?.[n]:e}function Et(){let{router:e}=vt(`useNavigate`),t=xt(`useNavigate`),n=_.useRef(!1);return it(()=>{n.current=!0}),_.useCallback(async(r,i={})=>{E(n.current,rt),n.current&&(typeof r==`number`?await e.navigate(r):await e.navigate(r,{fromRouteId:t,...i}))},[e,t])}var j={};function Dt(e,t,n){!t&&!j[e]&&(j[e]=!0,E(!1,n))}_.useOptimistic,_.memo(Ot);function Ot({routes:e,future:t,state:n,isStatic:r,onError:i}){return lt(e,void 0,{state:n,isStatic:r,onError:i,future:t})}function kt({to:e,replace:t,state:n,relative:r}){T(tt(),` may be used only in the context of a component.`);let{static:i}=_.useContext(A);E(!i,` must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.`);let{matches:a}=_.useContext(qe),{pathname:o}=nt(),s=at(),c=Ae(e,ke(a),o,r===`path`),l=JSON.stringify(c);return _.useEffect(()=>{s(JSON.parse(l),{replace:t,state:n,relative:r})},[s,l,r,t,n]),null}function At(e){T(!1,`A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .`)}function jt({basename:e=`/`,children:t=null,location:n,navigationType:r=`POP`,navigator:i,static:a=!1,unstable_useTransitions:o}){T(!tt(),`You cannot render a inside another . You should never have more than one in your app.`);let s=e.replace(/^\/*/,`/`),c=_.useMemo(()=>({basename:s,navigator:i,static:a,unstable_useTransitions:o,future:{}}),[s,i,a,o]);typeof n==`string`&&(n=ie(n));let{pathname:l=`/`,search:u=``,hash:d=``,state:f=null,key:p=`default`,unstable_mask:m}=n,h=_.useMemo(()=>{let e=Ce(l,s);return e==null?null:{location:{pathname:e,search:u,hash:d,state:f,key:p,unstable_mask:m},navigationType:r}},[s,l,u,d,f,p,r,m]);return E(h!=null,` is not able to match the URL "${l}${u}${d}" because it does not start with the basename, so the won't render anything.`),h==null?null:_.createElement(A.Provider,{value:c},_.createElement(Ke.Provider,{children:t,value:h}))}function Mt({children:e,location:t}){return ct(Nt(e),t)}function Nt(e,t=[]){let n=[];return _.Children.forEach(e,(e,r)=>{if(!_.isValidElement(e))return;let i=[...t,r];if(e.type===_.Fragment){n.push.apply(n,Nt(e.props.children,i));return}T(e.type===At,`[${typeof e.type==`string`?e.type:e.type.name}] is not a component. All component children of must be a or `),T(!e.props.index||!e.props.children,`An index route cannot have child routes.`);let a={id:e.props.id||i.join(`-`),caseSensitive:e.props.caseSensitive,element:e.props.element,Component:e.props.Component,index:e.props.index,path:e.props.path,middleware:e.props.middleware,loader:e.props.loader,action:e.props.action,hydrateFallbackElement:e.props.hydrateFallbackElement,HydrateFallback:e.props.HydrateFallback,errorElement:e.props.errorElement,ErrorBoundary:e.props.ErrorBoundary,hasErrorBoundary:e.props.hasErrorBoundary===!0||e.props.ErrorBoundary!=null||e.props.errorElement!=null,shouldRevalidate:e.props.shouldRevalidate,handle:e.props.handle,lazy:e.props.lazy};e.props.children&&(a.children=Nt(e.props.children,i)),n.push(a)}),n}var Pt=`get`,Ft=`application/x-www-form-urlencoded`;function It(e){return typeof HTMLElement<`u`&&e instanceof HTMLElement}function Lt(e){return It(e)&&e.tagName.toLowerCase()===`button`}function Rt(e){return It(e)&&e.tagName.toLowerCase()===`form`}function zt(e){return It(e)&&e.tagName.toLowerCase()===`input`}function Bt(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Vt(e,t){return e.button===0&&(!t||t===`_self`)&&!Bt(e)}var Ht=null;function Ut(){if(Ht===null)try{new FormData(document.createElement(`form`),0),Ht=!1}catch{Ht=!0}return Ht}var Wt=new Set([`application/x-www-form-urlencoded`,`multipart/form-data`,`text/plain`]);function Gt(e){return e!=null&&!Wt.has(e)?(E(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Ft}"`),null):e}function Kt(e,t){let n,r,i,a,o;if(Rt(e)){let o=e.getAttribute(`action`);r=o?Ce(o,t):null,n=e.getAttribute(`method`)||Pt,i=Gt(e.getAttribute(`enctype`))||Ft,a=new FormData(e)}else if(Lt(e)||zt(e)&&(e.type===`submit`||e.type===`image`)){let o=e.form;if(o==null)throw Error(`Cannot submit a `}renderLink(){let{ariaLabel:e,ariaHasPopup:t,ariaExpanded:n}=this;return D`${this.renderContent()} `}renderContent(){let e=D``;return D` ${this.trailingIcon?k:e} ${this.trailingIcon?e:k} `}handleClick(e){if(this.softDisabled||this.disabled&&this.href){e.stopImmediatePropagation(),e.preventDefault();return}!xt(e)||!this.buttonElement||(this.focus(),bt(this.buttonElement))}handleSlotChange(){this.hasIcon=this.assignedIcons.length>0}};yt(Z),Z.formAssociated=!0,Z.shadowRootOptions={mode:`open`,delegatesFocus:!0},e([L({type:Boolean,reflect:!0})],Z.prototype,`disabled`,void 0),e([L({type:Boolean,attribute:`soft-disabled`,reflect:!0})],Z.prototype,`softDisabled`,void 0),e([L()],Z.prototype,`href`,void 0),e([L()],Z.prototype,`download`,void 0),e([L()],Z.prototype,`target`,void 0),e([L({type:Boolean,attribute:`trailing-icon`,reflect:!0})],Z.prototype,`trailingIcon`,void 0),e([L({type:Boolean,attribute:`has-icon`,reflect:!0})],Z.prototype,`hasIcon`,void 0),e([L()],Z.prototype,`type`,void 0),e([L({reflect:!0})],Z.prototype,`value`,void 0),e([B(`.button`)],Z.prototype,`buttonElement`,void 0),e([ze({slot:`icon`,flatten:!0})],Z.prototype,`assignedIcons`,void 0);var Et=class extends Z{},Dt=s`:host{--_container-height: var(--md-text-button-container-height, 40px);--_disabled-label-text-color: var(--md-text-button-disabled-label-text-color, var(--md-sys-color-on-surface, #1d1b20));--_disabled-label-text-opacity: var(--md-text-button-disabled-label-text-opacity, 0.38);--_focus-label-text-color: var(--md-text-button-focus-label-text-color, var(--md-sys-color-primary, #6750a4));--_hover-label-text-color: var(--md-text-button-hover-label-text-color, var(--md-sys-color-primary, #6750a4));--_hover-state-layer-color: var(--md-text-button-hover-state-layer-color, var(--md-sys-color-primary, #6750a4));--_hover-state-layer-opacity: var(--md-text-button-hover-state-layer-opacity, 0.08);--_label-text-color: var(--md-text-button-label-text-color, var(--md-sys-color-primary, #6750a4));--_label-text-font: var(--md-text-button-label-text-font, var(--md-sys-typescale-label-large-font, var(--md-ref-typeface-plain, Roboto)));--_label-text-line-height: var(--md-text-button-label-text-line-height, var(--md-sys-typescale-label-large-line-height, 1.25rem));--_label-text-size: var(--md-text-button-label-text-size, var(--md-sys-typescale-label-large-size, 0.875rem));--_label-text-weight: var(--md-text-button-label-text-weight, var(--md-sys-typescale-label-large-weight, var(--md-ref-typeface-weight-medium, 500)));--_pressed-label-text-color: var(--md-text-button-pressed-label-text-color, var(--md-sys-color-primary, #6750a4));--_pressed-state-layer-color: var(--md-text-button-pressed-state-layer-color, var(--md-sys-color-primary, #6750a4));--_pressed-state-layer-opacity: var(--md-text-button-pressed-state-layer-opacity, 0.12);--_disabled-icon-color: var(--md-text-button-disabled-icon-color, var(--md-sys-color-on-surface, #1d1b20));--_disabled-icon-opacity: var(--md-text-button-disabled-icon-opacity, 0.38);--_focus-icon-color: var(--md-text-button-focus-icon-color, var(--md-sys-color-primary, #6750a4));--_hover-icon-color: var(--md-text-button-hover-icon-color, var(--md-sys-color-primary, #6750a4));--_icon-color: var(--md-text-button-icon-color, var(--md-sys-color-primary, #6750a4));--_icon-size: var(--md-text-button-icon-size, 18px);--_pressed-icon-color: var(--md-text-button-pressed-icon-color, var(--md-sys-color-primary, #6750a4));--_container-shape-start-start: var(--md-text-button-container-shape-start-start, var(--md-text-button-container-shape, var(--md-sys-shape-corner-full, 9999px)));--_container-shape-start-end: var(--md-text-button-container-shape-start-end, var(--md-text-button-container-shape, var(--md-sys-shape-corner-full, 9999px)));--_container-shape-end-end: var(--md-text-button-container-shape-end-end, var(--md-text-button-container-shape, var(--md-sys-shape-corner-full, 9999px)));--_container-shape-end-start: var(--md-text-button-container-shape-end-start, var(--md-text-button-container-shape, var(--md-sys-shape-corner-full, 9999px)));--_leading-space: var(--md-text-button-leading-space, 12px);--_trailing-space: var(--md-text-button-trailing-space, 12px);--_with-leading-icon-leading-space: var(--md-text-button-with-leading-icon-leading-space, 12px);--_with-leading-icon-trailing-space: var(--md-text-button-with-leading-icon-trailing-space, 16px);--_with-trailing-icon-leading-space: var(--md-text-button-with-trailing-icon-leading-space, 16px);--_with-trailing-icon-trailing-space: var(--md-text-button-with-trailing-icon-trailing-space, 12px);--_container-color: none;--_disabled-container-color: none;--_disabled-container-opacity: 0} `,Ot=class extends Et{};Ot.styles=[Ve,Dt],customElements.define(`ew-text-button`,Ot);var Q=class extends F{constructor(){super(...arguments),this.inset=!1,this.insetStart=!1,this.insetEnd=!1}};e([L({type:Boolean,reflect:!0})],Q.prototype,`inset`,void 0),e([L({type:Boolean,reflect:!0,attribute:`inset-start`})],Q.prototype,`insetStart`,void 0),e([L({type:Boolean,reflect:!0,attribute:`inset-end`})],Q.prototype,`insetEnd`,void 0);var kt=s`:host{box-sizing:border-box;color:var(--md-divider-color, var(--md-sys-color-outline-variant, #cac4d0));display:flex;height:var(--md-divider-thickness, 1px);width:100%}:host([inset]),:host([inset-start]){padding-inline-start:16px}:host([inset]),:host([inset-end]){padding-inline-end:16px}:host::before{background:currentColor;content:"";height:100%;width:100%}@media(forced-colors: active){:host::before{background:CanvasText}} `;function At(e,t){t.bubbles&&(!e.shadowRoot||t.composed)&&t.stopPropagation();let n=Reflect.construct(t.constructor,[t.type,t]),r=e.dispatchEvent(n);return r||t.preventDefault(),r}var jt=class extends Q{};jt.styles=[kt],jt=e([I(`md-divider`)],jt);var Mt={dialog:[[[{transform:`translateY(-50px)`},{transform:`translateY(0)`}],{duration:500,easing:W.EMPHASIZED}]],scrim:[[[{opacity:0},{opacity:.32}],{duration:500,easing:`linear`}]],container:[[[{opacity:0},{opacity:1}],{duration:50,easing:`linear`,pseudoElement:`::before`}],[[{height:`35%`},{height:`100%`}],{duration:500,easing:W.EMPHASIZED,pseudoElement:`::before`}]],headline:[[[{opacity:0},{opacity:0,offset:.2},{opacity:1}],{duration:250,easing:`linear`,fill:`forwards`}]],content:[[[{opacity:0},{opacity:0,offset:.2},{opacity:1}],{duration:250,easing:`linear`,fill:`forwards`}]],actions:[[[{opacity:0},{opacity:0,offset:.5},{opacity:1}],{duration:300,easing:`linear`,fill:`forwards`}]]},Nt={dialog:[[[{transform:`translateY(0)`},{transform:`translateY(-50px)`}],{duration:150,easing:W.EMPHASIZED_ACCELERATE}]],scrim:[[[{opacity:.32},{opacity:0}],{duration:150,easing:`linear`}]],container:[[[{height:`100%`},{height:`35%`}],{duration:150,easing:W.EMPHASIZED_ACCELERATE,pseudoElement:`::before`}],[[{opacity:`1`},{opacity:`0`}],{delay:100,duration:50,easing:`linear`,pseudoElement:`::before`}]],headline:[[[{opacity:1},{opacity:0}],{duration:100,easing:`linear`,fill:`forwards`}]],content:[[[{opacity:1},{opacity:0}],{duration:100,easing:`linear`,fill:`forwards`}]],actions:[[[{opacity:1},{opacity:0}],{duration:100,easing:`linear`,fill:`forwards`}]]},Pt=pt(F),$=class extends Pt{get open(){return this.isOpen}set open(e){e!==this.isOpen&&(this.isOpen=e,e?(this.setAttribute(`open`,``),this.show()):(this.removeAttribute(`open`),this.close()))}constructor(){super(),this.quick=!1,this.returnValue=``,this.noFocusTrap=!1,this.getOpenAnimation=()=>Mt,this.getCloseAnimation=()=>Nt,this.isOpen=!1,this.isOpening=!1,this.isConnectedPromise=this.getIsConnectedPromise(),this.isAtScrollTop=!1,this.isAtScrollBottom=!1,this.nextClickIsFromContent=!1,this.hasHeadline=!1,this.hasActions=!1,this.hasIcon=!1,this.escapePressedWithoutCancel=!1,this.treewalker=document.createTreeWalker(this,NodeFilter.SHOW_ELEMENT),this.addEventListener(`submit`,this.handleSubmit)}async show(){this.isOpening=!0,await this.isConnectedPromise,await this.updateComplete;let e=this.dialog;if(e.open||!this.isOpening){this.isOpening=!1;return}if(!this.dispatchEvent(new Event(`open`,{cancelable:!0}))){this.open=!1,this.isOpening=!1;return}e.showModal(),this.open=!0,this.scroller&&(this.scroller.scrollTop=0),this.querySelector(`[autofocus]`)?.focus(),await this.animateDialog(this.getOpenAnimation()),this.dispatchEvent(new Event(`opened`)),this.isOpening=!1}async close(e=this.returnValue){if(this.isOpening=!1,!this.isConnected){this.open=!1;return}await this.updateComplete;let t=this.dialog;if(!t.open||this.isOpening){this.open=!1;return}let n=this.returnValue;if(this.returnValue=e,!this.dispatchEvent(new Event(`close`,{cancelable:!0}))){this.returnValue=n;return}await this.animateDialog(this.getCloseAnimation()),t.close(e),this.open=!1,this.dispatchEvent(new Event(`closed`))}connectedCallback(){super.connectedCallback(),this.isConnectedPromiseResolve()}disconnectedCallback(){super.disconnectedCallback(),this.isConnectedPromise=this.getIsConnectedPromise()}render(){let e=this.open&&!(this.isAtScrollTop&&this.isAtScrollBottom),t={"has-headline":this.hasHeadline,"has-actions":this.hasActions,"has-icon":this.hasIcon,scrollable:e,"show-top-divider":e&&!this.isAtScrollTop,"show-bottom-divider":e&&!this.isAtScrollBottom},n=this.open&&!this.noFocusTrap,r=D` `,{ariaLabel:i}=this;return D`
${n?r:k}

${n?r:k}
`}firstUpdated(){this.intersectionObserver=new IntersectionObserver(e=>{for(let t of e)this.handleAnchorIntersection(t)},{root:this.scroller}),this.intersectionObserver.observe(this.topAnchor),this.intersectionObserver.observe(this.bottomAnchor)}handleDialogClick(){if(this.nextClickIsFromContent){this.nextClickIsFromContent=!1;return}this.dispatchEvent(new Event(`cancel`,{cancelable:!0}))&&this.close()}handleContentClick(){this.nextClickIsFromContent=!0}handleSubmit(e){let t=e.target,{submitter:n}=e;t.getAttribute(`method`)!==`dialog`||!n||this.close(n.getAttribute(`value`)??this.returnValue)}handleCancel(e){if(e.target!==this.dialog)return;this.escapePressedWithoutCancel=!1;let t=!At(this,e);e.preventDefault(),!t&&this.close()}handleClose(){this.escapePressedWithoutCancel&&(this.escapePressedWithoutCancel=!1,this.dialog?.dispatchEvent(new Event(`cancel`,{cancelable:!0})))}handleKeydown(e){e.key===`Escape`&&(this.escapePressedWithoutCancel=!0,setTimeout(()=>{this.escapePressedWithoutCancel=!1}))}async animateDialog(e){if(this.cancelAnimations?.abort(),this.cancelAnimations=new AbortController,this.quick)return;let{dialog:t,scrim:n,container:r,headline:i,content:a,actions:o}=this;if(!t||!n||!r||!i||!a||!o)return;let{container:s,dialog:c,scrim:l,headline:u,content:d,actions:ee}=e,te=[[t,c??[]],[n,l??[]],[r,s??[]],[i,u??[]],[a,d??[]],[o,ee??[]]],f=[];for(let[e,t]of te)for(let n of t){let t=e.animate(...n);this.cancelAnimations.signal.addEventListener(`abort`,()=>{t.cancel()}),f.push(t)}await Promise.all(f.map(e=>e.finished.catch(()=>{})))}handleHeadlineChange(e){this.hasHeadline=e.target.assignedElements().length>0}handleActionsChange(e){this.hasActions=e.target.assignedElements().length>0}handleIconChange(e){this.hasIcon=e.target.assignedElements().length>0}handleAnchorIntersection(e){let{target:t,isIntersecting:n}=e;t===this.topAnchor&&(this.isAtScrollTop=n),t===this.bottomAnchor&&(this.isAtScrollBottom=n)}getIsConnectedPromise(){return new Promise(e=>{this.isConnectedPromiseResolve=e})}handleFocusTrapFocus(e){let[t,n]=this.getFirstAndLastFocusableChildren();if(!t||!n){this.dialog?.focus();return}let r=e.target===this.firstFocusTrap,i=!r,a=e.relatedTarget===t,o=e.relatedTarget===n,s=!a&&!o;if(i&&o||r&&s){t.focus();return}if(r&&a||i&&s){n.focus();return}}getFirstAndLastFocusableChildren(){if(!this.treewalker)return[null,null];let e=null,t=null;for(this.treewalker.currentNode=this.treewalker.root;this.treewalker.nextNode();){let n=this.treewalker.currentNode;Ft(n)&&(e||=n,t=n)}return[e,t]}};e([L({type:Boolean})],$.prototype,`open`,null),e([L({type:Boolean})],$.prototype,`quick`,void 0),e([L({attribute:!1})],$.prototype,`returnValue`,void 0),e([L()],$.prototype,`type`,void 0),e([L({type:Boolean,attribute:`no-focus-trap`})],$.prototype,`noFocusTrap`,void 0),e([B(`dialog`)],$.prototype,`dialog`,void 0),e([B(`.scrim`)],$.prototype,`scrim`,void 0),e([B(`.container`)],$.prototype,`container`,void 0),e([B(`.headline`)],$.prototype,`headline`,void 0),e([B(`.content`)],$.prototype,`content`,void 0),e([B(`.actions`)],$.prototype,`actions`,void 0),e([R()],$.prototype,`isAtScrollTop`,void 0),e([R()],$.prototype,`isAtScrollBottom`,void 0),e([B(`.scroller`)],$.prototype,`scroller`,void 0),e([B(`.top.anchor`)],$.prototype,`topAnchor`,void 0),e([B(`.bottom.anchor`)],$.prototype,`bottomAnchor`,void 0),e([B(`.focus-trap`)],$.prototype,`firstFocusTrap`,void 0),e([R()],$.prototype,`hasHeadline`,void 0),e([R()],$.prototype,`hasActions`,void 0),e([R()],$.prototype,`hasIcon`,void 0);function Ft(e){let t=`:not(:disabled,[disabled])`;return e.matches(`:is(button,input,select,textarea,object,:is(a,area)[href],[tabindex],[contenteditable=true])`+t+`:not([tabindex^="-"])`)?!0:!e.localName.includes(`-`)||!e.matches(t)?!1:e.shadowRoot?.delegatesFocus??!1}var It=s`:host{border-start-start-radius:var(--md-dialog-container-shape-start-start, var(--md-dialog-container-shape, var(--md-sys-shape-corner-extra-large, 28px)));border-start-end-radius:var(--md-dialog-container-shape-start-end, var(--md-dialog-container-shape, var(--md-sys-shape-corner-extra-large, 28px)));border-end-end-radius:var(--md-dialog-container-shape-end-end, var(--md-dialog-container-shape, var(--md-sys-shape-corner-extra-large, 28px)));border-end-start-radius:var(--md-dialog-container-shape-end-start, var(--md-dialog-container-shape, var(--md-sys-shape-corner-extra-large, 28px)));display:contents;margin:auto;max-height:min(560px,100% - 48px);max-width:min(560px,100% - 48px);min-height:140px;min-width:280px;position:fixed;height:fit-content;width:fit-content}dialog{background:rgba(0,0,0,0);border:none;border-radius:inherit;flex-direction:column;height:inherit;margin:inherit;max-height:inherit;max-width:inherit;min-height:inherit;min-width:inherit;outline:none;overflow:visible;padding:0;width:inherit}dialog[open]{display:flex}::backdrop{background:none}.scrim{background:var(--md-sys-color-scrim, #000);display:none;inset:0;opacity:32%;pointer-events:none;position:fixed;z-index:1}:host([open]) .scrim{display:flex}h2{all:unset;align-self:stretch}.headline{align-items:center;color:var(--md-dialog-headline-color, var(--md-sys-color-on-surface, #1d1b20));display:flex;flex-direction:column;font-family:var(--md-dialog-headline-font, var(--md-sys-typescale-headline-small-font, var(--md-ref-typeface-brand, Roboto)));font-size:var(--md-dialog-headline-size, var(--md-sys-typescale-headline-small-size, 1.5rem));line-height:var(--md-dialog-headline-line-height, var(--md-sys-typescale-headline-small-line-height, 2rem));font-weight:var(--md-dialog-headline-weight, var(--md-sys-typescale-headline-small-weight, var(--md-ref-typeface-weight-regular, 400)));position:relative}slot[name=headline]::slotted(*){align-items:center;align-self:stretch;box-sizing:border-box;display:flex;gap:8px;padding:24px 24px 0}.icon{display:flex}slot[name=icon]::slotted(*){color:var(--md-dialog-icon-color, var(--md-sys-color-secondary, #625b71));fill:currentColor;font-size:var(--md-dialog-icon-size, 24px);margin-top:24px;height:var(--md-dialog-icon-size, 24px);width:var(--md-dialog-icon-size, 24px)}.has-icon slot[name=headline]::slotted(*){justify-content:center;padding-top:16px}.scrollable slot[name=headline]::slotted(*){padding-bottom:16px}.scrollable.has-headline slot[name=content]::slotted(*){padding-top:8px}.container{border-radius:inherit;display:flex;flex-direction:column;flex-grow:1;overflow:hidden;position:relative;transform-origin:top}.container::before{background:var(--md-dialog-container-color, var(--md-sys-color-surface-container-high, #ece6f0));border-radius:inherit;content:"";inset:0;position:absolute}.scroller{display:flex;flex:1;flex-direction:column;overflow:hidden;z-index:1}.scrollable .scroller{overflow-y:scroll}.content{color:var(--md-dialog-supporting-text-color, var(--md-sys-color-on-surface-variant, #49454f));font-family:var(--md-dialog-supporting-text-font, var(--md-sys-typescale-body-medium-font, var(--md-ref-typeface-plain, Roboto)));font-size:var(--md-dialog-supporting-text-size, var(--md-sys-typescale-body-medium-size, 0.875rem));line-height:var(--md-dialog-supporting-text-line-height, var(--md-sys-typescale-body-medium-line-height, 1.25rem));flex:1;font-weight:var(--md-dialog-supporting-text-weight, var(--md-sys-typescale-body-medium-weight, var(--md-ref-typeface-weight-regular, 400)));height:min-content;position:relative}slot[name=content]::slotted(*){box-sizing:border-box;padding:24px}.anchor{position:absolute}.top.anchor{top:0}.bottom.anchor{bottom:0}.actions{position:relative}slot[name=actions]::slotted(*){box-sizing:border-box;display:flex;gap:8px;justify-content:flex-end;padding:16px 24px 24px}.has-actions slot[name=content]::slotted(*){padding-bottom:8px}md-divider{display:none;position:absolute}.has-headline.show-top-divider .headline md-divider,.has-actions.show-bottom-divider .actions md-divider{display:flex}.headline md-divider{bottom:0}.actions md-divider{top:0}@media(forced-colors: active){dialog{outline:2px solid WindowText}} `,Lt=class extends ${};Lt.styles=[It],customElements.define(`ew-dialog`,Lt);var Rt=s` :host { --roboto-font: Roboto, system-ui; --text-color: rgba(0, 0, 0, 0.6); --danger-color: #db4437; --md-sys-color-primary: #03a9f4; --md-sys-color-on-primary: #fff; --md-ref-typeface-brand: var(--roboto-font); --md-ref-typeface-plain: var(--roboto-font); --md-sys-color-surface: #fff; --md-sys-color-surface-container: #fff; --md-sys-color-surface-container-high: #fff; --md-sys-color-surface-container-highest: #f5f5f5; --md-sys-color-secondary-container: #e0e0e0; --md-sys-typescale-headline-font: var(--roboto-font); --md-sys-typescale-title-font: var(--roboto-font); } a { color: var(--md-sys-color-primary); } `;export{je as A,I as C,O as D,Ne as E,s as M,e as N,xe as O,L as S,k as T,Be as _,bt as a,B as b,X as c,W as d,Ze as f,Je as g,Xe as h,Q as i,be as j,D as k,vt as l,Ye as m,At as n,xt as o,U as p,kt as r,yt as s,Rt as t,pt as u,ze as v,F as w,R as x,Re as y}; ================================================ FILE: docs/index.html ================================================ Brainslug Tools
================================================ FILE: faq.md ================================================ # Frequently Asked Questions ### Which ESP device to get? While technically any device compatible with the ESPHome could work, ESP32s have been tested the most and is what [ESPHome also recommends](https://esphome.io/guides/faq/#recommended). TLDR; ESP32, ESP32-S3 and ESP32-C3. **Keep in mind that some very cheaply made ESP32s sometimes have flaws where certain components might not work!** I will try my best to keep the ESP8266 supported as well, but I am not sure how it will behave with version 2 and 3, because that will probably require a bit more RAM. The webserver might also be a bit worse on the ESP8266. ### About the robot errors/alerts The robot status is, by default, fetched once every 2 seconds. If you have logging enabled, this creates many logs for `GetErr` and for D3-D7 `GetState`. If you see the alert/error `UI_ALERT_INVALID`, this means you have no alert/error. ### Valetudo? For those unaware, Valetudo is a cloud replacement for a limited range of vacuum cleaners, and while adding support for the Neato vacuums would be amazing, as it is right now, it is not possible. Valetudo works by overriding the original firmware to disable SSL certificate pinning and replace the hardcoded IP address of the server it connects to. The hardcoded IP address is not a problem for the Neatos but the SSL certificate pinning is, unless there is a way to disable that, is found or a firmware without the SSL certificate pinning is released, a cloud replacement for the Neato robots will never be possible. Aside from that, if the certificate pinning problem is fixed, it would not be part of Valetudo for a couple of reasons: - Valetudo requires that the entire package run on the robot, which is not possible with the Neatos, so would need to be a separate docker package like Congatudo - They had themselves said Neato and Vorwek is something they are not supporting. See https://dustbuilder.dontvacuum.me/unsupported.txt ### Making your own firmware? The firmware images are encrypted and signed, while it seams that the signing does not really matter, the encrypted firmware images makes it impossible to modify or create your own firmware. ================================================ FILE: ha-images.md ================================================ ## Home Assistant card Cleaning running | Cleaning paused :-------------------------:|:-------------------------: ![cleaning](./pics/esphome/cleaning.png) | ![paused](./pics/esphome/paused.png) Booting | Shutting down :-------------------------:|:-------------------------: ![booting](./pics/esphome/booting.png) | ![shutdown](./pics/esphome/shutdown.png) Disconnected | Alert | Error :-------------------------:|:-------------------------:|:---- ![disconnected](./pics/esphome/disconnected.png) | ![alert](./pics/esphome/alert-dustbin.png) | ![error](./pics/esphome/error-undock-failed.png) ![advanced-spot-clean](./pics/esphome/advanced-spot-clean.png) ## Webserver interface If you want, you can also control the robot via the esphome webserver interface, this just shows all settings, the log and the buttons to do actions. ![webserver-esphome](./pics/esphome/web_server.png) ================================================ FILE: install-esp-device-gen2.md ================================================ # Install ESPHome device in a gen2 robot **Please read the guide fully before doing it yourself!** You will have to go a bit back and forth between this guide and where you came from! There is no full step by step guide for the gen2 robots, just general information. Info and pictures are taken from https://github.com/Philip2809/neato-brainslug/discussions/72 1. Turn the robot off, remove the battery 2. Take the top cover off to expose the board, there is many yt videos showing how to take it apart 3. Find the debug port on the right side ![](pics/installs/gen2/debug-port.jpg) 4. Solder wires according to the pinout ![](pics/installs/gen2/pinout.png) ![](pics/installs/gen2/soldered-1.jpg) ![](pics/installs/gen2/soldered-2.jpg) 5. Connect to the ESP device and TEST your configuration, make sure that it works! 6. Install the ESP device inside of the robot where there is space, make sure to cover the ESP device in electrical or kaptop tape to avoid any shorts. 7. Put the robot back togehter, keep the cables in mind and make the hole larger if nedded. ![](pics/installs/gen2/closed-up.jpg) 8. Once all is back togheter, put the battery back in and turn it on! ================================================ FILE: install-esp-device-gen3.md ================================================ # Install ESPHome device in a gen3 robot **Please read the guide fully before doing it yourself!** Now its time to install the ESPHome device in a more permanent way, there is a couple of ways to do this, but for all of these methods it is recommended to use this type of cable: | | | | | :-:|:-:|:-:|:-: **JST-XH to DuPont** | ![JST-XH to dupont](pics/installs/0_jst-xh.jpg) | Either buy or make this type of cable, it will be used to connect the ESP device to the debug port. You could also use dupont female-female connectors, but you will need the make the hole larger and add some protection for the cables because they would be too stiff and could break when the robot bumps into things. | https://www.aliexpress.com/item/1005006251847933.html ### Make this cable: You will need - [One female JST-XH cable](https://www.amazon.com/dp/B0D9R3MP4G) (the one that does not have pins sticking out) - A soldering iron - 4 pieces of about 10 cm of wire - 4 pieces of heath shrink tubing - [4x Dupont connector and crimping tool](https://a.co/d/8DN4Z0P) or 4x female DuPont connectors Cut and strip the wires, solder the DuPont cables to the JST-XH cables, if you don't have pre-crimped cables, crimp the first. Use heat shrink to protect your soldering. ### About the JST-XH connector Make sure the connector plugs in as far back as possible, this is easier for the internall install since you see everything a lot better. Use the following images as a referance on how far it should be plugged in! When you plug it in it might feel like it stops after a little while, but the top part catches the connector, keep pushing | It should be pushed this far back, make sure to be careful, you don't want to break anything! :-------------------------:|:-------------------------: ![not connected to debug port](pics/installs/7_not-connected.jpg) | ![connected to debug port](pics/installs/7_connected.jpg) The connector just fits, I needed to press on each side of the connector a little bit at a time, that way it went in correctly, it should be about as deep as shown here | How far back the connector should be pushed with a ruler in **cm** :-------------------------:|:-------------------------: ![not connected to debug port](pics/installs/7_how-far-to-push.jpg) | ![connected to debug port](pics/installs/7_connector-with-ruler.jpg) #### Bending pins (not recommended) You could, instead of using an JST-XH connector bend the debug pins to allow for dupont connectors to connect. ![bent-pins-dupont](./pics/installs/tom/bend-pins.png) Bending the pins you risk breaking the board, so this method should be avoided. ## Choose the method to install | Method | State | | | :-:|:-:|:-:|:-: Externally | NOT Recommended, least stable, if the tape peals you will get deck debris error | ![cables-via-bumper](./pics/installs/8_cables-via-bumper.jpg) | ![d3-install-outside](./pics/installs/external.jpg) Behind bumper cut | Recommended but you will need to cut some plastic | ![](./pics/installs/behind-bumper-cut-1.jpg) | ![](./pics/installs/behind-bumper-cut-2.jpg) Behind bumper small ESP | Recommended but you need an small ESP device and solder the connections, not using DuPont on the ESP | ![](./pics/installs/behind-bumper-c3.png) | Or use ANTALIFE's custom pcba cable ![](./pics/installs/pcba-cable.png) Internally | Recommended approach | ![jay-jst-xh](./pics/installs/jay/2-install-JST-XH.jpg) | ![jay-installed](./pics/installs/jay/4-installed-and-taped.jpg) ## Step by step **Warning: before doing anything on the robot, make sure to open the battery compartment and remove the battery. We will include this again step in the step-by-step procedure.** ### What you need - Protective cover for your work surface - The JST-XH cable - The ESP you flashed - Hot glue - Tape - Electrical tape (preferably black) - Electrical masking tape / Kapton tape to prevent shorts Internally: - Long T10 Torx bit (Some robots also have T10 security screws) - Philips screwdriver Behind bumper: - If you are cutting the plastic, a utility knife ### Let's begin! 1. Put a protective cover over your work surface as there will be some dust coming out of your robot and you don't want that on your kitchen table. A piece of cardboard, plastic or tablecloth will do. 2. Remove the dustbin from the robot. 3. Turn the robot over, so you can see the bottom side of the robot, where the wheels are. 4. Remove the main brush cover by pulling upwards. If you have a side brush, you will need to pull that off too. 5. Remove the main brush. 6. Remove the bumper by pulling it off. 7. Remove the 2 Philips screws marked yellow in the image below and open the battery compartment. Remove the battery and leave it unplugged until told to plug it back in. ![Neato robot on its back. Screws to remove are marked](pics/installs/jay/1-removing-screws.jpg "Remove these screws") How do you want to continue? - [Internally](#internally) - [Behind bumper cutting](#behind-bumper---cutting) - [Behind bumper with small ESP (or custom pcba cable)](#behind-bumper---small-esp-device) - [Externally](#externally) ### Internally 8. Remove the 6 Torx screws red marked in the image from before. You will need a long T10 Torx bit for that, it might be security torx. 9. Turn the robot back over so you can see space where the dust bin normally sits. 10. Remove two more torx screws as indicated in the image below. ![Neato robot normals ide up. Screws to remove are marked](pics/installs/jay/1a-removing-two-more-screws.jpg "Remove two more screws") 11. Remove the top cover by pushing the two tabs at the front. 12. Double check again that you've removed the battery before continuing. There should be no LEDs on at this point. 13. Install the JST-XH cable on the connector on the front-left of the board and then run the wire up tightly across the board, under some other wires, to the right of the board. The connector should be plugged in as far as it goes ![Close-up of Neato board with JST-XH connector and wire installed](pics/installs/jay/2-install-JST-XH.jpg "Installing the JST-XH cable") 14. Find a place for your ESP. It normally fits in the space to the right of the main board. 15. Run the wires up/under existing wires as needed so they don't stick out. 16. Connect the wires to the ESP, making sure you make the right connections, you should have tried what connections works for you since before! 17. Wrap your ESP in masking tape / Kapton tape to prevent shorts. Also make sure to secure the connections to the ESP as there will be a lot of vibration. Either include them in the tape wrap or use some hot glue. ![ESP installed and taped](pics/installs/jay/4-installed-and-taped.jpg "Installing and taping the ESP") 18. Use some electrical tape on the Neato enclosure across the JST-XH cable wires to secure them. ![Closeup of JST-XH cable with electrical tape on plastic enclosure](pics/installs/jay/5-electrical-tape-1.jpg "Securing JST-XH cable") 19. Put back the top cover. Don't turn Neato over just yet. 20. Add another round of electrical tape to further secure the JST-XH cable. ![Closeup of Neato with electrical tape on plastic enclosure](pics/installs/jay/6-electrical-tape-2.jpg "Securing JST-XH cable once more") 21. Put back the two screws you removed earlier. 22. Now, turn Neato back over and put back the 6 Torx screws your removed from the bottom. 23. Reconnect battery and close battery compartment. 24. Turn Neato on and enjoy! ### Behind bumper - cutting 8. Score the side and bottom of the pastic part under the debug port and break it off ![](./pics/installs/behind-bumper-cut-1.jpg) 9. Cover up all the exposed metal parts of the mainboard and drop sensor to avoid shorts 10. Connect the JST-XH connector the the debug port and connect/solder the other end to the ESP device - see the section above about the JST-XH connector! Put some hot glue or wrap it very well in tape to make sure it does not disconnect! ![](pics/installs/9_connected-hot-glue.jpg) *For this install you don't need to bend the pins, reused the image, the important is the hot glue* 11. Once the connectors are secure to the ESP device, place it in the cavity and tape it all up! ![](./pics/installs/behind-bumper-cut-2.jpg) 12. If needed some struts on the bumper may need to be broken out/cut ![](./pics/installs/behind-bumper-cut-3.jpg) 13. Put the bumper back on, battery back into its place, battery cover on, brush in its place and enjoy! ### Behind bumper - small ESP device 8. Either use ANTALIFE's custom pcba cable or: 1. Make sure how you want the ESP device to fit 2. Connect the JST-XH connector the the debug port and route the cable to the place the ESP device will go, make it as flat as possible, if you can route it behind the plastic - see the section above about the JST-XH connector! 3. Solder the cables to the ESP device 4. Secure the ESP device in its place and make the cable as flush as possbile, secure it with tape or hot glue If using the JST-XH or self made cable, try to route it like this | ANTALIFE's custom pcba cable :-------------------------:|:-------------------------: ![](pics/installs/behind-bumper-c3.png) | ![](pics/installs/pcba-cable.png) 9. If needed some struts on the bumper may need to be broken out/cut ![](./pics/installs/behind-bumper-cut-3.jpg) 10. Put the bumper back on, battery back into its place, battery cover on, brush in its place and enjoy! ### Externally 8. Drill a hole in the bumper Bumber front | Bumber back :-------------------------:|:-------------------------: ![bumper-front](./pics/d3/bumper-front.jpg) | ![bumper-back](./pics/d3/bumper-back.jpg) Debug port | Debug port with bumber :-------------------------:|:-------------------------: ![debug-port](./pics/d3/debug-port.jpg) | ![bumper-with-hole](./pics/d3/bumper-with-hole.jpg) 9. Route the JST-XH thour the hole and connect them to the debug port ![Route the JST-XH throuhg bumper](pics/installs/6_cable-in-bumper.jpg) 10. Plug the JST-XH connector into the debug port - see the section above about the JST-XH connector! ![Plug the JST-XH connector into the debug port](pics/installs/7_plug-cable-in.jpg) 11. Put the bumper back on and make sure the cable is out Make sure the cables come out like this | The cables should reach the handle like this :-------------------------:|:-------------------------: ![not connected to debug port](pics/installs/8_cables-via-bumper.jpg) | ![connected to debug port](pics/installs/8_cables-to-handle.jpg) 12. Connect (and bend the pins) the ESP device to the robot Depedning on how your ESP device looks like, you may need, like me, to bend the pins to fit it in a better way, it is to make sure the cables don't interfere with the lidar. Place your ESP device down | Connect dupont cables to the pins you want to bend | Bend the pins out towards the side like this, don't use too much force! :-------------------------:|:-------------------------:|:-------------------------: ![bend step 1](pics/installs/9_bend-1.jpg) | ![bend step 2](pics/installs/9_bend-2.jpg) | ![bend step 3](pics/installs/9_bend-3.jpg) Now you need to connect the cables from the debug port to the ESP device Based on above, on my ESP device with this adapter cable, I needed to connect like this | Put some hot glue on the connections to make sure they are not going anywhere :-------------------------:|:-------------------------: ![bend step 1](pics/installs/9_connected.jpg) | ![bend step 1](pics/installs/9_connected-hot-glue.jpg) 13. Secure the ESP in the handle area You should now have your ESP device connected to the robot like this | Place the ESP device like this; MAKE SURE YOU CAN CLOSE THE DUST BIN!! :-------------------------:|:-------------------------: ![secure step 1](pics/installs/10_step-1.jpg) | ![secure step 2](pics/installs/10_step-2.jpg) Put some tape on the cables, hole and ESP device to keep them in place| Put more tape over the ESP device to secure it and avoid shorts. You can also like me put a dot of hot glue :-------------------------:|:-------------------------: ![secure step 3](pics/installs/10_step-3.jpg) | ![secure step 4](pics/installs/10_step-4.jpg) Cover all cable but a little piece (needed for the bumper action to work) with tape to avoid it getting cought on anything. ![Installed externally](./pics/installs/external.jpg) 14. Put the bumper back on, battery back into its place, battery cover on, brush in its place and enjoy! ================================================ FILE: install-ha.md ================================================ # Install with Home Assistant My initial, and recommened, way to use this repair. All versions of this repair will be supported via this route, later versions should be much easier since I plan on making it all as one package on HACS. (ESPHome will probably still be required) ![HA Card](pics/setup/ha-card.png) **Overview of steps:** 1. Setup HACS and install required add-ons 2. Import the config to ESPHome 3. Flash the image to your ESP device 4. Connect the ESP device to your robot 5. Add the ESP device to Home Assistant 6. Setup the Home Assistant card 7. Install the ESP device on the robot 8. Enjoy your locally connected robot! I know this might be quite a bit overwhelming, but the reason there is this many steps is to have it as detailed as possible. Once again, at any point, feel free to ask for help. ## Step 1 We need to install certain add-ons to the home assistant installation to use all the features of this project. ### Home assistant add-ons Donwload "ESPHome Device Builder" by 1. In Home Assistant, go to `Settings` --> `Add-ons` --> `Add-on Store` --> click on `"ESPHome Device Builder"`. Or click this button: - [![Open your Home Assistant instance and show the dashboard of an add-on.](https://my.home-assistant.io/badges/supervisor_addon.svg)](https://my.home-assistant.io/redirect/supervisor_addon/?addon=5c53de3b_esphome&repository_url=https%3A%2F%2Fgithub.com%2Fesphome%2Fhome-assistant-addon) 3. Select install. 4. I would recommend to enable `Add to sidebar` and `Start on boot`. If you decide not to add it to the sidebar, you will need to open ESPHome by coming back to this page and selecting `Open web UI`. ### HACS If you don't already have HACS, [follow this guide](https://www.hacs.xyz/docs/use/) to set it up. Once you have HACS setup, open it and install the following addons: (search with the id number!) - `button-card` `146194325` - An button element to place on a dashboard with a lot of configurations to make the card look nice. - `browser_mod` `194140521` - Allow for a popup when clicking on settings or holding down the spot clean button. - Don't forget to add the "Browser Mod" integration in `Settings` -> `Devices & Services` -> `Add Integration` or click this button: - [![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=browser_mod) - It will ask you if you want to register your browser as a device, you don't need to do this for it to work. After installing these add-ons, you need to refresh your page, however, some browsers need a hard refresh. This you can do by pressing `Ctrl + Shift + R`. If it still does not want to work you might need to restart Home Assistant. ## Step 2 ### ESPHome Secrets Open the ESPHome Builder and click the "Secrets" in the top right. Make sure your secrets include at the minimum this: ```yaml # Generate at https://esphome.io/components/api/#api-key neato_vacuum_api: "" # Generate at https://bitwarden.com/password-generator/ neato_vacuum_ota: "" # Your Wi-Fi SSID and password wifi_ssid: "" wifi_password: "" ``` Once you have filled this file with your values, save it, and make sure to never share this file if asking for support etc. Remove the `<>` characters, these are used for marking a field of what you should replace. If you want to add more devices, best practice is to set the api key and ota password in your secrets file. Your wifi password and ssid should also be kept here. Since the esp device will be strapped to, or inside the robot OTA (over the air) updates is quite important for this use case. ### Config file [Download the ESPHome config file](https://github.com/philip2809/neato-brainslug/releases/latest/download/ha-esphome.yaml) Once back at the ESPHome main page, click the big green button in the bottom left to add a new device. Read the information, but for now, click "Continue" and either import the file you downloaded, or start with an empty configuration and paste the contents in. Open the file in edit mode in case it does not automatically open in edit mode. **The following two steps might be hard to do, feel free to ask for help in the discord or discussions.** It should look something like this: ![esphome config file](pics/setup/esphome-config-file.png) Whatever text is in green, has the `#` in the begining is not used, the `#` marks the line as a comment, so whatever config you want needs to be in white, add a `#` to the begining of the line you don't want to include. Here you can easily change some parts, if you board type is not listed you will need to add something like: ```yaml esp32: variant: ESP32C3 # Change this to whatever you have! framework: type: esp-idf ``` to this config, I have included some common boards here. If you want change the name, if needed the ip address etc. If you want to use home assistant or just the webserver, if you have an gen3 or gen2 robot. UART pins it is best to keep at default, 17 and 16 for most boards, the smaller C3 board the default is 7 and 6, however you can choose something else if you want in the config. In most cases, the pins labeled `TX` and `RX` cannot be used, as these are used to upload the firmware, you will need to find GPIO pins that support using using UART, on the ESP32 many of the GPIO pins can be used. There is many tutorials for the different boards, here is some common ones: - [ESP32](https://randomnerdtutorials.com/esp32-pinout-reference-gpios/) - [ESP8266](https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/) ## Step 3 Now you will need to build and flash the images onto your ESP device! While in the editor, press the "Install" button in the top right, since the device is not yet setup, select "Manual download", this will build the configuration file to an image you can flash, this might take a while on a fresh system, or not powerful hardware. Once the image has been built, select to download in "Factory format", save this file on your computer and open [ESPHome Web](https://web.esphome.io/). Since this uses WebSerial you will need to use a chromium based browser. ESPHome has an amazing [guide](https://esphome.io/guides/physical_device_connection/) if this is your first time doing this, but to summerize, if you have an usb-port on your device, connect to it, if not you will need to connect to the `TX`, `RX`, `GND` and `3.3V/5V` with an TTY adapter. Then go into bootloader mode by pressing the "BOOT" button, if you don't have one, connect `GPIO0` to `GND`. Once in ESPHome Web, connect your device to your computer, while going into bootloader mode, then select it in the list. Once selected, upload the firmware file you downloaded before and wait for it to finish. Once the device has connected you need to verify that it works and you can see the web server it is hosting before we continue. For most people one of two links will bring you to the ESP device's web server: - [`http://neato-vacuum.local`](http://neato-vacuum.local) - [`http://neato-vacuum.lan`](http://neato-vacuum.lan) **If you changed the name of your device in the config, these links will be different!** If neither of these link work, please check that the device actually connected to your wifi and see if you can get the ip-address of the ESP device. If you are still having problems or have trouble finding the ip-address, feel free to ask for help! ## Step 4 When you have navigated to the site of the ESP device it should look something like this: ![Webserver disconnected](pics/setup/step_4-webserver-disconnected.png) This is the webserver of the device. It will show up as not connected since we are not connected to the robot, we are only connected to a power source so that the ESP device can be configured. Now you can connect the device to the robot via the debug port to make sure that it works are you want to! To do this: 1. Turn the robot off 2. Take of the bumper of the robot 3. Connect to the robot - if you have an `gen2` robot, proceed to [the install guide](./install-esp-device-gen2.md) | Robot | ESP | |---|---| |RX|GPIO17 (TX of esp device)| |3.3V|3.3V| |TX|GPIO16 (RX of esp device)| |GND|GND| When it comes to UART, TX connects to RX and RX connects to TX. ![Connection diagram](pics/setup/step_4-connection-diagram.png) 4. Turn the robot back on, this should power up the ESP device and you can now go to the webserver interface page we saw before and the data from the robot should now show up! ![Webserver connected](pics/setup/step_4-webserver-connected.png) 5. Click the different buttons to make sure that it works, if you have a D3-D7, drive it around with the manual mode, however, remeber that the bumper is off! ## Step 5 After flashing and connecting the ESP device to the robot we need to add the ESP device into Home Assistant. 1. Power the robot on if it is off 2. In Home Assistant navigate to: `Settings` --> `Devices & Services` -- `Click "Add integration"` --> `Search "ESPHome"` 3. Enter the hostname or ip address of the ESPHome device - If you haven't change the name of the device in the config, it is most likely `neato-vacuum.local` or `neato-vacuum.lan` depending on your router. It is the same as the link that worked before in step 4. - If you want to use the ip address, find what ip the device got in your router. If you decide to use the ip, make sure to set it static! 4. Click submit and the device should be added. ## Step 6 Copy the contents of the Home Assistant card for your vacuum generation (the files are also in the [config](./config/) directory) - [`gen2`](https://github.com/philip2809/neato-brainslug/releases/latest/download/ha-card_gen2.yaml) - [`gen3`](https://github.com/philip2809/neato-brainslug/releases/latest/download/ha-card_gen3.yaml) **If you have changed the name in the ESPHome config:** 1. Paste the content into a text editor 2. Go to `Developer tools` --> `States` --> `In "Filter entities" search for "_fuel_percent"` 3. There should be a result for `sensor._fuel_percent` - This entity id is probaly the same as the name you gave but lowercase and dashes changed for underscores. 4. Replace all instances of `neato_vacuum` with your `` ### Add the card 1. Press the pen icon in the top right on the desired dashboard 2. Press `Add card` 3. Scroll to the buttom and select `Manual` 4. Paste the contents of the card (if you changed the name, then the version that you changed) ### Vacuum Entity You can also use neato-brainslug as an Home Assistant vacuum entity. The vacuum entity is needed in case you want to use any of the automations or scripts. ![Vacuum entity](pics/setup/ha-step_6-vacuum-entity.png) Sadly vacuum entities can only be added by editing the Home Assistant config files, however, I will walk though the entire proccess! 1. Going to `Settings` --> `Add-ons` --> `Add-on Store` --> `Open "File editor"`. - [![Open your Home Assistant instance and show the dashboard of an add-on.](https://my.home-assistant.io/badges/supervisor_addon.svg)](https://my.home-assistant.io/redirect/supervisor_addon/?addon=core_configurator&repository_url=https%3A%2F%2Fgithub.com%2Fesphome%2Fhome-assistant-addon) 2. Select install. 3. I would recommend to enable `Add to sidebar` and `Start on boot`. If you decide not to add it to the sidebar, you will need to come back here to open the file editor. 4. Open the file editor by clicking on "Open web UI" or if you added it to the sidebar, click on it in the sidebar. 5. Open the main `configuration.yaml` file by clicking on the folder icon in the top left then selecting the `configuration.yaml` file. 6. Add the following like to this config and then save by pressing the red save button in the top right or press `Ctrl + S`. ```yaml template: !include_dir_merge_list templates/ ``` 7. Click on the folder icon again and create a folder called `templates`. 8. Create a new file in this folder called `vacuums.yaml`. 9. Copy the contents of the Home Assistant entity for your vacuum generation (the files are also in the [config](./config/) directory) - [`gen2`](https://github.com/philip2809/neato-brainslug/releases/latest/download/ha-entity_gen2.yaml) - [`gen3`](https://github.com/philip2809/neato-brainslug/releases/latest/download/ha-entity_gen3.yaml) - if you have multiple vacuums, duplicate the config from the `- name:` part and change the ids! 10. Save the file and make sure the configuration is good by going to `Developer tools` --> `YAML` --> `Click on "Check configuration"` --> `If configuration is good, click on "All YAML configuration" under "YAML configuration reloading"`. ### Schedule automation Via Home Assistant you can also schedule your robot, this allows for smarter scheduling since this can check if someone is home (if setup of course), holidays etc. To set this up: 1. Go to `Settings` --> `Automations & Scenes` --> `Automations` --> Press the big blue `Create automation` in the bottom right corner 2. Press `Create new automation` 3. Add a trigger, for example that the vacuum should run every day at 08:00 4. Send an event to start the vacuum, either via the button esphome created **OR** via the vacuum entity you created ![Home Assistant Schedule Automation](pics/setup/ha-step_6-schedule-automation.png) You can add as many triggers you want, any trigger added will cause the automation to run, and then you can add `And if` rules to make sure it is only tirggered when all conditions added there are meet. ### Notifications Via Home Assistant you can also get notifications. For now with 1.2 the showcased notification system here is rudamentory, the notifications will be vastly improved with 1.3. 1. Go to `Settings` --> `Automations & Scenes` --> `Automations` --> Press the big blue `Create automation` in the bottom right corner 2. Press `Create new automation` 3. Add a trigger for when the robot error (and alert if you have a gen3 robot and want to) changes 4. Make it send a notification or a request to a notification service like unifiedpush. ![Home Assistant Notifications Automation](pics/setup/ha-step_6-notifications.png) For the notification, I would make the message: ```yaml Alert: {{ state_attr('vacuum.template_neato_vacuum', 'alert') }} Error: {{ state_attr('vacuum.template_neato_vacuum', 'error') }} ``` Remove the alert part in case you don't have a gen3 robot. You can also use the esphome sensor directly if you don't want to use the vacuum entity like: `{{ states('sensor.neato_vacuum_robot_error') }}` ## Step 7 **Before you make a permanent installation, make sure it all works via Home Assistant as you want it to!** Now lets install the ESP device: - [`gen2`](./install-esp-device-gen2.md) - [`gen3`](./install-esp-device-gen3.md) ## Step 8 Now you can enjoy your locally controllable neato vacuum cleaner! Of course there is some quirks with this repair, however we feel they are worth the ability to regain functionality. ================================================ FILE: install-no-ha.md ================================================ # Install without Home Assistant You can also use this repair without Home Assistant, however future versions of neato-brainslug will require a docker conatiner that is running, easiest installation will be via home assistant. ![Webserver](pics/setup/step_4-webserver-connected.png) **Overview of steps** 1. Flash ESP device with prebuilt images 2. Connect to esp wifi 3. Configure wifi 4. Make sure it works 5. Fix ESP device inside or outside robot 6. Enjoy a locally connected vacuum & how to install updates ### Step 1 I have made some prebuilt images for recommended ESP32s, `ESP32`, `ESP32-S3` and `ESP32-C3`, if you have another ESP32 that is not listed here, ask me and I will build you one! The easiest way to flash your ESP device is with the [Brainslug Web Flasher](https://brainslug.phma.dev/). ![Brainslug Web Flasher](pics/setup/brainslug-web-flasher.png) 1. Connect your device with a cable. (Note: many micro usb cables are power only, if one of your cables is not working, try another!) 2. Select your generation of neato and click "Connect" 3. Select your ESP32 device in the list, if you don't know which one is your ESP, unplug and replug your device to see what shows up! 4. Wait for it to connect and select "Install Neato Brainslug" ![Webflasher menu](pics/setup/brainslug-web-flasher-menu.png) 5. Follow the instructions in the web flasher. **If you get any errors about unsupported devices, then you are not using a recommended ESP32, please ask me for a build in that case!** **If you have multiple vacuums you will need prebuild images with different names or it will be annoying to connect to them, please ask for me for a build!** Once you have it installed it should go back to this menu: ![menu flashed with improv](pics/setup/brainslug-web-flasher-menu-flashed.png) Here you can also configure the wifi settings if your device is not too exotic, if you cannot configure the wifi here, continue onto step 2. ### Step 2 With the device still connected to your computer, go to the wifi settings of your computer or phone and connect to the network hosted by the esp device. - SSID: `neato-brainslug` - Password: `make-it-suck-again` Once you have connected your browser should automatically open a window to "login" to the network, you may need to see your notifications on the phone or open the web browser on a computer, but if none of that works, go to [`http://192.168.4.1/`](http://192.168.4.1/) manually. When you are on this site you should see the default captive portal that looks like this: ![Captive portal on esphome](./pics/setup/noha-step_1-captive-portal.png) Now you will need to select and enter your wifi details so that the ESP device can connect to it. When you hit save, it will restart and connect to your wifi, if you entered your details incorrectly or it could not connect, the network you just connected to, `neato-brainslug` should show up once again in your wifi settings so you can try again. Once the device has connected you need to verify that it works and you can see the web server it is hosting before we continue. For most people one of two links will bring you to the ESP device's web server: - [`neato-vacuum.local`](http://neato-vacuum.local) - [`neato-vacuum.lan`](http://neato-vacuum.lan) If neither of these link work, please check that the device actually connected to your wifi and see if you can get the IP address of the ESP device. If you are still having problems or have trouble finding the IP address, feel free to ask for help. ### Step 4 When you have navigated to the site of the ESP device it should look something like this: ![Webserver disconnected](pics/setup/step_4-webserver-disconnected.png) This is the webserver of the device. It will show up as not "loading..." since we are not connected to the robot, we are only connected to a power source so that the ESP device can be configured. Now you can connect the device to the robot via the debug port to make sure that it works are you want to! To do this: 1. Turn the robot off 2. Take of the bumper off the robot 3. Connect to the robot - if you have an `gen2` robot, proceed to [the install guide](./install-esp-device-gen2.md) | Robot | ESP | |---|---| |RX|GPIO17 (TX of esp device)| |3.3V|3.3V| |TX|GPIO16 (RX of esp device)| |GND|GND| When it comes to UART, TX connects to RX and RX connects to TX. **ESP32-C3 HAS TX ON GPIO7 and RX ON GPIO6, so in that case just remove the "1" from the image below, connect the blue wire to GPIO6 and yellow wire to GPIO7** ![Connection diagram](pics/setup/step_4-connection-diagram.png) 4. Turn the robot back on, this should power up the ESP device and you can now go to the webserver interface page we saw before and the data from the robot should now show up. ![Webserver connected](pics/setup/step_4-webserver-connected.png) 5. Click the different buttons to make sure that it works, if you have a gen3 robot, drive it around with the manual mode! ### Step 5 Now lets install the ESP device: - [`gen2`](./install-esp-device-gen2.md) - [`gen3`](./install-esp-device-gen3.md) ### Step 6 Now you can enjoy your locally controllable neato vacuum cleaner. Eventually there will be updates, either as new features or stability fixes, however all of this connecting and flashing will no longer be required since ESPHome have support for OTA-updates. To update when a new version comes out, you will need to: 1. Download the OTA update file for your device, recommended ESP32 devices is here: |`gen2` | `gen3` | |---|---| | [ESP32](https://github.com/philip2809/neato-brainslug/releases/latest/download/nbs-gen2-esp32.ota.bin) | [ESP32](https://github.com/philip2809/neato-brainslug/releases/latest/download/nbs-gen3-esp32.ota.bin) | | [ESP32-S3](https://github.com/philip2809/neato-brainslug/releases/latest/download/nbs-gen2-esp32s3.ota.bin) | [ESP32-S3](https://github.com/philip2809/neato-brainslug/releases/latest/download/nbs-gen3-esp32s3.ota.bin) | | [ESP32-C3](https://github.com/philip2809/neato-brainslug/releases/latest/download/nbs-gen2-esp32c3.ota.bin) | [ESP32-C3](https://github.com/philip2809/neato-brainslug/releases/latest/download/nbs-gen3-esp32c3.ota.bin) | 2. Go to the webserver of the device and upload the file under the OTA section, then press "UPDATE" ![OTA update](pics/setup/step_6-webserver-ota.png) 3. Wait for it to update and restart then refresh the page, you should now be on the latest version! ================================================ FILE: manual.md ================================================ # Robot manual Some of the features of the robot is detailed here. The pictures here show the webserver, but the same applies to the entities exposed in Home Assistant in case that is used. This document will be expanded upon with time, when more info about the robot is found out! ## Buttons and last cleaning ![](./pics/esphome/webserver/buttons-last-cleaning.png) - Use the different buttons for controlling the robot, some robots may not have all functions availible. - `gen2` does not have last cleaning data ## Basic data and spot clean ![](./pics/esphome/webserver/basic-data-spot-clean.png) - Basic status of the robot, battery, docked, erros/alerts and the time by ESP device - Spot clean a custom area by selecting the size and pressing the spot clean button. `gen2` robots may or may not do this properly ## ESP schedule ![](./pics/esphome/webserver/esp-schedule.png) - Schedule starting the robot running on the ESP device, compares to the NBS time ## Robot settings ![](./pics/esphome/webserver/robot-settings.png) - These are the settings exposed by the robot, the settings is a little different based on your robot generation, but it's a hit or miss based on what setting does anything or if it applies correctly - `gen3` robots have the "Navigation Mode" option, while it needs to be verified more an idea based on some observations the different modes could mean. The mode needs to be reselected after each restart of the robot - `gentle` - the bot shouldn't push any object that is higher than itself (visible via the lidar). - `deep` - the robot will drive into the corners as deep as it can, drive a bit backwards and then clean the corners in a curve. - `Intense clean` - reduced the distance between the lanes - `Wall Enable/Follower` - follows all walls one round and starts cleaning 'senseful' areas. No wall follower will follow the wall for a distance and then start cleaning the area before following the wall for the next area. ## Manual driving ![](./pics/esphome/webserver/manual-driving.png) - Only for `gen3` robots, make sure to start manual cleaning before using the buttons. ## Detailed data ![](./pics/esphome/webserver/detailed-data.png) - Detailed data about the robot, text fields generated by the timezone selector and schedule as well as the option to change the log level ## Timezone select and ota/info ![](./pics/esphome/webserver/timezone-ota.png) - If your NBS time is incorrect, select your timezone here, it will update the timezone textfield in the detailed data part - Upload OTAs, check ESP firmware type (gen2 or gen3) and toggle the debug logs ## Debug logs ![](./pics/esphome/webserver/debug-logs.png) - When enabled, you will see this at the bottom of the page, change the log level to get more or less detailed logs. As explained in the [faq](faq.md), seeing `GetState`, `GetErr` and `GetCharger` is normal and happens when the ESP reads the status of the robot. ================================================ FILE: research/all_errors_alerts_states.3.2.0.md ================================================ ## All the states that exist in firmware version 3.2.0 I don't expect much core functionallity has changed, so the list should be about the same for `4.5.3`, however the number have changed! Also `4.5.3` not only has an "Current UI State is" but also an "Current Robot State is", and I have included what those states probably are. #### Errors/Alerts This list I took from the `4.5.3` firmware with the command `SetUIError list` ``` 200: UI_ALERT_INVALID 201: UI_ALERT_RETURN_TO_BASE 202: UI_ALERT_RETURN_TO_BASE_PWR 203: UI_ALERT_RETURN_TO_START 204: UI_ALERT_RETURN_TO_CHARGE 205: UI_ALERT_DUST_BIN_FULL 206: UI_ALERT_BUSY_CHARGING 207: UI_ALERT_OLD_ERROR 208: UI_ALERT_RECOVERING_LOCATION 209: UI_ALERT_INFO_THANK_YOU 210: UI_ALERT_LOG_READ_FAIL 211: UI_ALERT_LOG_WRITE_FAIL 212: UI_ALERT_USB_DISCONNECTED 213: UI_ALERT_SWUPDATE_SUCCESS // diff from gen2 214: UI_ALERT_SWUPDATE_FAIL 215: UI_ALERT_LOG_WRITE_SUCCESS 216: UI_ALERT_TIME_NOT_SET 217: UI_ALERT_TIME_SET 218: UI_ALERT_TIMER_SET 219: UI_ALERT_TIMER_REMOVED 220: UI_ALERT_ENABLE_TIMER 221: UI_ALERT_CHARGING_POWER 222: UI_ALERT_CHARGING_BASE 223: UI_ALERT_BATTERY_ChargeBaseCommErr 224: UI_ALERT_CONNECT_CHRG_CABLE 225: UI_ALERT_WAIT_FOR_POWER_SWITCH_DETECT 226: UI_ALERT_LINKEDAPP 227: UI_ALERT_ORIGIN_UNCLEAN 228: UI_ALERT_LOGUPLOAD_FAIL 229: UI_ALERT_BRUSH_CHANGE 230: UI_ALERT_FILTER_CHANGE 231: UI_ALERT_PERSISTENT_RELOCALIZATION_FAIL 232: UI_ALERT_TRAINING_MULTIPLE_FLOORPLANS_VALID 233: UI_ALERT_MULTIPLE_FLOORPLANS_VALID 234: UI_ALERT_PM_LOAD_FAIL 235: UI_ALERT_PM_SETUP_FAIL 236: UI_ALERT_ACQUIRING_PERSISTENT_MAP_IDS 237: UI_ALERT_CREATING_AND_UPLOADING_MAP 238: UI_ALERT_PM_START_CLEAN_FAIL 239: UI_ALERT_NAV_FLOORPLAN_NOT_CREATED 240: UI_ALERT_NAV_FLOORPLAN_ZONE_UNREACHABLE 241: UI_ALERT_NAV_FLOORPLAN_ZONE_WRONG_FLOOR 242: UI_ALERT_TRAINING_MAP_SPARSE 243: UI_ERROR_CHECK_BATTERY_SWITCH 244: UI_ERROR_DISCONNECT_CHRG_CABLE 245: UI_ERROR_DISCONNECT_USB_CABLE 246: UI_ERROR_SCHED_OFF 247: UI_ERROR_TIME_NOT_SET 248: UI_ERROR_DUST_BIN_EMPTIED 249: UI_ERROR_DUST_BIN_MISSING 250: UI_ERROR_DUST_BIN_FULL 251: UI_ERROR_BATTERY_OVERTEMP 252: UI_ERROR_UNABLE_TO_RETURN_TO_BASE 253: UI_ERROR_QA_FAIL // diff 254: UI_ERROR_BUMPER_STUCK 255: UI_ERROR_PICKED_UP 256: UI_ERROR_RECONNECT_FAILED 257: UI_ERROR_LWHEEL_STUCK 258: UI_ERROR_RWHEEL_STUCK 259: UI_ERROR_LDS_JAMMED 260: UI_ERROR_LDS_DISCONNECTED 261: UI_ERROR_LDS_MISSED_PACKETS 262: UI_ERROR_LDS_BAD_PACKETS 263: UI_ERROR_LDS_LASER_OVER_POWER // diff 264: UI_ERROR_LDS_LASER_UNDER_POWER // diff 265: UI_ERROR_BRUSH_STUCK 266: UI_ERROR_BRUSH_OVERLOAD 267: UI_ERROR_VACUUM_STUCK 268: UI_ERROR_VACUUM_SLIP 269: UI_ERROR_BATTERY_CRITICAL 270: UI_ERROR_BATTERY_OverVolt 271: UI_ERROR_BATTERY_UnderVolt 272: UI_ERROR_BATTERY_UnderCurrent 273: UI_ERROR_BATTERY_Mismatch 274: UI_ERROR_BATTERY_LithiumAdapterFailure 275: UI_ERROR_BATTERY_UnderTemp 276: UI_ERROR_BATTERY_Unplugged 277: UI_ERROR_BATTERY_NoThermistor 278: UI_ERROR_BATTERY_BattUnderVoltLithiumSafety 279: UI_ERROR_BATTERY_InvalidSensor 280: UI_ERROR_BATTERY_PermanentError // diff 281: UI_ERROR_BATTERY_Fault // diff 282: UI_ERROR_NAVIGATION_UndockingFailed 283: UI_ERROR_NAVIGATION_Falling 284: UI_ERROR_NAVIGATION_PinkyCommsFail 285: UI_ERROR_NAVIGATION_NoMotionCommands 286: UI_ERROR_NAVIGATION_BackDrop_LeftBump 287: UI_ERROR_NAVIGATION_BackDrop_FrontBump 288: UI_ERROR_NAVIGATION_BackDrop_WheelExtended 289: UI_ERROR_NAVIGATION_RightDrop_LeftBump 290: UI_ERROR_NAVIGATION_NoExitsToGo 291: UI_ERROR_NAVIGATION_PathProblems_ReturningHome 292: UI_ERROR_NAVIGATION_NoProgress 293: UI_ERROR_NAVIGATION_BadMagSensor 294: UI_ERROR_NAVIGATION_Origin_Unclean 295: UI_ERROR_NAVIGATION_PathBlocked_GoingToZone // diff 296: UI_ERROR_SHUTDOWN 297: UI_ERROR_DFLT_APP 298: UI_ERROR_CORRUPT_SCB 299: UI_ERROR_SCB_FLASH_READ 300: UI_ERROR_SCB_SIGNATURE 301: UI_ERROR_SCB_LENGTH_MISMATCH 302: UI_ERROR_SCB_CHECKSUM 303: UI_ERROR_SCB_VALIDATION 304: UI_ERROR_SCB_INTERFACE 305: UI_ERROR_HARDWARE_FAILURE 306: UI_ERROR_DECK_DEBRIS 307: UI_ERROR_RDROP_STUCK 308: UI_ERROR_LDROP_STUCK 309: UI_ERROR_UNABLE_TO_SEE 310: UI_ERROR_TILTED_ON_CLEANING_STARTUP 311: UI_ERROR_SWUPDATE_FILEMISSING 312: UI_ERROR_FLIGHT_SENSOR_DISCONNECTED // diff 313: UI_ERROR_WIFIPSWDORROUTERISSUE 314: UI_ERROR_CONNECTINGTOSERVER 315: UI_ERROR_TIMEDOUTCONNECTROUTER 316: LAST_UI_ALERT ``` For gen2 robot, confirmed with a couple of codes, but should confirm with more: ``` 200: UI_ALERT_INVALID 201: UI_ALERT_RETURN_TO_BASE 202: UI_ALERT_RETURN_TO_BASE_PWR 203: UI_ALERT_RETURN_TO_START 204: UI_ALERT_RETURN_TO_CHARGE 205: UI_ALERT_DUST_BIN_FULL 206: UI_ALERT_BUSY_CHARGING 207: UI_ALERT_OLD_ERROR 208: UI_ALERT_RECOVERING_LOCATION 209: UI_ALERT_INFO_THANK_YOU 210: UI_ALERT_LOG_READ_FAIL 211: UI_ALERT_LOG_WRITE_FAIL 212: UI_ALERT_USB_DISCONNECTED 213: UI_ALERT_SWUPDATE_FAIL 214: UI_ALERT_LOG_WRITE_SUCCESS 215: UI_ALERT_TIME_NOT_SET 216: UI_ALERT_TIME_SET 217: UI_ALERT_TIMER_SET 218: UI_ALERT_TIMER_REMOVED 219: UI_ALERT_ENABLE_TIMER 220: UI_ALERT_CHARGING_POWER 221: UI_ALERT_CHARGING_BASE 222: UI_ALERT_BATTERY_ChargeBaseCommErr 223: UI_ALERT_CONNECT_CHRG_CABLE 224: UI_ALERT_WAIT_FOR_POWER_SWITCH_DETECT 225: UI_ALERT_WAIT_TILL_FULL_CHARGE // does not exist in gen3 226: UI_ALERT_LINKEDAPP // last alert for gen2, gen3 has quite a bit more 227: UI_ERROR_CHECK_BATTERY_SWITCH 228: UI_ERROR_DISCONNECT_CHRG_CABLE 229: UI_ERROR_DISCONNECT_USB_CABLE 230: UI_ERROR_SCHED_OFF 231: UI_ERROR_TIME_NOT_SET 232: UI_ERROR_DUST_BIN_EMPTIED 233: UI_ERROR_DUST_BIN_MISSING 234: UI_ERROR_DUST_BIN_FULL 235: UI_ERROR_BATTERY_OVERTEMP 236: UI_ERROR_UNABLE_TO_RETURN_TO_BASE 237: UI_ERROR_QA 238: UI_ERROR_BUMPER_STUCK 239: UI_ERROR_PICKED_UP 240: UI_ERROR_RECONNECT_FAILED 241: UI_ERROR_LWHEEL_STUCK 242: UI_ERROR_RWHEEL_STUCK 243: UI_ERROR_LDS_JAMMED 244: UI_ERROR_LDS_DISCONNECTED 245: UI_ERROR_LDS_MISSED_PACKETS 246: UI_ERROR_LDS_BAD_PACKETS 247: UI_ERROR_BRUSH_STUCK 248: UI_ERROR_BRUSH_OVERLOAD 249: UI_ERROR_VACUUM_STUCK 250: UI_ERROR_VACUUM_SLIP 251: UI_ERROR_BATTERY_CRITICAL 252: UI_ERROR_BATTERY_OverVolt 253: UI_ERROR_BATTERY_UnderVolt 254: UI_ERROR_BATTERY_UnderCurrent 255: UI_ERROR_BATTERY_Mismatch 256: UI_ERROR_BATTERY_LithiumAdapterFailure 257: UI_ERROR_BATTERY_UnderTemp 258: UI_ERROR_BATTERY_Unplugged 259: UI_ERROR_BATTERY_NoThermistor 260: UI_ERROR_BATTERY_BattUnderVoltLithiumSafety 261: UI_ERROR_BATTERY_InvalidSensor 262: UI_ERROR_NAVIGATION_UndockingFailed 263: UI_ERROR_NAVIGATION_Falling 264: UI_ERROR_NAVIGATION_PinkyCommsFail 265: UI_ERROR_NAVIGATION_NoMotionCommands 266: UI_ERROR_NAVIGATION_BackDrop_LeftBump 267: UI_ERROR_NAVIGATION_BackDrop_FrontBump 268: UI_ERROR_NAVIGATION_BackDrop_WheelExtended 269: UI_ERROR_NAVIGATION_RightDrop_LeftBump 270: UI_ERROR_NAVIGATION_NoExitsToGo 271: UI_ERROR_NAVIGATION_PathProblems_ReturningHome 272: UI_ERROR_NAVIGATION_NoProgress 273: UI_ERROR_NAVIGATION_BadMagSensor 274: UI_ERROR_NAVIGATION_Origin_Unclean 275: UI_ERROR_WARNING 276: UI_ERROR_SHUTDOWN 277: UI_ERROR_WDT 278: UI_ERROR_DFLT_APP 279: UI_ERROR_CORRUPT_SCB 280: UI_ERROR_SCB_FLASH_READ 281: UI_ERROR_SCB_SIGNATURE 282: UI_ERROR_SCB_LENGTH_MISMATCH 283: UI_ERROR_SCB_CHECKSUM 284: UI_ERROR_SCB_VALIDATION 285: UI_ERROR_SCB_INTERFACE 286: UI_ERROR_HARDWARE_FAILURE 287: UI_ERROR_DECK_DEBRIS 288: UI_ERROR_RDROP_STUCK 289: UI_ERROR_LDROP_STUCK 290: UI_ERROR_UNABLE_TO_SEE 291: UI_ERROR_TILTED_ON_CLEANING_STARTUP 292: UI_ERROR_SWUPDATE_FILEMISSING 293: UI_ERROR_WIFIPSWDORROUTERISSUE 294: UI_ERROR_CONNECTINGTOSERVER 295: UI_ERROR_TIMEDOUTCONNECTROUTER 296: LAST_UI_ALERT ``` #### UI States: ``` UIMGR_STATE_POWERUP UIMGR_STATE_IDLE UIMGR_STATE_USERMENU UIMGR_STATE_STANDBY UIMGR_STATE_STARTSPOTCLEANING UIMGR_STATE_SPOTCLEANINGRUNNING UIMGR_STATE_STARTHOUSECLEANING UIMGR_STATE_HOUSECLEANINGRUNNING UIMGR_STATE_HOUSECLEANINGPAUSED UIMGR_STATE_SPOTCLEANINGPAUSED UIMGR_STATE_DOCKINGRUNNING UIMGR_STATE_DOCKINGPAUSED UIMGR_STATE_CLEANINGTESTRUNNING UIMGR_STATE_CLEANINGSUSPENDED UIMGR_STATE_CLEANINGSUSPENDEDMENU UIMGR_STATE_TESTMENU UIMGR_STATE_MANUALDRIVING UIMGR_STATE_TESTMODE UIMGR_STATE_INITIALSETUPMENU UIMGR_STATE_SMARTDEVICECONTROL UIMGR_STATE_USB_LOGCOPY UIMGR_STATE_SWUPGRADE UIMGR_STATE_OTA_LOGUPLOAD UIMGR_STATE_INVALID ``` #### Robot States: ``` ST_F3_InteriorCleaning ST_F1_Undocking ST_F2_PartialMapManagement ST_F21_Exploring ST_F4_BoundaryFollowing ST_F5_PickedUp ST_F6_CleaningErrRecovery ST_F7_CleaningError ST_G3_InteriorCleaning ST_G1_Undocking ST_G2_PartialMapManagement ST_G21_Exploring ST_G4_BoundaryFollowing ST_G5_PickedUp ST_G6_CleaningErrRecovery ST_G7_CleaningError ST_M1_Charging_Cleaning ST_M2_Charging_StdBy ST_T3_ProxFollowTest ST_T6_BoundFollowOnly ST_T7_TestDocking ST_T8_TestService ST_T1_TestObstacleMonitor ST_T4_TestMotionExecutor ST_A_Init ST_C_Standby ST_F_Cleaning ST_G_SpotCleaning ST_K_Critical ST_L_Safety ST_M_Charging ST_P_PopState ST_T_Test ST_X_ManNav ´´´ ================================================ FILE: research/command-experiments.md ================================================ # Experimentation with commands If all of these experiments prove successful, the robot would be able to be driven via the serial interface fully, meaning a custom program to drive it via serial and use the lidar data to know where to drive would be possible. Conditions needed to make a ROS2, slam_toolbox cleaning possible, and they cannot interfere with each other: - Rotate LDS - Get LDS Scan periodically - Drive - Stop at any time, one or two wheels - Change direction etc - GetDigitalSensors/GetAnalogSensors? - Get info about other sensors/state I will need to do more experimatation on this part; but this is a command to drive forward: `SetMotor RWheelDist 3000 LWheelDist 3000 Speed 60` Stop the wheels: `SetMotor LWheelDisable RWheelDisable` You can at any time can the speed by sending a new command, stopping the motors can be done as stated with the disable commands, but then you need to enable them again. A faster way would be to just set the distance to 1mm. You cannot the the values to 0, those are ignored. If you send a commad with a space in the begninning the robot will say: ``` Nice try, but I'm not falling for that one again! :P ``` I have now tested that you can send commands while reciving data and that the robot will execute them, it does do so. However I have a small problem, via esphome the `GetLdsScan` crashes my esp when the delimiter is set to the endbyte sent by the robot `\x1A`, it works when setting a line by line reading, so if the fully custom solution does come, then it would not be via esphome. Since the LDS scan takes a while to arrive, at least via the esphome console but that does however go via my ha and network, it was quite a lot faster via direct serial connection, but we would still need to queue the commands, something like like: 1. GetErr (Could maybe happen every 10th loop) 2. GetDigitalSensors (bumper, dustbin & pickup) 3. GetAnalogSensors (drop-, mag- and wall-sensor) 4. GetLdsScan (lidar) - Repeat `GetMotor` can be used to get current speed and distance traveled from an 0,0 point, however, since we will be setting the speed with the `SetMotor` commands, we already know what the speed is, and the direction we can calculate based what wheel (and its rpm) is spinning. GetState will no longer be needed as we will be in TestMode so this will always be `UIMGR_STATE_TESTMODE`. There is also quite a lot of [hidden commands](./hidden-commands.md). ### Raw commands #### Keeping here for remembering [02:16:45.118][D][uart_debug:158]: <<< "GetDigitalSensors\r\n" [02:16:45.119][D][uart_debug:158]: <<< "Digital Sensor Name, Value\r\n" [02:16:45.119][D][uart_debug:158]: <<< "SNSR_DC_JACK_IS_IN,0\r\n" [02:16:45.119][D][uart_debug:158]: <<< "SNSR_DUSTBIN_IS_IN,1\r\n" [02:16:45.129][D][uart_debug:158]: <<< "SNSR_LEFT_WHEEL_EXTENDED,0\r\n" [02:16:45.145][D][uart_debug:158]: <<< "SNSR_RIGHT_WHEEL_EXTENDED,0\r\n" [02:16:45.157][D][uart_debug:158]: <<< "LSIDEBIT,0\r\n" [02:16:45.171][D][uart_debug:158]: <<< "LFRONTBIT,0\r\n" [02:16:45.185][D][uart_debug:158]: <<< "LLDSBIT,0\r\n" [02:16:45.200][D][uart_debug:158]: <<< "RSIDEBIT,0\r\n" [02:16:45.212][D][uart_debug:158]: <<< "RFRONTBIT,0\r\n" [02:16:45.223][D][uart_debug:158]: <<< "RLDSBIT,0\r\n" [02:17:58.028][D][uart_debug:158]: <<< "GetAnalogSensors\r\n" [02:17:58.028][D][uart_debug:158]: <<< "SensorName,Unit,Value\r\n" [02:17:58.028][D][uart_debug:158]: <<< "BatteryVoltage,mV,14585,\r\n" [02:17:58.028][D][uart_debug:158]: <<< "BatteryCurrent,mA,-238,\r\n" [02:17:58.041][D][uart_debug:158]: <<< "BatteryTemperature,mC,22800,\r\n" [02:17:58.054][D][uart_debug:158]: <<< "ExternalVoltage,mV,0,\r\n" [02:17:58.069][D][uart_debug:158]: <<< "AccelerometerX,mG,16,\r\n" [02:17:58.081][D][uart_debug:158]: <<< "AccelerometerY,mG,2,\r\n" [02:17:58.096][D][uart_debug:158]: <<< "AccelerometerZ,mG,963,\r\n" [02:17:58.113][D][uart_debug:158]: <<< "VacuumCurrent,mA,0,\r\n" [02:17:58.128][D][uart_debug:158]: <<< "SideBrushCurrent,mA,0,\r\n" [02:17:58.141][D][uart_debug:158]: <<< "MagSensorLeft,VAL,0,\r\n" [02:17:58.156][D][uart_debug:158]: <<< "MagSensorRight,VAL,0,\r\n" [02:17:58.171][D][uart_debug:158]: <<< "WallSensor,mm,255,\r\n" [02:17:58.184][D][uart_debug:158]: <<< "DropSensorLeft,mm,19,\r\n" [02:17:58.200][D][uart_debug:158]: <<< "DropSensorRight,mm,19,\r\n" [02:44:43.617][D][uart_debug:158]: <<< "GetMotor\r\n" [02:44:43.632][D][uart_debug:158]: <<< "Parameter,Value\r\n" [02:44:43.645][D][uart_debug:158]: <<< "Brush_RPM,0\r\n" [02:44:43.659][D][uart_debug:158]: <<< "Brush_mA,0\r\n" [02:44:43.673][D][uart_debug:158]: <<< "Vacuum_RPM,0\r\n" [02:44:43.686][D][uart_debug:158]: <<< "Vacuum_mA,0\r\n" [02:44:43.701][D][uart_debug:158]: <<< "LeftWheel_RPM,0\r\n" [02:44:43.715][D][uart_debug:158]: <<< "LeftWheel_Load%,0\r\n" [02:44:43.730][D][uart_debug:158]: <<< "LeftWheel_PositionInMM,-365\r\n" [02:44:43.742][D][uart_debug:158]: <<< "LeftWheel_Speed,0\r\n" [02:44:43.757][D][uart_debug:158]: <<< "RightWheel_RPM,0\r\n" [02:44:43.768][D][uart_debug:158]: <<< "RightWheel_Load%,0\r\n" [02:44:43.784][D][uart_debug:158]: <<< "RightWheel_PositionInMM,-365\r\n" [02:44:43.802][D][uart_debug:158]: <<< "RightWheel_Speed,0\r\n" [02:44:43.812][D][uart_debug:158]: <<< "ROTATION_SPEED,0.00\r\n" [02:44:43.830][D][uart_debug:158]: <<< "SideBrush_mA,0\r\n" ================================================ FILE: research/command_mapping.md ================================================ ### Command mapping for gen3 robots ```csv Error, Message, Detail message 223: UI_ALERT_BATTERY_ChargeBaseCommErr, Batter fault, tbd 239: UI_ALERT_NAV_FLOORPLAN_NOT_CREATED 240: UI_ALERT_NAV_FLOORPLAN_ZONE_UNREACHABLE 241: UI_ALERT_NAV_FLOORPLAN_ZONE_WRONG_FLOOR 249: UI_ERROR_DUST_BIN_MISSING, Put dirt bin back in 250: UI_ERROR_DUST_BIN_FULL, Please empty my dirt bin and filter, Empty dirt bin & filter and clean drop & wall sensors 251: UI_ERROR_BATTERY_OVERTEMP, Batter fault, tbd 255: UI_ERROR_PICKED_UP, Please put me down, tbd 256: UI_ERROR_RECONNECT_FAILED, Move base to new location, tbd 257: UI_ERROR_LWHEEL_STUCK, Clean my left wheel, tbd 258: UI_ERROR_RWHEEL_STUCK, Clean my right wheel, tbd 259: UI_ERROR_LDS_JAMMED, Press button on robot to continue (1000), tbd 260: UI_ERROR_LDS_DISCONNECTED, Press button on robot to continue (5000), tbd 261: UI_ERROR_LDS_MISSED_PACKETS, Reboot me, tbd 262: UI_ERROR_LDS_BAD_PACKETS, Press button on robot to continue (4000), tbd 263: UI_ERROR_LDS_LASER_OVER_POWER, Vision error (4101), Please press the start button on the robot to continue. 264: UI_ERROR_LDS_LASER_UNDER_POWER, Vision error (4102), Please press the start button on the robot to continue. 265: UI_ERROR_BRUSH_STUCK, Clean my brush 266: UI_ERROR_BRUSH_OVERLOAD, Clean my brush 267: UI_ERROR_VACUUM_STUCK, Press button on robot to continue 269: UI_ERROR_BATTERY_CRITICAL,Battery fault,tbd 270: UI_ERROR_BATTERY_OverVolt,Battery fault,tbd 271: UI_ERROR_BATTERY_UnderVolt,Battery fault,tbd 272: UI_ERROR_BATTERY_UnderCurrent,Battery fault,tbd 273: UI_ERROR_BATTERY_Mismatch,Battery fault,tbd 274: UI_ERROR_BATTERY_LithiumAdapterFailure,Battery fault,tbd 275: UI_ERROR_BATTERY_UnderTemp,Battery fault,tbd 276: UI_ERROR_BATTERY_Unplugged,Battery fault,tbd 277: UI_ERROR_BATTERY_NoThermistor,Battery fault,tbd 278: UI_ERROR_BATTERY_BattUnderVoltLithiumSafety,Battery fault,tbd 279: UI_ERROR_BATTERY_InvalidSensor,Battery fault,tbd 280: UI_ERROR_BATTERY_PermanentError,Battery fault,tbd 281: UI_ERROR_BATTERY_Fault,Battery fault,tbd 282: UI_ERROR_NAVIGATION_UndockingFailed, Clear my path (2000), tbd 283: UI_ERROR_NAVIGATION_Falling, Clear my path (2001), tbd 285: UI_ERROR_NAVIGATION_NoMotionCommands, Clear my path (2003), I'm stuck. Please free me - pick me up and move me no more than 2 feet so I can recover my location. 286: UI_ERROR_NAVIGATION_BackDrop_LeftBump, Clear my path (2004), tbd 287: UI_ERROR_NAVIGATION_BackDrop_FrontBump, Clear my path (2005), tbd 288: UI_ERROR_NAVIGATION_BackDrop_WheelExtended, Clear my path (2006), tbd 289: UI_ERROR_NAVIGATION_RightDrop_LeftBump, Clear my path (2007), tbd 290: UI_ERROR_NAVIGATION_NoExitsToGo, Clear my path (2008), tbd 291: UI_ERROR_NAVIGATION_PathProblems_ReturningHome, tbd 292: UI_ERROR_NAVIGATION_NoProgress, Clear my path (2010), tbd 293: UI_ERROR_NAVIGATION_BadMagSensor, Clear my path (2012), tbd 295: UI_ERROR_NAVIGATION_PathBlocked_GoingToZone, The path to a zone is blocked (2013), tbd 296: UI_ERROR_SHUTDOWN, Remove me from base to shut down, tbd 306: UI_ERROR_DECK_DEBRIS, Dust me off, tbd 307: UI_ERROR_RDROP_STUCK, Clear right drop sensor 308: UI_ERROR_LDROP_STUCK, Clear left drop sensor ``` ================================================ FILE: research/findings.md ================================================ My findings about the Neato D3 Connected, however should be same/very similar for any Neato connected robot. I found an old Neato D3 that was broken, the left wheel didn't spin so I opened it up and realized the black cable for the left motor was not connected, it had ripped out of the JST connector. I ordered some new JST connectors since I didn't have any and because I didn't have the correct crimp I soldered an already crimped cable that I got with the set to the cable and connected it to a new JST connector. One connected back to the board it worked perfectly, ran a couple of cleanings in my apartment and it works very well, the left wheels gears is starting to wear off so I am thinking of 3d printing some new ones. When I got the robot I had firmware `4.5.3_189`, the factory firmware on my robot is `3.2.0_305` and I am easily able to switch between them now. Since you always can upgrade to `4.5.3_189` I will be basing this project of that version, and that is that all commands etc will be using unless otherwise noted. Here is the different firmware images availible: https://github.com/RobertSundling/neato-botvac I was able to update from the factory firmware using the original neato images with the certificates expired, both to update to the latest `4.5.3` but also to update to `4.2.0`. I don't know why exactly this worked for me, I never connected it to the internet. In case you have problems, feel free to ask for help here, and I can make a custom ntp server if needed. Lets clearify a quick thing, I think this is obvious to many, but since I myself got confused lets write it down. The front of the robot is where the bumper is, back is where the charger and excuaset is. If we look at the robot from a top down view seeing in the way that the robot is going to drive forward, the right side is where the button and blinky lights are. Left is the other side. Once the bot was working, my journey began. I have split up the different parts into different documents, find them below: - [Setup network](./setup-network.md) - [Serial interface](./serial.md) - [Command experimentations](./command-experiments.md) User @algaen has checked out the [serial](./other-robots/serial-D8.md) and [nmap](./other-robots/nmap-D8.md) for their D8 robot, it is probably the same for the D9 and D10 robots, they use a compleatly different board and firmware, sadly not anything that is supported by this project because the serial console is behind a password lock. ================================================ FILE: research/gen4/nmap-D8.md ================================================ C:\Users\algra>nmap -p0- -v -A -T4 XX.XX.50.100 Starting Nmap 7.80 ( https://nmap.org ) at 2025-11-30 10:58 Mountain Standard Time NSE: Loaded 151 scripts for scanning. NSE: Script Pre-scanning. Initiating NSE at 10:58 Completed NSE at 10:58, 0.00s elapsed Initiating NSE at 10:58 Completed NSE at 10:58, 0.00s elapsed Initiating NSE at 10:58 Completed NSE at 10:58, 0.00s elapsed Initiating Ping Scan at 10:58 Scanning XX.XX.50.100 [4 ports] Completed Ping Scan at 10:58, 0.47s elapsed (1 total hosts) Initiating Parallel DNS resolution of 1 host. at 10:58 Completed Parallel DNS resolution of 1 host. at 10:58, 0.00s elapsed Initiating SYN Stealth Scan at 10:58 Scanning Neato-Robot.abcd.ca (XX.XX.50.100) [65536 ports] Discovered open port 53/tcp on XX.XX.50.100 SYN Stealth Scan Timing: About 3.83% done; ETC: 11:11 (0:12:59 remaining) SYN Stealth Scan Timing: About 9.78% done; ETC: 11:08 (0:09:23 remaining) SYN Stealth Scan Timing: About 10.51% done; ETC: 11:12 (0:12:55 remaining) SYN Stealth Scan Timing: About 16.70% done; ETC: 11:10 (0:10:04 remaining) Increasing send delay for XX.XX.50.100 from 0 to 5 due to max_successful_tryno increase to 5 SYN Stealth Scan Timing: About 18.52% done; ETC: 11:12 (0:11:04 remaining) SYN Stealth Scan Timing: About 18.73% done; ETC: 11:14 (0:13:05 remaining) SYN Stealth Scan Timing: About 19.52% done; ETC: 11:16 (0:14:30 remaining) SYN Stealth Scan Timing: About 21.06% done; ETC: 11:18 (0:15:26 remaining) SYN Stealth Scan Timing: About 24.29% done; ETC: 11:20 (0:16:25 remaining) Increasing send delay for XX.XX.50.100 from 5 to 10 due to max_successful_tryno increase to 6 Warning: XX.XX.50.100 giving up on port because retransmission cap hit (6). SYN Stealth Scan Timing: About 43.75% done; ETC: 11:25 (0:15:19 remaining) SYN Stealth Scan Timing: About 52.10% done; ETC: 11:27 (0:13:57 remaining) SYN Stealth Scan Timing: About 58.04% done; ETC: 11:28 (0:12:29 remaining) SYN Stealth Scan Timing: About 64.08% done; ETC: 11:29 (0:10:58 remaining) SYN Stealth Scan Timing: About 69.85% done; ETC: 11:29 (0:09:26 remaining) SYN Stealth Scan Timing: About 75.86% done; ETC: 11:31 (0:07:51 remaining) SYN Stealth Scan Timing: About 81.62% done; ETC: 11:32 (0:06:11 remaining) SYN Stealth Scan Timing: About 87.08% done; ETC: 11:33 (0:04:28 remaining) SYN Stealth Scan Timing: About 92.36% done; ETC: 11:33 (0:02:42 remaining) SYN Stealth Scan Timing: About 97.54% done; ETC: 11:34 (0:00:53 remaining) Completed SYN Stealth Scan at 11:34, 2174.49s elapsed (65536 total ports) Initiating Service scan at 11:34 Scanning 1 service on Neato-Robot.abcd.ca (XX.XX.50.100) Completed Service scan at 11:34, 0.22s elapsed (1 service on 1 host) Initiating OS detection (try #1) against Neato-Robot.abcd.ca (XX.XX.50.100) Retrying OS detection (try #2) against Neato-Robot.abcd.ca (XX.XX.50.100) Retrying OS detection (try #3) against Neato-Robot.abcd.ca (XX.XX.50.100) Retrying OS detection (try #4) against Neato-Robot.abcd.ca (XX.XX.50.100) Retrying OS detection (try #5) against Neato-Robot.abcd.ca (XX.XX.50.100) Initiating Traceroute at 11:34 Completed Traceroute at 11:34, 0.03s elapsed Initiating Parallel DNS resolution of 2 hosts. at 11:34 Completed Parallel DNS resolution of 2 hosts. at 11:34, 0.00s elapsed NSE: Script scanning XX.XX.50.100. Initiating NSE at 11:34 Completed NSE at 11:35, 7.13s elapsed Initiating NSE at 11:35 Completed NSE at 11:35, 0.00s elapsed Initiating NSE at 11:35 Completed NSE at 11:35, 0.00s elapsed Nmap scan report for Neato-Robot.abcd.ca (XX.XX.50.100) Host is up (0.039s latency). Not shown: 53770 closed ports, 11765 filtered ports PORT STATE SERVICE VERSION 53/tcp open tcpwrapped | dns-nsid: | id.server: marge.abcd.ca |_ bind.version: unbound 1.24.0 No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ). TCP/IP fingerprint: OS:SCAN(V=7.80%E=4%D=11/30%OT=53%CT=21%CU=40519%PV=Y%DS=2%DC=T%G=Y%TM=692C8 OS:E59%P=i686-pc-windows-windows)SEQ(SP=FE%GCD=1%ISR=10E%TI=Z%II=I%TS=21)SE OS:Q(II=I)OPS(O1=M5B4NW7ST11%O2=M5B4NW7ST11%O3=M5B4NW7NNT11%O4=M5B4NW7ST11% OS:O5=M5B4NW7ST11%O6=M5B4ST11)WIN(W1=FECC%W2=FECC%W3=FECC%W4=FECC%W5=FECC%W OS:6=FECC)ECN(R=Y%DF=Y%T=41%W=FECC%O=M5B4NW7SLL%CC=Y%Q=)ECN(R=N)T1(R=Y%DF=Y OS:%T=41%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=N)T5(R=Y%DF=Y%T=40%W=0%S= OS:Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=N)T7(R=N)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL= OS:G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S) Network Distance: 2 hops TRACEROUTE (using port 80/tcp) HOP RTT ADDRESS 1 0.00 ms XX.XX.20.1 2 26.00 ms Neato-Robot.abcd.ca (XX.XX.50.100) NSE: Script Post-scanning. Initiating NSE at 11:35 Completed NSE at 11:35, 0.00s elapsed Initiating NSE at 11:35 Completed NSE at 11:35, 0.00s elapsed Initiating NSE at 11:35 Completed NSE at 11:35, 0.00s elapsed Read data files from: C:\Program Files (x86)\Nmap OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 2201.85 seconds Raw packets sent: 138770 (6.111MB) | Rcvd: 56142 (2.250MB) C:\Users\algra> ================================================ FILE: research/gen4/serial-D8.md ================================================ The boot sequence differs on the Neato D8 (and presumably D9 / D10). Shutdown sequence: Neato LEGO Distro Release 1.7.0-2933_10060147_cfae4f98 Neato-Robot ttymxc1 Neato-Robot login: [[1B][0;32m OK [1B][0m] Removed slice [1B][0;1;39msystem-getty.slice[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mBluetooth[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mMulti-User System[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mLogin Prompts[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mHost and Network Name Lookups[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSound Card[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSystem Time Synchronized[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSystem Time Set[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mTimers[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mDaily Cleanup of Temporary Directories[1B][0m. [[1B][0;32m OK [1B][0m] Closed [1B][0;1;39mLoad/Save RF Kill Switch Status /dev/rfkill Watch[1B][0m. Stopping [1B][0;1;39mSave/Restore Sound Card State[1B][0m... Stopping [1B][0;1;39mAuto-update Service[1B][0m... Stopping [1B][0;1;39mD-Bus System Message Bus[1B][0m... Stopping [1B][0;1;39mCheck and fix Bluetooth function periodically[1B][0m... Stopping [1B][0;1;39mFlush file cache[1B][0m... Stopping [1B][0;1;39mPython OTA Upgrade Service[1B][0m... [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mPlay 1 second silence file[1B][0m. Stopping [1B][0;1;39mR Stopping [1B][0;1;39mSerial Getty on ttymxc1[1B][0m... Stopping [1B][0;1;39mLogin Service[1B][0m... Stopping [1B][0;1;39mLoad/Save Random Seed[1B][0m... Stopping [1B][0;1;39mTEE Supplicant[1B][0m... Stopping [1B][0;1;39mUSB gadgets[1B][0m... [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mAuto-update Service[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mD-Bus System Message Bus[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mTEE Supplicant[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mFlush file cache[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mLogin Service[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mCheck and fix Bluetooth function periodically[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mUSB gadgets[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mTron Connectivity[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mSerial Getty on ttymxc1[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mSave/Restore Sound Card State[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mLoad/Save Random Seed[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mBluetooth service[1B][0m. [[1B][0;32m OK [1B][0m] Removed slice [1B][0;1;39msystem-serial\x2dgetty.slice[1B][0m. Stopping [1B][0;1;39mPython User Event Scheduler Service[1B][0m... Stopping [1B][0;1;39mPermit User Sessions[1B][0m... [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mPython User Event Scheduler Service[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mPython OTA Upgrade Service[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mPermit User Sessions[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mNetwork[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mRemote File Systems[1B][0m. Stopping [1B][0;1;39mBattery and Charger Control Service[1B][0m... Stopping [1B][0;1;39mNetwork Name Resolution[1B][0m... [00][00]NOTICE: BL31: v2.2(release):rel_imx_5.4.47_2.2.0-0-gc949a888e-dirty NOTICE: BL31: Built : 08:42:51, Nov 3 2021 Boot Sequence: Welcome to [1B][1mNXP i.MX Release Distro 5.4-zeus (zeus)[1B][0m! [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mDispatch Password …ts to Console Directory Watch[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mPaths[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSlices[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSwap[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mProcess Core Dump Socket[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39minitctl Compatibility Named Pipe[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mJournal Socket (/dev/log)[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mJournal Socket[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mNetwork Service Netlink Socket[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mudev Control Socket[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mudev Kernel Socket[1B][0m. Mounting [1B][0;1;39mHuge Pages File System[1B][0m... Mounting [1B][0;1;39mPOSIX Message Queue File System[1B][0m... Mounting [1B][0;1;39mKernel Debug File System[1B][0m... Mounting [1B][0;1;39mTemporary Directory (/tmp)[1B][0m... Mounting [1B][0;1;39m/var/volatile[1B][0m... Starting [1B][0;1;39mJournal Service[1B][0m... Mounting [1B][0;1;39mKernel Configuration File System[1B][0m... Starting [1B][0;1;39mApply Kernel Variables[1B][0m... Starting [1B][0;1;39mCreate Static Device Nodes in /dev[1B][0m... Starting [1B][0;1;39mudev Coldplug all Devices[1B][0m... Starting [1B][0;1;39mUnlock encrypted root filesystem[1B][0m... [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39mHuge Pages File System[1B][0m. [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39mPOSIX Message Queue File System[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mJournal Service[1B][0m. [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39mKernel Debug File System[1B][0m. [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39mTemporary Directory (/tmp)[1B][0m. [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39m/var/volatile[1B][0m. [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39mKernel Configuration File System[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mApply Kernel Variables[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mCreate Static Device Nodes in /dev[1B][0m. Starting [1B][0;1;39mudev Kernel Device Manager[1B][0m... Starting [1B][0;1;39mLoad/Save Random Seed[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mudev Kernel Device Manager[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mUnlock encrypted root filesystem[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mInitrd Root Device[1B][0m. [[1B][0;32m OK [1B][0m] Found device [1B][0;1;39m/dev/mapper/encrypted[1B][0m. Mounting [1B][0;1;39mMount encrypted root filesystem[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mudev Coldplug all Devices[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mLoad/Save Random Seed[1B][0m. [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39mMount encrypted root filesystem[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mInitrd Root File System[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mLocal File Systems[1B][0m. Starting [1B][0;1;39mReload Configuration from the Real Root[1B][0m... Starting [1B][0;1;39mCreate Volatile Files and Directories[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mCreate Volatile Files and Directories[1B][0m. Starting [1B][0;1;39mRun pending postinsts[1B][0m... Starting [1B][0;1;39mNetwork Time Synchronization[1B][0m... Starting [1B][0;1;39mUpdate UTMP about System Boot/Shutdown[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mUpdate UTMP about System Boot/Shutdown[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mReload Configuration from the Real Root[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mRun pending postinsts[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mInitrd File Systems[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mNetwork Time Synchronization[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSystem Initialization[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mDaily Cleanup of Temporary Directories[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSystem Time Set[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSystem Time Synchronized[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mTimers[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mD-Bus System Message Bus Socket[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSockets[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mBasic System[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mInitrd Default Target[1B][0m. Starting [1B][0;1;39mCleaning Up and Shutting Down Daemons[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mCleaning Up and Shutting Down Daemons[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mInitrd Default Target[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mInitrd Root Device[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSystem Time Synchronized[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSystem Time Set[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mTimers[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mDaily Cleanup of Temporary Directories[1B][0m. [[1B][0;32m OK [1B][0m] Closed [1B][0;1;39mProcess Core Dump Socket[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mBasic System[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mPaths[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mDispatch Password …ts to Console Directory Watch[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSlices[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSockets[1B][0m. [[1B][0;32m OK [1B][0m] Closed [1B][0;1;39mD-Bus System Message Bus Socket[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSystem Initialization[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSwap[1B][0m. [[1B][0;32m OK [1B][0m] Closed [1B][0;1;39minitctl Compatibility Named Pipe[1B][0m. [[1B][0;32m OK [1B][0m] Closed [1B][0;1;39mNetwork Service Netlink Socket[1B][0m. Stopping [1B][0;1;39mLoad/Save Random Seed[1B][0m... [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mApply Kernel Variables[1B][0m. Stopping [1B][0;1;39mNetwork Time Synchronization[1B][0m... [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mudev Coldplug all Devices[1B][0m. Stopping [1B][0;1;39mudev Kernel Device Manager[1B][0m... Stopping [1B][0;1;39mUpdate UTMP about System Boot/Shutdown[1B][0m... [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mNetwork Time Synchronization[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mLoad/Save Random Seed[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mudev Kernel Device Manager[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mUpdate UTMP about System Boot/Shutdown[1B][0m. [[1B][0;32m OK [1B][0m] Closed [1B][0;1;39mudev Control Socket[1B][0m. [[1B][0;32m OK [1B][0m] Closed [1B][0;1;39mudev Kernel Socket[1B][0m. Starting [1B][0;1;39mCleanup udevd DB[1B][0m... [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mCreate Static Device Nodes in /dev[1B][0m. [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mCreate Volatile Files and Directories[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mLocal File Systems[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mCleanup udevd DB[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSwitch Root[1B][0m. Starting [1B][0;1;39mSwitch Root[1B][0m... Welcome to [1B][1mNXP i.MX Release Distro 5.4-zeus (zeus)[1B][0m! [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mSwitch Root[1B][0m. [[1B][0;32m OK [1B][0m] Created slice [1B][0;1;39msystem-getty.slice[1B][0m. [[1B][0;32m OK [1B][0m] Created slice [1B][0;1;39msystem-serial\x2dgetty.slice[1B][0m. [[1B][0;32m OK [1B][0m] Created slice [1B][0;1;39msystem-systemd\x2dfsck.slice[1B][0m. [[1B][0;32m OK [1B][0m] Created slice [1B][0;1;39msystem-wpa_supplicant.slice[1B][0m. [[1B][0;32m OK [1B][0m] Created slice [1B][0;1;39mUser and Session Slice[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mDispatch Password …ts to Console Directory Watch[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mForward Password R…uests to Wall Directory Watch[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mSwitch Root[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mInitrd File Systems[1B][0m. [[1B][0;32m OK [1B][0m] Stopped target [1B][0;1;39mInitrd Root File System[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mPaths[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mRemote File Systems[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSlices[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSwap[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mProcess Core Dump Socket[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39minitctl Compatibility Named Pipe[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mNetwork Service Netlink Socket[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mudev Control Socket[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mudev Kernel Socket[1B][0m. Starting [1B][0;1;39mCreate list of st…odes for the current kernel[1B][0m... [[1B][0;32m OK [1B][0m] Stopped [1B][0;1;39mJournal Service[1B][0m. Starting [1B][0;1;39mJournal Service[1B][0m... Starting [1B][0;1;39mLoad Kernel Modules[1B][0m... Starting [1B][0;1;39mRemount Root and Kernel File Systems[1B][0m... Starting [1B][0;1;39mudev Coldplug all Devices[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mCreate list of sta… nodes for the current kernel[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mJournal Service[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mRemount Root and Kernel File Systems[1B][0m. Starting [1B][0;1;39mLoad/Save Random Seed[1B][0m... Starting [1B][0;1;39mCreate Static Device Nodes in /dev[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mLoad/Save Random Seed[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mCreate Static Device Nodes in /dev[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mLocal File Systems (Pre)[1B][0m. Starting [1B][0;1;39mFile System Check on /dev/mmcblk2p5[1B][0m... Starting [1B][0;1;39mudev Kernel Device Manager[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mudev Kernel Device Manager[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mFile System Check on /dev/mmcblk2p5[1B][0m. Mounting [1B][0;1;39m/user[1B][0m... [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39m/user[1B][0m. [ 12.855894] 000: hifDeviceInserted: Dumping clocks (50000000,400000000) Mounting [1B][0;1;39m/var/log[1B][0m... [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39m/var/log[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mLocal File Systems[1B][0m. Starting [1B][0;1;39mFlush Journal to Persistent Storage[1B][0m... [ 13.102802] 000: ol_download_firmware: chip_id:0x5020001 board_id:0x0 [ 13.124915] 000: __ol_transfer_bin_file: Failed to get :-22 [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mudev Coldplug all Devices[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mFlush Journal to Persistent Storage[1B][0m. [ 13.998755] 000: ENTER sme_set_btc_coex_dutycycle = 30 Starting [1B][0;1;39mCreate Volatile Files and Directories[1B][0m...[ 13.998759] 000: ENTER sme_set_btc_coex_dutycycle =30 [ 14.106682] 000: debugfs: Directory '30050000.sai' with parent 'tfa9896-audio' already present! [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mLoad Kernel Modules[1B][0m. Starting [1B][0;1;39mApply Kernel Variables[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mApply Kernel Variables[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mCreate Volatile Files and Directories[1B][0m. Starting [1B][0;1;39mNetwork Service[1B][0m... Starting [1B][0;1;39mNetwork Time Synchronization[1B][0m... Starting [1B][0;1;39mUpdate UTMP about System Boot/Shutdown[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mUpdate UTMP about System Boot/Shutdown[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mNetwork Service[1B][0m. Starting [1B][0;1;39mNetwork Name Resolution[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mNetwork Time Synchronization[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSystem Initialization[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mDaily Cleanup of Temporary Directories[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSystem Time Set[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSystem Time Synchronized[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mTimers[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mD-Bus System Message Bus Socket[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSockets[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mBasic System[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mAuto-update Service[1B][0m. Starting [1B][0;1;39mBluetooth service[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mD-Bus System Message Bus[1B][0m. Starting [1B][0;1;39mFlush file cache[1B][0m... Starting [1B][0;1;39mPlay 1 second silence file[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mUser Space Regulatory Firmware Loading[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mUser Space SDMA Firmware Loading[1B][0m. Starting [1B][0;1;39mLogin Service[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mTEE Supplicant[1B][0m. Starting [1B][0;1;39mSW upgrade service[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mNetwork Name Resolution[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mFlush file cache[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mPlay 1 second silence file[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mHost and Network Name Lookups[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mSW upgrade service[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mMCU SPI Communication Service[1B][0m. Starting [1B][0;1;39mDatabase Files Setup Service[1B][0m... Starting [1B][0;1;39mSetup dev permission[1B][0m... Starting [1B][0;1;39mSet host name[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mLogin Service[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mSet host name[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mBluetooth service[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mCheck and fix Bluetooth function periodically[1B][0m. Mounting [1B][0;1;39mTemporary Directory (/tmp)[1B][0m... [[1B][0;32m OK [1B][0m] Mounted [1B][0;1;39mTemporary Directory (/tmp)[1B][0m. Starting [1B][0;1;39mHostname Service[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mHostname Service[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mSetup dev permission[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mLDS Driver Service[1B][0m. [[1B][0;32m OK [1B][0m] Listening on [1B][0;1;39mLoad/Save RF …itch Status /dev/rfkill Watch[1B][0m. Starting [1B][0;1;39mLoad/Save RF Kill Switch Status[1B][0m... Starting [1B][0;1;39mSave/Restore Sound Card State[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mLoad/Save RF Kill Switch Status[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mBluetooth[1B][0m. [ 26.805560] 000: tfa98xx_set_volume(): tfa98xx_set_volume [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mSave/Restore Sound Card State[1B][0m. [[1B][0;32m OK [1B][0m] Found device [1B][0;1;39m/sys/subsystem/net/devices/wlan0[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mSound Card[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mWPA supplicant dae… (interface-specific version)[1B][0m. [[1B][0;32m OK [1B][0m] Reached target [1B][0;1;39mNetwork[1B][0m. Starting [1B][0;1;39mPermit User Sessions[1B][0m... [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mPermit User Sessions[1B][0m. [[1B][0;32m OK [1B][0m] Started [1B][0;1;39mSerial Getty on ttymxc1[1B][0m. Starting [1B][0;1;39mUSB gadgets[1B][0m... [ 28.030253] 000: tfa98xx_set_volume(): tfa98xx_set_volume Neato LEGO Distro Release 1.7.0-2933_10060147_cfae4f98 Neato-Robot ttymxc1 Neato-Robot login: ================================================ FILE: research/hidden-commands.md ================================================ ### Some of the hidden commands for gen3 robots ``` Log Text - write this text to log Flush - flush entires SetUIError brief - Sets a UI State Machine Error/Alert clearall - Clears all UI Alerts and Errors list - Lists all UI Alerts and Errors clearalert - Clears the specified UI Alert or Error setalert - Sets the specified UI Alert or Error CopyDumps - Copy All core dumps to emmc and pack them. GetActiveServices - Displays all running services GetLoggingType - Displays the type of log (QA, NavPen, or Production) GetRobotPassword - Returns the robot's saved random password GetState - Gets the state of the UI Finite State Machine (NOTE: No idea how it works, what it does) RunUSMFGTest - Run Ultrasonic MFG Test. GetI2CBlowerInfo - Get I2C Blower Registers (TestMode Only) (NOTE: No idea how to actually see the data) GetDatabase - show database tables. Database options are: All, Factory, Robot, Runtime, Statistics, System or CleanStats. RestoreDefaults - Restore user settings to default. GetRobotPos - Returns Robot position. Raw - Return Odometry. Smooth - Return smoothed/Localized position. USBLogCopy - Copies all logs onto a USB drive. (NOTE: Needs some security key, I don't know how it works) CalibrateSensor - Automatically calibrates sensors and stores the values into SCB. (NOTE: Needs some security key, I don't know how it works) CalibrateAccelerometer - Calibrates the Accelerometer's X/Y Positions. (NOTE: Just says "Not supported yet...") GetStats - show system statistics. (NOTE: I don't know how it works) SetApp - Sets the alert/error to be sent to app . Alert UpdateSW - Returns SwUpdate stuff. GetStatus - Returns Status of SW Update Verify - Returns Status of SW Update Terminate - Flag to force a shutdown of the SoftwareManager TestPWM GetWifiStatus mfgtest - Do MFG Test to determine if the WIFI chip is there registry - show WIFI registries ? - show wifi log file sloginfo - show sloginfo info Pattern - display sysloginfo info which matches specified pattern Pattern2 - display sysloginfo info which matches both specified patterns.Pattern must be specified. For example, getwifis sloginfo pattern ROSIE pattern2 REPLY Exclude - display sysloginfo info which doesn't contain specified pattern.Pattern must be specifed.For example, getwifis sloginfo pattern ROSIE exclude REPLY wpacfg - show /emmc/wpa_supplicant.cfg file ? - Key to unlock sloginfo retrieval command copy sloginfo content to /emmc/black_box/sloginfo.txt and clear sloginfo. For example: getwifis sloginfo clear 1 Clean House (Optional) Equivalent to pressing 'Start' button once. Starts a house cleaning. (House cleaning mode is the default cleaning mode.) (Choose only 1 of House,Spot,Stop) Spot (Optional) Starts a spot clean. (Not available with AutoCycle) (Choose only 1 of House,Spot,Stop) Persistent (Optional) Equivalent to starting a persistent cleaning from the Smart App. Width (Optional) Spot Width in CM (100-400)(-1=use default). Height (Optional) Spot Height in CM (100-400)(-1=use default). AutoCycle Auto cycle Mode. (Shutdown will clear. 'Clean Stop' will also clear.) (Not available with Spot clean.) MinCharge (Optional) Minimum charge level to trigger a recharge(-1 = use default(50%)). Stop Stop Cleaning. (Choose only 1 of House,Spot,Stop) CleaningEnable Enables the brush and vacuum during cleaning. (Choose only 1 of House,Spot,Stop,NavTest,CleaningEnable,CleaningDisable,IEC1mTest) CleaningDisable Disables the brush and vacuum during cleaning. (Choose only 1 of House,Spot,Stop,NavTest,CleaningEnable,CleaningDisable,IEC1mTest) IEC1mTest Runs the IEC cleaning test. (Choose only 1 of House,Spot,Stop,NavTest,CleaningEnable,CleaningDisable,IEC1mTest) MaxModeEnable Enable max cleaning mode. MaxModeDisable Disable max cleaning mode. ClearFiles BB - (Optional) Clears Managed Logs in BlackBox Directory. All - (Optional) Additionally clears unmanaged files (Crash, ...) in the specified directories. Life - ?? ``` ### Other Hidden commands ``` NewBattery (NOTE: Exact function unknown, but some users report this can fix an issue where a new battery isn't being charged) Tells the robot a new battery has been installed. ``` ### Hidden gems looking at the decompiled code: ``` YOU OVERFLOWED A 64-bit NUMBER! WHAT WERE YOU THINKING??? Nice try, but I'm not falling for that one again! :P Invalid SCB blower value. How did we get here?! ``` ================================================ FILE: research/serial.md ================================================ ## Serial interface There seams to be two different serial interfaces on the robot. One you can connect via the usb port where the dustbin resides, and blocks. The other one with jumper wires or soldering. Here you can connect via a TTY adapter. ### On different robots **NOTE: Verified robots are D3, D5, D7. It should be the same for the other D4 and D6** #### D3/D4 I suspect that the D4 looks the same as the D3, because from some research the only difference was their battery and maybe filter. ![pcb-top-annotated](/pics/d3/pcb-top-annotated.png) ![pcb-top](/pics/d3/pcb-top.jpg) ![pcb-bottom](/pics/d3/pcb-bottom.jpg) #### D5/D6 Inside looks practically identical to D3. It has the extra sidebrush motor that is missing in the D3. [source](https://www.youtube.com/watch?v=OqgD7zuyLuA) ![d5](/pics/other-robots/d5.png) #### D7 Looks once again practically identical to the D3. It has the extra sidebrush motor and the status leds are from a separate board. [source](https://www.youtube.com/watch?v=i-CzfMNqBFw) ![d7](/pics/other-robots/d7.png) ### Pinout for D3 But I am very very certain it is the same for D3/D4/D5/D6/D7. The pinout looks like this: ![pinout on d3](/pics/d3/pinout.png) ![pinout connected](/pics/d3/pinout-connected.png) (The pin labled 3.3V? is a 3.3V pin) my setup ![my setup](/pics/d3/pinout-tty-setup.jpg) #### D70/D75/D80/D85 These use a compleatly different pcb and they have the serial debug strips on the left side of the chassi. d80 ![d80](/pics/other-robots/d80.png) d85 ![d85](/pics/other-robots/d85.png) [source d80](https://www.youtube.com/watch?v=oIHImYWqOh4) [source d85](https://www.youtube.com/watch?v=DixC1pDCsh8) I suspect the D70 and D75 look the same. The pinout for these should be like this: ![pinout for d70/d75/d80/85](/pics/other-robots/pinout.d70-d75-d80-d85.png) [source](https://github.com/jeroenterheerdt/neato-serial/issues/3#issuecomment-510543522) ### Connecting Connecting to a USB TTY Adapter you only need to connect ground and RX to the robot TX to the output, to be able to send commands, you connect the TX to the robot RX. Connecting to the serial interface with screen: `screen /dev/ttyUSB0 115200` Shows some logs of the robot starting up, including a boot menu if the robot was fully shut down and you press enter twice. The serial interface on the `3.2.0` version: (the `4.5.3` version I will add later) ``` ARCHES Board (04.0x90c973a5) Press enter twice within the next 2 seconds for boot menu Enter Key Detected! Enter Key Detected! **Commands: Press 'M' to load IFS from main image flash partition Press 'F' to load IFS from factory image flash partition Press 'X' for serial download, using XModem-1k Press '1' for XModem-1k download at 1Mb/s Press '3' for XModem-1k download at 3Mb/s Press 'S' to scan existing memory without download -- Compressed Image Mode ('c' to toggle)-- initialize_sdmmc finished. sdmmc.sdmmc_pbase=481D8000 Booting Main Image ``` If it just was sleeping or not fullt shut down. ``` ARCHES Board (04.0x90c973a5) Not factory boot... initialize_sdmmc finished. sdmmc.sdmmc_pbase=481D8000 Booting Main Image ``` Then it starts up, you can now enter commands! The commands listed by the `Help` command are as follows: ``` Help - Without any argument, this prints a list of all possible cmds. With a command name, it prints the help for that particular command Clean - Starts a cleaning by simulating press of start button. ClearFiles - Erases Black Box, and other Logs DiagTest - Executes different test modes. Once set, press Start button to engage. (Test modes are mutually exclusive.) GenerateRobotLinkCode - Generate and send robot linking code to server. GetConfiguredWifiNetworks - Get the list of configured wifi networks. GetRobotLinkCode - Get the robot linking code CancelRobotLink - Cancel link request. SetNTPTime - Set system time using the NTP servers (WIFI must be up for this to work) GetAccel - Get the Accelerometer readings. GetAnalogSensors - Get the A2D readings for the analog sensors. GetButtons - Get the state of the UI Buttons. GetCalInfo - Prints out the cal info from the System Control Block. GetCharger - Get the diagnostic data for the charging system. GetDigitalSensors - Get the state of the digital sensors. GetErr - Get Error Message. GetLDSScan - Get scan packet from LDS. GetMotors - Get the diagnostic data for the motors. GetSensor - Gets the sensors status ON/OFF (Wall Follower and Ultra Sound Only) GetTime - Get Current Scheduler Time. GetVersion - Get the version information for the system software and hardware. GetWarranty - Get the warranty data. GetWifiInfo - Get a list of available wifi networks. GetWifiStatus - Get the current status of the wifi. GetUserSettings - Get the user settings. GetUsage - Get usage settings PlaySound - Play the specified sound in the robot. SetBatteryTest - Sets California Energy Commission 10-CFR-430 Battery Charging System Test mode. SetButton - Simulates a button press. SetFuelGauge - Set Fuel Gauge Level. SetIEC - Sets the IEC Cleaning Test parameters SetLCD - Sets the LCD to the specified display. (TestMode Only) SetLED - Sets the specified LED to on,off,blink, or dim. (TestMode Only) SetLDSRotation - Sets LDS rotation on or off. Can only be run in TestMode. SetMotor - Sets the specified motor to run in a direction at a requested speed. (TestMode Only) SetSystemMode - Set the operation mode of the robot. (TestMode Only) SetTime - Sets the current day, hour, and minute for the scheduler clock. SetUserSettings - Sets user settings SetUsage - Sets usage settings SetWifi - SetWifi variables TestMode - Sets TestMode on or off. Some commands can only be run in TestMode. Upload - Uploads new program to the robot. ``` Now the commandline is not case-sensetive and it also checks if the string you written is part of a command or parameter for a command. If it is an only match it will "autocomplete" the command for you, if multiple matches then it will list what it could be. Thanks to this functionality I have discovered some commands that is not documented in the `Help` command: ``` GetActiveServices GetLoggingType GetState ``` Commands that was in the old programming manual but I don't know how to use: ``` GetSysLog ``` Since then I have found more [hidden commands](./hidden-commands.md)! #### Differences I found in 3.2.0 and 4.5.3 3.2.0 has more options for `SetSystemMode`: ``` SetSystemMode - Set the operation mode of the robot. (TestMode Only) Shutdown - Shut down the robot. (mutually exclusive of other options) Hibernate - Start hibernate operation.(mutually exclusive of other options) Standby - Start standby operation. (mutually exclusive of other options) PowerCycle - Power cycles the entire system. (mutually exclusive of other options) ``` #### Logs when doing a factory reset: ``` ARCHES Board (04.0x90c973a5) Press enter twice within the next 2 seconds for boot menu Dustbin open , Right Side Bumper and Right Front Bumper closed.... Booting Factory Image...Loading FACTORY SECURE/ENCRYPTED image ... initialize_sdmmc finished. sdmmc.sdmmc_pbase=481D8000 Factory Mode: Restoring Image. 32768+0 records in 32768+0 records out 32768+0 records in 32768+0 records out Initializing UI cp: Can't open source file. (/emmc/uiFactory) Resetting wifi /etc/factory_reset.sh[69]: [: missing ] Reset Security Reset complete. Rebooting Shutting down daemons... Shutting down filesystems... ``` #### Shutdown the robot fully ``` TestMode on SetSystemMode Shutdown ``` (This could be written as `t on` and `setsy sh`) ================================================ FILE: research/setup-network.md ================================================ ## Setup network If factory reset (by loading the factory IFS or holding the front right and right bumper switches while turning it off (This method worked once for them, next time it didn't)) or by holding the left bumper switch while turning the robot off, the next time it turns on it will go into pairing mode. It will host a network called `Neato-{serial-number}` (on my factory firmware it is `neato-{serial-number}`). Connecting to this network, the ip range will be `192.168.219.XXX` with the robot at `192.168.219.1`. (on my factory firmware it is at `192.168.0.1`). An nmap scan reveals that port 4443 and port 8081 is open of the robot. (on my factory firmware it is only port 4443 that is open). I am not sure why, but running nmap seams to crash the webserver and to connect to it again I need to restart the robot. I looked first at how the app connects to the robot, and had some issues when trying to use pcapdroid to decrypt the payload, I got SSL protocol not supported, I tried to connect via curl, same error. I am not exactly sure why; but after some debuggning I think it requires a TLS 1.0 handshake and modern curl/mitmproxy/python is not happy for that. Sending curl requests with `--ciphers ALL:@SECLEVEL=0` solves the issues with TLS. The endpoints important to the setup is: ``` GET /info GET /wifi_networks PUT /robot/initialize GET /robot/wifi_networks/new/progress PUT /robot/access_point/shutdown ``` The app sends `authorization` headers but I think that it is pointless, I have just been sending random data on this header during all my testing. I will be testing to remove headers that might be unnessecery. Lets look at the request body and response body. GET /info ```json { "serial": "serial-number", "model": "BotVacD3Connected", "firmware": "4.5.3-189", // 3.2.0-305 for my factory firmware "name": "name-of-robot", // empty string if factory reset "easyWifiConnectVersion": "advanced-3" // basic-3 for my factory firmware } ``` GET /wifi_networks ```json [ { "ssid": "wifi ssid", "strength": 3 // seams to be from 1-3, 3 being strongest } [...] ] ``` PUT /robot/initialize request: ```json { "name": "name to give robot", "password": "password of wifi to connect to", "server_urls": { "beehive": "beehive", // .neatocloud.com will be autoapended "ntp": "pool.ntp.org", "nucleo": "nucleo" // .neatocloud.com will be autoapended }, "ssid": "ssid of wifi to connect to", "timezone": "Europe/Stockholm", "user_id": "userid", // hash of some kind? "utc_offset": "UTC+1:00UTC+2:00" } ``` response is just an empty object. GET /robot/wifi_networks/new/progress ```json { "type": "primary", "step": 1/2/3/4, // 1 - connecting to wifi, 2 - connecting to internet, 3 - connecting to neato cloud, 4 - connecting robot } ``` There seams to be 2 more endpoints: ``` PUT /robot/wifi PUT /robot/wifi_networks/primary ``` however I am not sure how these work, might be used by older firmware versions, havn't tried to do the setup via app then mitm it with the factory firmware yet. ### mitmproxy to look at data You will need mitmproxy 4.0.4 (other versions may also work). I would recommend downloading their binary with the correct python version bundeled from: https://www.mitmproxy.org/downloads/#4.0.4/ Run mitmproxy with: `mitmproxy --listen-host 0.0.0.0 --listen-port 8080 --ssl-insecure` Open port 8080, I just open all temporarly: `iptables -I INPUT -j ACCEPT` Connect the pc running mitmproxy to the robot AP first, then connect the phone to the robot AP and set the proxy settings as follows: ``` Server: 192.168.219.9 Port: 8080 ``` Of couse, check your exact ip in case you are on another firmware version or it is not working. ## Once connected to wifi Once connected to wifi the robot will do the following: 1. Check internet connactivity by pinging 8.8.8.8 2. Get NTP servers from pool and update the time 3. Contact the "beehive" url (with `.neatocloud.com` appended) Rewriting the DNS in my router to an local openssl server to see if the robot is checking certificates gave: `SSL3 alert read:fatal:unknown CA`, meaning the cloud replacment method is not possible without removing secure boot and changing the CA list. ================================================ FILE: status.md ================================================ # Project status ### Roadmap & current state - Version 1. Current state of the project gives you basic local control - Neato cleaning logic - Will always maintain and get support - Occasional updates based on feature requests or bugs - Version 2. Currently a work in progress, all the features in a non-ideal way (ETA: March 2026, if all stars align) - ROS2 & Neato hybrid - Neato cleaning logic - ROS2 for nogo-lines and zone cleaning, (return to dock?) - NOTE: Big limitation - if it goes outside of its area, all we can do is "move it back" - Version 3. End goal of project, any lidar vacuum should be supported as long as a "driver" for each vacuum is created. (ETA: future) - Fully custom cleaning logic and navigation via ROS2 - There might be limitations on how good this can be, relying on a serial interface, how fast commands can happen, etc - Would work with a fully custom-made robot ### Version 1 #### Version 1.2 - Add support for Connected, D70-D85, XV-series - Event based commands, return to dock and manual driving for D3-D7 - Full non-home assistant version - Rework docs, add faq - Add Home Assistant entity and automations/scripts #### Version 1.3 - planned - Custom state managment based on ui-state, robot-state and sensors - Gen2 robots error strings - If possible: push notifications for non-ha install - Improve notifications #### Not sure if 1.3 or 1.4 - Set schedule of gen1 and gen2 robots via esphome #### Version 1.4 - planned - Translations - Use esphome package stuff to fetch from github directly ### Version 2 #### Version 2.0 - in dev The first version of version 2, will be the latest version of version 1 but will also be sending all the data to a ROS2 server to do SLAM that then can give commands to stop and move the robot ================================================ FILE: supported-esp32.md ================================================ ## Supported ESP-32 Boards It is hard to provide link of what to buy since everyone is located at different places so instead I have opted to give some guidelines when buying ESP32 devices. - Stick to the ESPHome recommendations `ESP32`, `ESP32S3` and `ESP32C3` devices - Pay at least 6-8 €/£/$ for your esp device - Avoid SuperMini devices as they often have the antenna too close to the rest of the electronics - Perferably it should have a shield - Avoid ceramic antennas Sheilded with pcb antenna (black part sticking out) | Unsheilded with ceramic antenna (red rectangle) :-------------------------:|:-------------------------: ![sheilded](pics/setup/sheilded.png) | ![unsheilded](pics/setup/unsheilded.png) This is not in any terms any perfect guidelines, but mainly what we have learned from all the users in the discord and here on github! ## More technical/examples Antenna too close: https://www.reddit.com/r/esp32/comments/1dsh3b5/warning_some_c3_super_mini_boards_have_a_design/ There seems to be two major issues with bad ESP designs and the neato robots 1. Smaller/Cheaper ESP boards lack proper brownout protection - A brownout means that the voltage drops and if the ESP does not have proper protection against this, the device will restart 2. Antenna is placed too close to the rest of the electronics, causing interferance or refusal to work ================================================ FILE: webserver/.prettierrc.json ================================================ { "html.format.wrapAttributes": "auto", "html.format.wrapLineLength": 0, "printWidth": 80 } ================================================ FILE: webserver/LICENSE ================================================ MIT License Copyright (c) 2021 wilberforce Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: webserver/README.md ================================================ # webserver Code taken from https://github.com/esphome/esphome-webserver and neato style built upon the v3 style. Currently this folder includes the minimum needed to run `pnpm build` to build and `PROXY_TARGET=http:// pnpm start` in the neato package folder. Will need to be updated with the upstream, planned after 1.2 ================================================ FILE: webserver/package.json ================================================ { "name": "esphome-webserver", "version": "3.0.0", "license": "MIT", "workspaces": [ "packages/*" ], "scripts": { "build": "npm run build:packages", "build:packages": "npm run build --workspaces --if-present", "deploy": "npm run deploy --workspaces --if-present" } } ================================================ FILE: webserver/packages/neato/index.html ================================================ ================================================ FILE: webserver/packages/neato/package.json ================================================ { "name": "@esphome-webserver/neato", "version": "3.0.0", "scripts": { "start": "npm run dev", "dev": "vite", "xbuild": "vite build --emptyOutDir", "build": "vite build --emptyOutDir", "serve": "vite preview" }, "dependencies": { "chart.js": "^4.4.1", "http-proxy-middleware": "^2.0.1", "iconify-icon": "^1.0.8", "lit": "^2.0.2" }, "devDependencies": { "rollup-plugin-copy": "^3.4.0", "rollup-plugin-gzip": "^3.0.0", "rollup-plugin-minify-html-template-literals": "^1.2.0", "@rollup/plugin-node-resolve": "^13.0.6", "@rollup/plugin-replace": "^3.0.0", "@types/node": "^15.12.1", "rollup-plugin-strip-banner": "^2.0.0", "typescript": "^4.1.3", "vite": "^2.3.6", "vite-plugin-html": "^2.1.1", "vite-plugin-package-version": "^1.0.2", "vite-plugin-singlefile": "^0.5.1" } } ================================================ FILE: webserver/packages/neato/selector.html ================================================

Which webserver would you like to see?

================================================ FILE: webserver/packages/neato/src/api.ts ================================================ import { entityConfig } from "./types"; import { getBasePath } from "./utils"; window.apiBasePath = getBasePath(); export function restAction(entity: entityConfig, action: string) { fetch(`${window.apiBasePath}/${entity.domain}/${entity.id}/${action}`, { method: "POST", headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, }) } export function pressButton(entity: entityConfig) { restAction(entity, "press"); } export function setText(entity: entityConfig, val: string) { restAction(entity, `set?value=${encodeURIComponent(val)}`) } ================================================ FILE: webserver/packages/neato/src/css/app.ts ================================================ import { css } from "lit"; export default css` .main-grid { display: grid; grid-template-columns: 1fr 1fr; width: 100%; padding-bottom: 2rem; } .main-buttons { display: grid; gap: 1rem; justify-content: center; grid-template-columns: auto auto auto auto; align-content: center; align-items: center; justify-content: center; } @media (max-width: 1024px) { .main-grid { grid-template-columns: 1fr; } .main-buttons { gap: 0.5rem; grid-template-columns: auto auto auto; } } .flex-grid-half { display: grid; grid-template-columns: 700px 2fr; } .flex-grid-half.expanded_entity, .flex-grid-half.expanded_logs { grid-template-columns: 1fr; } .flex-grid-half .col { margin: 8px; } .flex-grid-half .col:nth-child(2) { overflow: hidden; } .flex-grid-half.expanded_logs .col:nth-child(1) { display: none; } .flex-grid-half.expanded_entity .col:nth-child(2) { display: none; } @media (max-width: 1024px) { .flex-grid, .flex-grid-half { display: block; } .flex-grid-half .col { width: 100% !important; margin: 0 0 10px 0 !important; display: block !important; } } * { box-sizing: border-box; } .flex-grid { margin: 0 0 20px 0; } h1 { text-align: center; width: 100%; line-height: 1.1em; margin-block: 0.25em; } header div { text-align: center; width: 100%; } header #logo, header iconify-icon { float: right; font-size: 2.5rem; color: rgba(127, 127, 127, 0.5); } header #logo { float: left; color: rgba(127, 127, 127, 0.5); } .connected { color: rgba(0, 157, 16, 0.75); } esp-logo { float: left; line-height: 1em; font-size: initial; } form { display: flex; justify-content: space-between; background-color: rgba(127, 127, 127, 0.05); border-radius: 12px; border-width: 1px; border-style: solid; border-color: rgba(127, 127, 127, 0.12); } form .btn { margin-right: 0px; } .helper { width: 75%; padding: 1rem; margin: .3rem 0; background-color: rgba(127, 127, 127, 0.3); border-radius: 0.5rem; } `; ================================================ FILE: webserver/packages/neato/src/css/button.ts ================================================ import { css } from "lit"; export default css` button, .btn { cursor: pointer; border-radius: 4px; color: rgb(3, 169, 244); border: none; background-color: unset; padding: 8px; font-weight: 500; font-size: 12.25px; letter-spacing: 1.09375px; text-transform: uppercase; margin-right: -8px; } button:active, .btn:active { background-image: rgba(127, 127, 127, 0.2); transition-duration: 1s; } button:hover, .btn:hover { background-color: rgba(127, 127, 127, 0.2); transition-duration: 1s; } .abuttonIsState { background-color: #28a745; color: white; border: none; padding: 10px 20px; font-size: 16px; border-radius: 4px; transition: background-color 0.3s ease; } `; ================================================ FILE: webserver/packages/neato/src/css/esp-entity-table.ts ================================================ import { css } from "lit"; export default css` :host { position: relative; } select { background-color: inherit; color: inherit; width: 100%; border-radius: 4px; } option { color: currentColor; background-color: var(--primary-color, currentColor); } input[type="range"], input[type="text"] { width: calc(100% - 3rem); height: 0.75rem; } .range { text-align: center; } .entity-row { display: flex; align-items: center; flex-direction: row; transition: all 0.3s ease-out 0s; min-height: 40px; position: relative; } .entity-row.expanded { min-height: 240px; } .entity-row:nth-child(2n) { background-color: rgba(90, 90, 90, 0.1); } .entity-row iconify-icon { vertical-align: middle; } .entity-row > :nth-child(1) { flex: 0 0 40px; color: #44739e; line-height: 40px; text-align: center; } .entity-row > :nth-child(2) { flex: 1 1 40%; margin-left: 16px; margin-right: 8px; text-wrap: nowrap; overflow: hidden; text-overflow: ellipsis; } .entity-row > :nth-child(3) { flex: 1 1 50%; margin-right: 8px; margin-left: 20px; text-align: right; display: flex; justify-content: space-between; white-space: normal; overflow-wrap: anywhere; word-break: break-word; } .entity-row > :nth-child(3) > :only-child { margin-left: auto; } .binary_sensor_off { color: rgba(127, 127, 127, 0.7); } .singlebutton-row button { margin: auto; display: flex; } .climate-wrap{ width: 100%; margin: 10px 0 10px 0; } .climate-row { width: 100%; display: inline-flex; flex-wrap: wrap; text-align: left; } .climate-row > select{ width: 50%; } .climate-row > label{ align-content: center; width: 150px; } input[type="color"]::-webkit-color-swatch-wrapper { padding: 0 !important; } `; ================================================ FILE: webserver/packages/neato/src/css/input.ts ================================================ import { css } from "lit"; export default css` input[type="text"] { width: 100% !important; height: 1rem !important; } `; ================================================ FILE: webserver/packages/neato/src/css/reset.ts ================================================ import { css } from "lit"; export default css` :host, button, select, input { font-family: ui-monospace, system-ui, "Helvetica", "Roboto", "Oxygen", "Ubuntu", sans-serif; --primary-color: #03a9f4; transition: all 350ms !important; } `; ================================================ FILE: webserver/packages/neato/src/css/tab.ts ================================================ import { css } from "lit"; export default css` .tab-header { display: inline-flex; max-width:90%; font-weight: 400; padding-inline: 1.5em; padding-top: 0.5em; padding-bottom: 0.5em; align-items: center; border-radius: 12px 12px 0px 0px; background-color: rgba(127, 127, 127, 0.3); margin-top: 1em; user-select: none; } .tab-container { border: 2px solid rgba(127, 127, 127, 0.3); border-radius: 0px 12px 12px 12px; } `; ================================================ FILE: webserver/packages/neato/src/custom-button.ts ================================================ import { html, css, LitElement, PropertyValues } from "lit"; import { customElement, property, query } from "lit/decorators.js"; import cssReset from "./css/reset"; import { pressButton } from "./api"; import { entityStore } from "./entity-store"; import { entityConfig } from "./types"; @customElement("custom-button") export class CustomButton extends LitElement { @property({ type: String }) click = ""; @property({ type: String }) press = ""; @property({ type: String }) release = ""; @property({ type: String }) icon = ""; @property({ type: String }) name = ""; @query("#custom-button") customButtom!: HTMLDivElement; private unsubscribe?: () => void; private entities: { click?: entityConfig, press?: entityConfig, release?: entityConfig, } = {} connectedCallback() { super.connectedCallback(); this.unsubscribe = entityStore.subscribe((entity) => { // only re-render when *its* entity changes if (entity.unique_id === this.click) this.entities.click = entity; if (entity.unique_id === this.press) this.entities.press = entity; if (entity.unique_id === this.release) this.entities.release = entity; this.requestUpdate(); }); } disconnectedCallback() { this.unsubscribe?.(); super.disconnectedCallback(); } get entityIds() { let i = []; if (this.click) i.push(this.click); if (this.press) i.push(this.press); if (this.release) i.push(this.release); return i; } onClick() { if (this.entities.click) pressButton(this.entities.click); } onMouseDown() { if (this.entities.press) pressButton(this.entities.press); } onMouseUp() { if (this.entities.release) pressButton(this.entities.release); } protected updated(_changedProperties: PropertyValues): void { if (!this.customButtom) return; if (this.press) { this.customButtom.removeEventListener("touchstart", this.onMouseDown.bind(this)) this.customButtom.addEventListener("touchstart", this.onMouseDown.bind(this)) } if (this.release) { this.customButtom.removeEventListener("touchend", this.onMouseUp.bind(this)) this.customButtom.addEventListener("touchend", this.onMouseUp.bind(this)) } } render() { // loaded.length !== to_load.length --> show loading... if (Object.keys(this.entities).length !== this.entityIds.length) { return html`
loading…
`; } return html`
${this.name || this.entities.click?.name || this.entities.press?.name || this.entities.release?.name}
`; } static get styles() { return [ cssReset, css` .cb { border: .1rem solid gray; border-radius: .5rem; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; padding: 1rem; width: 6rem; } .cb span { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } .cb:hover { background-color: rgba(255, 255, 255, 0.1); cursor: pointer; } @media (max-width: 1024px) { .cb { font-size: 0.8rem; width: 5.3rem; } } `, ]; } } ================================================ FILE: webserver/packages/neato/src/custom-table.ts ================================================ import { html, css, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import cssReset from "./css/reset"; import { pressButton } from "./api"; import { Button_Gen3 } from "./neato-enums"; import { entityStore } from "./entity-store"; import { entityConfig } from "./types"; import cssEntityTable from "./css/esp-entity-table"; export const stateOn = "ON"; export const stateOff = "OFF"; interface RestAction { restAction(entity?: entityConfig, action?: string): void; } @customElement("custom-table") export class CustomTable extends LitElement implements RestAction { @property({ type: String }) entityIds: string[] = [] @property() customNames: { [key: string]: string } = {} @property() customValues: { [key: string]: (value: string) => string } = {} private unsubscribe?: () => void; private entities: entityConfig[] = []; private _actionRenderer = new ActionRenderer(); connectedCallback() { super.connectedCallback(); this.unsubscribe = entityStore.subscribe((entity) => { // only re-render when *its* entity changes if (this.entityIds.some(id => id === entity.unique_id)) { this.entities.push(entity); this.entities.sort( (a, b) => this.entityIds.indexOf(a.unique_id) - this.entityIds.indexOf(b.unique_id) ); this.requestUpdate(); } }); } disconnectedCallback() { this.unsubscribe?.(); super.disconnectedCallback(); } render() { if (!this.entities.length) { return html`loading…`; } return html`
${this.entities.map((component) => html`
${component.icon ? html` ` : nothing}
${this.customNames?.[component.unique_id] || component.name}
${component.has_action ? this.control(component) : html`
${this.customValues?.[component.unique_id] ? this.customValues?.[component.unique_id](component.state) : component.state}
`}
`)}
`; } hasAction(entity: entityConfig): boolean { return `render_${entity.domain}` in this._actionRenderer; } control(entity: entityConfig) { this._actionRenderer.entity = entity; this._actionRenderer.actioner = this; return this._actionRenderer.exec( `render_${entity.domain}` as ActionRendererMethodKey ); } restAction(entity: entityConfig, action: string) { fetch(`${window.apiBasePath}/${entity.domain}/${entity.id}/${action}`, { method: "POST", headers:{ 'Content-Type': 'application/x-www-form-urlencoded' }, }).then((r) => { console.log(r); }); } static get styles() { return [ cssReset, cssEntityTable, ]; } } type ActionRendererNonCallable = "entity" | "actioner" | "exec"; export type ActionRendererMethodKey = keyof Omit< ActionRenderer, ActionRendererNonCallable >; export class ActionRenderer { public entity?: entityConfig; public actioner?: RestAction; exec(method: ActionRendererMethodKey) { if (!this[method] || typeof this[method] !== "function") { console.log(`ActionRenderer.${method} is not callable`); return; } return this[method](); } private _actionButton(entity: entityConfig, label: string, action: string, isCurrentState: boolean = false) { if (!entity) return; let a = action || label.toLowerCase(); return html``; } private _datetime( entity: entityConfig, type: string, action: string, opt: string, value: string, ) { return html` `; } private _switch(entity: entityConfig) { return html``; } private _select( entity: entityConfig, action: string, opt: string, options: string[] | number[], val: string | number | undefined ) { return html``; } private _range( entity: entityConfig, action: string, opt: string, value: string | number, min?: string | undefined, max?: string | undefined, step = 1 ) { if(entity.mode == 1) { return html`
`; } else { return html` `; } } private _textinput( entity: entityConfig, action: string, opt: string, value: string | number, min: number | undefined, max: number | undefined, pattern: string | undefined ) { return html` `; } private _colorpicker(entity: entityConfig, action: string, value: any) { function u16tohex(d: number) { return Number(d).toString(16).padStart(2, "0"); } function rgb_to_str(rgbhex: string) { const rgb = rgbhex .match(/[0-9a-f]{2}/gi) ?.map((x) => parseInt(x, 16)) || [0, 0, 0]; return `r=${rgb[0]}&g=${rgb[1]}&b=${rgb[2]}`; } return html`
`; } render_binary_sensor() { if (!this.entity) return; const isOn = this.entity.state == stateOn; return html``; } render_date() { if (!this.entity) return; return html` ${this._datetime( this.entity, "date", "set", "value", this.entity.value, )} `; } render_time() { if (!this.entity) return; return html` ${this._datetime( this.entity, "time", "set", "value", this.entity.value, )} `; } render_datetime() { if (!this.entity) return; return html` ${this._datetime( this.entity, "datetime-local", "set", "value", this.entity.value, )} `; } render_switch() { if (!this.entity) return; if (this.entity.assumed_state) return html`${this._actionButton(this.entity, "❌", "turn_off")} ${this._actionButton(this.entity, "✔️", "turn_on")}`; else return this._switch(this.entity); } render_fan() { if (!this.entity) return; return [ this.entity.speed, " ", this.entity.speed_level, this._switch(this.entity), this.entity.speed_count ? this._range( this.entity, `turn_${this.entity.state.toLowerCase()}`, "speed_level", this.entity.speed_level ? this.entity.speed_level : 0, 0, this.entity.speed_count, 1 ) : "", ]; } render_light() { if (!this.entity) return; return [ html`
${this._switch(this.entity)} ${this.entity.brightness ? this._range( this.entity, "turn_on", "brightness", this.entity.brightness, 0, 255, 1 ) : ""} ${this.entity.color_mode === "rgb" || this.entity.color_mode === "rgbw" ? this._colorpicker(this.entity, "turn_on", this.entity?.color) : ""} ${this.entity.effects?.filter((v) => v != "None").length ? this._select( this.entity, "turn_on", "effect", this.entity.effects || [], this.entity.effect ) : ""}
`, ]; } render_lock() { if (!this.entity) return; return html`${this._actionButton(this.entity, "🔐", "lock", this.entity.state === "LOCKED")} ${this._actionButton(this.entity, "🔓", "unlock", this.entity.state === "UNLOCKED")} ${this._actionButton(this.entity, "↑", "open")} `; } render_cover() { if (!this.entity) return; return html`${this._actionButton(this.entity, "↑", "open", this.entity.state === "OPEN")} ${this._actionButton(this.entity, "☐", "stop")} ${this._actionButton(this.entity, "↓", "close", this.entity.state === "CLOSED")}`; } render_button() { if (!this.entity) return; return html`${this._actionButton(this.entity, "PRESS", "press")}`; } render_select() { if (!this.entity) return; return this._select( this.entity, "set", "option", this.entity.option || [], this.entity.value ); } render_number() { if (!this.entity) return; return html` ${this._range( this.entity, "set", "value", this.entity.value, this.entity.min_value, this.entity.max_value, this.entity.step )} ${this.entity.uom} `; } render_text() { if (!this.entity) return; return this._textinput( this.entity, "set", "value", this.entity.value, this.entity.min_length, this.entity.max_length, this.entity.pattern ); } render_climate() { if (!this.entity) return; let target_temp_slider, target_temp_label, target_temp; let current_temp = html`
`; if ( this.entity.target_temperature_low !== undefined && this.entity.target_temperature_high !== undefined ) { target_temp = html`
${this._range( this.entity, "set", "target_temperature_low", this.entity.target_temperature_low, this.entity.min_temp, this.entity.max_temp, this.entity.step )}
${this._range( this.entity, "set", "target_temperature_high", this.entity.target_temperature_high, this.entity.min_temp, this.entity.max_temp, this.entity.step )}
`; } else { target_temp = html`
${this._range( this.entity, "set", "target_temperature", this.entity.target_temperature!!, this.entity.min_temp, this.entity.max_temp, this.entity.step )}
`; } let modes = html``; if ((this.entity.modes ? this.entity.modes.length : 0) > 0) { modes = html`
${this._select( this.entity, "set", "mode", this.entity.modes || [], this.entity.mode || "" )}
`; } return html`
${current_temp} ${target_temp} ${modes}
`; } render_valve() { if (!this.entity) return; return html`${this._actionButton(this.entity, "OPEN", "open", this.entity.state === "OPEN")} ${this._actionButton(this.entity, "☐", "stop")} ${this._actionButton(this.entity, "CLOSE", "close", this.entity.state === "CLOSED")}`; } } ================================================ FILE: webserver/packages/neato/src/entity-store.ts ================================================ // entity-store.ts import { entityConfig } from "./types"; class EntityStore { private entities = new Map(); private listeners = new Set<(entity: entityConfig) => void>(); set(entity: entityConfig) { this.entities.set(entity.unique_id, entity); this.notify(entity); } get(entityId: string) { return this.entities.get(entityId); } subscribe(cb: (entity: entityConfig) => void) { this.listeners.add(cb); return () => this.listeners.delete(cb); } private notify(entity: entityConfig) { for (const cb of this.listeners) cb(entity); } } export const entityStore = new EntityStore(); ================================================ FILE: webserver/packages/neato/src/esp-app.ts ================================================ import { LitElement, html, css, PropertyValues, nothing } from "lit"; import { customElement, state, query } from "lit/decorators.js"; import './api'; import "./esp-log"; import "./custom-button"; import "./custom-table"; import "./esp-switch"; import "./esp-range-slider"; import "./esp-schedule"; import "./timezone-selector"; import "./manual-driving"; import "iconify-icon"; import cssReset from "./css/reset"; import cssButton from "./css/button"; import cssApp from "./css/app"; import cssTab from "./css/tab"; import { BinarySensor, Button, Button_Gen2, Button_Gen3, ESPNumber, ESPText, Select, Select_gen3, Sensor, Switch, Switch_gen2, TextSensor } from "./neato-enums"; import { entityStore } from "./entity-store"; import { ActionRenderer } from "./custom-table"; import { getBasePath } from "./utils"; window.source = new EventSource(getBasePath() + "/events"); window.entities = []; const _unknown_state_events: { [key: string]: number } = {}; window.source?.addEventListener('state', (e: Event) => { const messageEvent = e as MessageEvent; const data = JSON.parse(messageEvent.data.replace(/[\u0000-\u001F\u007F-\u009F]/g, "")); let idx = window.entities.findIndex((x) => x.unique_id === data.id); if (idx != -1 && data.id) { if (typeof data.value === 'number') { let history = [...window.entities[idx].value_numeric_history]; history.push(data.value); window.entities[idx].value_numeric_history = history.splice(-50); } delete data.id; delete data.domain; delete data.unique_id; Object.assign(window.entities[idx], data); } else { // is it a `detail_all` event already? if (data?.name) { addEntity(data); } else { if (_unknown_state_events[data.id]) { _unknown_state_events[data.id]++; } else { _unknown_state_events[data.id] = 1; } // ignore the first few events, maybe the esp will send a detail_all // event soon if (_unknown_state_events[data.id] < 1) { return; } let parts = data.id.split('-'); let domain = parts[0]; let id = parts.slice(1).join('-'); fetch(`${window.apiBasePath}/${domain}/${id}?detail=all`, { method: 'GET', }) .then((r) => { console.log(r); if (!r.ok) { throw new Error(`HTTP error! Status: ${r.status}`); } return r.json(); }) .then((data) => { console.log(data); addEntity(data); }) .catch((error) => { console.error('Fetch error:', error); }); } } }); function addEntity(data: any) { console.log(data); let idx = window.entities.findIndex((x) => x.unique_id === data.id); if (idx === -1 && data.id) { // Dynamically add discovered.. let parts = data.id.split("-"); let entity = { ...data, domain: parts[0], unique_id: data.id, id: parts.slice(1).join("-"), entity_category: data.entity_category, value_numeric_history: [data.value], }; entity.has_action = `render_${entity.domain}` in ActionRenderer.prototype window.entities.push(entity); entityStore.set(entity); } } interface Config { ota: boolean; log: boolean; title: string; comment: string; lang: string; } interface NBSConfig { type: "gen1" | "gen2" | "gen3"; version: string; comment: string; } function getRelativeTime(diff: number) { const mark = Math.sign(diff); if (diff === 0) return new Intl.RelativeTimeFormat("en").format(0, "second"); const times = [ { type: "year", seconds: 12 * 30 * 24 * 60 * 60 * 1000 }, { type: "month", seconds: 30 * 24 * 60 * 60 * 1000 }, { type: "week", seconds: 7 * 24 * 60 * 60 * 1000 }, { type: "day", seconds: 24 * 60 * 60 * 1000 }, { type: "hour", seconds: 60 * 60 * 1000 }, { type: "minute", seconds: 60 * 1000 }, { type: "second", seconds: 1000 }, ]; let result = ""; const timeformat = new Intl.RelativeTimeFormat("en"); let count = 0; for (let t of times) { const segment = Math.trunc(Math.abs(diff / t.seconds)); if (segment > 0) { const part = timeformat.format( segment * mark, t.type as Intl.RelativeTimeFormatUnit ); diff -= segment * t.seconds * mark; // remove "ago" from the first segment - if not the only one result += count === 0 && t.type != "second" ? part.replace(" ago", " ") : part; if (count++ >= 1) break; // do not display detail after two segments } } return result; } @customElement("esp-app") export default class EspApp extends LitElement { @state() scheme: string = ""; @state() ping: number = 0; @state() connected: boolean = true; @state() lastUpdate: number = 0; @query("#beat") beat!: HTMLSpanElement; @state() showLog: boolean = localStorage.showLog === "true"; version: String = import.meta.env.PACKAGE_VERSION; config: Config = { ota: false, log: true, title: "", comment: "", lang: "" }; nbsconfig?: NBSConfig; darkQuery: MediaQueryList = window.matchMedia("(prefers-color-scheme: dark)"); frames = [{}, { color: "rgba(0, 196, 21, 0.75)" }, {}]; constructor() { super(); const conf = document.querySelector("script#config"); console.log(conf); if (conf) this.setConfig(JSON.parse(conf.innerText)); } setConfig(config: Config) { if (!("log" in config)) { (config as any).log = this.config.log; } this.config = config; document.title = config.title; document.documentElement.lang = config.lang; const [type, version, comment] = config.comment.split("|"); this.nbsconfig = { type: type as NBSConfig["type"], version, comment }; } firstUpdated(changedProperties: PropertyValues) { super.firstUpdated(changedProperties); document.getElementsByTagName("head")[0].innerHTML += ''; const l = document.querySelector("link[rel~='icon']"); // Set favicon to house l.href = 'data:image/svg+xml,'; this.scheme = this.schemeDefault(); window.source.addEventListener("ping", (e: MessageEvent) => { if (e.data?.length) { const data = JSON.parse(e.data); if (data.title !== undefined) { this.setConfig(data); this.requestUpdate(); } } this._updateUptime(e); this.lastUpdate = Date.now(); }); window.source.addEventListener("log", (e: MessageEvent) => { this._updateUptime(e); this.lastUpdate = Date.now(); }); window.source.addEventListener("state", (e: MessageEvent) => { this.lastUpdate = Date.now(); }); window.source.addEventListener("error", (e: Event) => { console.dir(e); //console.log("Lost event stream!") this.connected = false; this.requestUpdate(); }); setInterval(() => { this.connected = !!this.ping && Date.now() - this.lastUpdate < 15000; }, 5000); document.addEventListener('entity-tab-header-double-clicked', (e) => { const mainElement = this.shadowRoot?.querySelector('main.flex-grid-half'); mainElement?.classList.toggle('expanded_entity'); }); document.addEventListener('log-tab-header-double-clicked', (e) => { const mainElement = this.shadowRoot?.querySelector('main.flex-grid-half'); mainElement?.classList.toggle('expanded_logs'); }); } schemeDefault() { return this.darkQuery.matches ? "dark" : "light"; } updated(changedProperties: Map) { super.updated(changedProperties); if (changedProperties.has("scheme")) { let el = document.documentElement; document.documentElement.style.setProperty("color-scheme", this.scheme); } if (changedProperties.has("ping")) { if (!!this.ping) this.beat.animate(this.frames, 1000); } } uptime() { return `${getRelativeTime(-this.ping | 0)}`; } renderOta() { if (this.config.ota) { let basePath = getBasePath(); return html`
`; } } renderLog() { if (!this.config.log || !this.showLog) return nothing; return html`
`; } renderTitle() { return html`

${this.config.title || html` `}

${[this.nbsconfig?.comment, `started ${this.uptime()}`, this.nbsconfig?.version] .filter((n) => n) .map((e) => `${e}`) .join(" · ")}
`; } /** * * */ render() { return html`
${this.renderTitle()}
${this.nbsconfig?.type === "gen3" ? html`` : nothing} ${this.nbsconfig?.type === "gen2" ? html`` : nothing}
${this.nbsconfig?.type === "gen3" ? html`
` : nothing}
value.startsWith("200") ? "No alerts" : value, }}" >
${this.nbsconfig?.type === "gen2" ? html`` : nothing}
${this.nbsconfig?.type === "gen3" ? html`` : nothing}
The manual for the Webserver can be found here. When new updates for Neato Brainslug is out, you can easily update your device by uploading the OTA file here! ${this.renderOta()} Type: ${this.nbsconfig?.type}       Toggle the debug logs: 
${this.renderLog()} `; } private _updateUptime(e: MessageEvent) { if (e.lastEventId) { this.ping = parseInt(e.lastEventId); this.connected = true; this.requestUpdate(); } } static get styles() { return [cssReset, cssButton, cssApp, cssTab]; } } ================================================ FILE: webserver/packages/neato/src/esp-log.ts ================================================ import { html, css, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import cssTab from "./css/tab"; interface recordConfig { type: string; level: string; tag: string; detail: string; when: string; } @customElement("esp-log") export class DebugLog extends LitElement { @property({ type: Number }) rows = 10; @property({ type: String }) scheme = ""; @state() logs: recordConfig[] = []; constructor() { super(); } connectedCallback() { super.connectedCallback(); window.source?.addEventListener("log", this.handleLog); } disconnectedCallback(): void { window.source?.removeEventListener("log", this.handleLog); super.disconnectedCallback(); } handleLog = (e: Event) => { const messageEvent = e as MessageEvent; const d: String = messageEvent.data; const types: Record = { "": "e", "": "w", "": "i", "": "c", "": "d", "": "v", }; // Extract the type from the color code const type = types[d.slice(0, 7)]; if (!type) { // No color code, skip return; } // Extract content without color codes and ANSI termination const content = d.slice(7, d.length - 4); // Split by newlines to handle multi-line messages const lines = content.split('\n'); // Process the first line to extract metadata const firstLine = lines[0]; const parts = firstLine.slice(3).split(":"); const tag = parts.slice(0, 2).join(":"); const firstDetail = firstLine.slice(5 + tag.length); const level = firstLine.slice(0, 3); const when = new Date().toTimeString().split(" ")[0]; // Create a log record for each line lines.forEach((line, index) => { console.log("ESP_LOG", line); const record = { type: type, level: level, tag: tag, detail: index === 0 ? firstDetail : line, when: when, } as recordConfig; this.logs.push(record); }); this.logs = this.logs.slice(-this.rows); } render() { return html`
Debug Log
Time
Level
Tag
Message
${this.logs.map( (log: recordConfig) => html`
${log.when}
${log.level}
${log.tag}
${log.detail}
` )}
`; } _handleTabHeaderDblClick(e: Event) { const doubleClickEvent = new CustomEvent('log-tab-header-double-clicked', { bubbles: true, composed: true, }); e.target?.dispatchEvent(doubleClickEvent); } static get styles() { return [ cssTab, css` .thead, .tbody .trow:nth-child(2n) { background-color: rgba(127, 127, 127, 0.05); } .trow div { font-family: monospace; width: 100%; line-height: 1.2rem; } .trow { display: flex; } .thead { line-height: 1rem; } .thead .trow { text-align: left; padding: 0.25rem 0.5rem; } .trow { display: flex; } .trow > div { align-self: flex-start; padding-right: 0.25em; flex: 2 0; min-width: 70px; } .trow > div:nth-child(2) { flex: 1 0; overflow: hidden; text-overflow: ellipsis; max-width: 40px; } .trow > div:nth-child(3) { flex: 3 0; overflow: hidden; text-overflow: ellipsis; } .trow > div:last-child { flex: 15 0; padding-right: 0em; overflow: hidden; text-overflow: ellipsis; } pre { margin: 0; } .v { color: #888888; } .d { color: #00dddd; } .c { color: magenta; } .i { color: limegreen; } .w { color: yellow; } .e { color: red; font-weight: bold; } .logs[color-scheme="light"] { font-weight: bold; } .logs[color-scheme="light"] .w { color: #cccc00; } .logs[color-scheme="dark"] .d { color: #00aaaa; } .logs { overflow-x: auto; border-radius: 12px; border-width: 1px; border-style: solid; border-color: rgba(127, 127, 127, 0.12); transition: all 0.3s ease-out 0s; font-size: 14px; padding: 16px; } @media (max-width: 1024px) { .trow > div:nth-child(2) { display: none !important; } } `, ]; } } ================================================ FILE: webserver/packages/neato/src/esp-range-slider.ts ================================================ import { html, css, LitElement } from "lit"; import { customElement, property } from "lit/decorators.js"; import cssReset from "./css/reset"; const inputRangeID: string = "range"; const currentValueID: string = "rangeValue"; const pressTimeToShowPopup = 500; @customElement("esp-range-slider") export class EspRangeSlider extends LitElement { private inputRange: HTMLInputElement | null = null; private currentValue: HTMLInputElement | null = null; private longPressTimer: ReturnType | null = null; private isPopupInputVisible: boolean = false; @property({ type: String }) value = 0; @property({ type: String }) min = 0; @property({ type: String }) max = 0; @property({ type: String }) step = 0; @property({ type: String }) name = ""; protected firstUpdated( _changedProperties: Map ): void { this.inputRange = this.shadowRoot?.getElementById( inputRangeID ) as HTMLInputElement; this.currentValue = this.shadowRoot?.getElementById( currentValueID ) as HTMLInputElement; document.addEventListener('mousedown', (event) => { if(!document.querySelector('.popup-number-input')) { return; } const isClickedOutside = !document.querySelector('.popup-number-input')?.contains(event.target as Node); if (isClickedOutside && this.isPopupInputVisible) { this.deletePopupInput(); } }); } protected updated(): void { this.updateCurrentValueOverlay(); } onMouseDownCurrentValue(event: MouseEvent): void { this.longPressTimer = setTimeout(() => { this.showPopupInput(event.pageX, event.pageY); }, pressTimeToShowPopup); } onMouseUpCurrentValue(event: MouseEvent): void { if (this.longPressTimer && !this.isPopupInputVisible) { clearTimeout(this.longPressTimer); this.longPressTimer = null; } } onTouchStartCurrentValue(event: TouchEvent): void { this.longPressTimer = setTimeout(() => { this.showPopupInput(event.touches[0].pageX,event.touches[0].pageY); }, pressTimeToShowPopup); } onTouchEndCurrentValue(event: TouchEvent): void { if (this.longPressTimer && !this.isPopupInputVisible) { clearTimeout(this.longPressTimer); this.longPressTimer = null; } } deletePopupInput(): void { const popupInputElement = document.querySelector('.popup-number-input'); if (popupInputElement) { popupInputElement.remove(); } this.isPopupInputVisible = false; } showPopupInput(x: number, y: number): void { const popupInputElement = document.createElement('input'); popupInputElement.type = 'number'; popupInputElement.value = this.inputRange.value; popupInputElement.min = this.inputRange.min; popupInputElement.max = this.inputRange.max; popupInputElement.step = this.inputRange.step; popupInputElement.classList.add('popup-number-input'); const styles = ` position: absolute; left: ${x}px; top: ${y}px; width: 50px; -webkit-appearance: none; margin: 0; `; popupInputElement.setAttribute('style', styles); document.body.appendChild(popupInputElement); popupInputElement.addEventListener('contextmenu', (event) => { event.preventDefault(); }); popupInputElement.addEventListener('change', (ev: Event) =>{ let input = ev.target as HTMLInputElement; this.inputRange.value = input?.value; var event = new Event('input'); this.inputRange?.dispatchEvent(event); var event = new Event('change'); this.inputRange?.dispatchEvent(event); }); popupInputElement.addEventListener('keydown', (event) => { if (event.key === 'Enter') { this.deletePopupInput(); } }); popupInputElement.focus(); this.isPopupInputVisible = true; } updateCurrentValueOverlay(): void { const newValueAsPercent = Number( (this.inputRange.value - this.inputRange.min) * 100 / (this.inputRange.max - this.inputRange.min) ), newPosition = 10 - (newValueAsPercent * 0.2); this.currentValue.innerHTML = `${this.inputRange?.value}`; this.currentValue.style.left = `calc(${newValueAsPercent}% + (${newPosition}px))`; const spanTooltip = this.currentValue?.querySelector('span'); spanTooltip?.addEventListener('mousedown', this.onMouseDownCurrentValue.bind(this)); spanTooltip?.addEventListener('mouseup', this.onMouseUpCurrentValue.bind(this)); spanTooltip?.addEventListener('touchstart', this.onTouchStartCurrentValue.bind(this)); spanTooltip?.addEventListener('touchend', this.onTouchEndCurrentValue.bind(this)); spanTooltip?.addEventListener('contextmenu', (event) => { event.preventDefault(); }); } onInputEvent(ev: Event): void { this.updateCurrentValueOverlay(); } onInputChangeEvent(ev: Event): void { this.sendState(this.inputRange?.value); } sendState(value: string|undefined): void { let event = new CustomEvent("state", { detail: { state: value, id: this.id, }, }); this.dispatchEvent(event); } render() { return html`
`; } static get styles() { return [ cssReset, css` :host { min-width: 150px; flex: 1; } input[type=range] { background: transparent; -webkit-appearance: none; appearance: none; margin: 20px 0; width: 100%; touch-action: none; } input[type=range]:focus { outline: none; } input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 4px; cursor: pointer; animate: 0.2s; background: #03a9f4; border-radius: 25px; } input[type=range]::-moz-range-track { width: 100%; height: 4px; cursor: pointer; animate: 0.2s; background: #03a9f4; border-radius: 25px; } input[type=range]::-ms-track { background: transparent; width: 100%; height: 4px; cursor: pointer; animate: 0.2s; background: transparent; border-color: transparent; color: transparent; } input[type=range]::-ms-fill-lower { background: #03a9f4; border-radius: 25px; } input[type=range]::-ms-fill-upper { background: #03a9f4; border-radius: 25px; } input[type=range]::-webkit-slider-thumb { height: 20px; width: 20px; border-radius: 50%; background: #fff; box-shadow: 0 0 4px 0 rgba(0,0,0, 1); cursor: pointer; -webkit-appearance: none; margin-top: -8px; } input[type=range]::-moz-range-thumb { height: 20px; width: 20px; border-radius: 50%; background: #fff; box-shadow: 0 0 4px 0 rgba(0,0,0, 1); cursor: pointer; border: none; } input[type=range]::-ms-thumb { height: 20px; width: 20px; border-radius: 50%; background: #fff; box-shadow: 0 0 4px 0 rgba(0,0,0, 1); cursor: pointer; border: none; } input[type=range]:focus::-webkit-slider-runnable-track { background: #03a9f4; } input[type=range]:focus::-moz-range-track { background: #03a9f4; } input[type=range]:focus::-ms-fill-lower { background: #03a9f4; } input[type=range]:focus::-ms-fill-upper { background: #03a9f4; } .range-wrap { display: flex; align-items: center; } .slider-wrap { flex-grow: 1; margin: 0px 15px; position: relative; } .range-value { position: absolute; top: -50%; } .range-value span { padding: 0 3px 0 3px; height: 19px; line-height: 18px; text-align: center; background: #03a9f4; color: #fff; font-size: 11px; display: block; position: absolute; left: 50%; transform: translate(-50%, +80%); border-radius: 6px; } @-moz-document url-prefix() { .range-value span { transform: translate(-50%, +150%); } } .range-value span:before { content: ""; position: absolute; width: 0; height: 0; border-top: 10px solid #03a9f4; border-left: 5px solid transparent; border-right: 5px solid transparent; top: 100%; left: 50%; margin-left: -5px; margin-top: -1px; pointer-events: none; } `, ]; } } ================================================ FILE: webserver/packages/neato/src/esp-schedule.ts ================================================ import { LitElement, html, css } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { entityConfig } from "./types"; import { entityStore } from "./entity-store"; import { setText } from "./api"; @customElement("esp-schedule") export class WeeklySchedule extends LitElement { static styles = css` :host { display: block; /* max-width: 420px; */ } .row { display: grid; grid-template-columns: 44px 1fr auto; align-items: center; gap: 10px; padding: 6px 0; } .row.global { grid-template-columns: 1fr auto; padding-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); margin-bottom: 8px; } label { font-size: 0.95rem; font-weight: bold; } .day-disabled label { opacity: 0.5; } input[type="text"] { background: #2b2b2b; color: #fff; border: 1px solid #444; border-radius: 8px; padding: 6px 8px; font-size: 0.9rem; width: 70px; text-align: center; } input[type="text"]:disabled { opacity: 0.4; } .time { display: flex; align-items: center; gap: 6px; } .time input { width: 36px; padding: 6px 4px; text-align: center; font-size: 0.9rem; } .hour, .minute { display: flex; flex-direction: column; align-items: center; } .sep { opacity: 0.6; } .arrow { border: none; background: transparent; color: #aaa; font-size: 10px; line-height: 10px; cursor: pointer; } .arrow:hover { color: #6aa9ff; } .arrow:disabled { opacity: 0.3; cursor: default; } .switch { position: relative; width: 44px; height: 24px; border-radius: 999px; background: #555; cursor: pointer; transition: background 0.2s ease; } .switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: #fff; transition: transform 0.2s ease; } .switch.on { background: #3b82f6; } .switch.on::after { transform: translateX(20px); } `; @property({ type: String }) entityId = "text-scheduleset"; private unsubscribe?: () => void; private entity?: entityConfig; connectedCallback() { super.connectedCallback(); this.unsubscribe = entityStore.subscribe(() => { const entity = entityStore.get(this.entityId); if (entity) { this.entity = entity; this.setFromString(this.entity.value); } }); } disconnectedCallback() { this.unsubscribe?.(); super.disconnectedCallback(); } private days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", ]; @state() globalEnabled = true; @state() dayEnabled = Array(7).fill(true); @state() values = Array(7).fill(0); // 0–143 private buildFlags(): number { let flags = this.globalEnabled ? 1 : 0; this.dayEnabled.forEach((en, i) => { if (en) flags |= 1 << (i + 1); }); return flags; } private scheduleString(): string { const flags = this.buildFlags(); return [ flags, ...this.values, ].join(","); } private emit() { const value = this.scheduleString(); console.log(value); if (!this.entity) return; this.requestUpdate(); setText(this.entity, value); } setFromString(str: string) { const parts = str.split(",").map(s => Number(s.trim())); // safety check if (parts.length !== 8 || parts.some(isNaN)) return; const flags = parts[0]; this.globalEnabled = (flags & 0x01) === 0x01; // first bit for (let i = 0; i < 7; i++) { this.dayEnabled[i] = ((flags >> (i + 1)) & 0x01) === 0x01; this.values[i] = this.clampValue(parts[i + 1]); } this.requestUpdate(); } clampValue(v: number) { if (isNaN(v)) return 0; if (v < 0) return 0; if (v > 143) return 143; return v; } private valueToHM(v: number) { return { h: Math.floor(v / 6), m: (v % 6) * 10, }; } private pad2(n: number): string { return String(n).padStart(2, "0"); } private roundMinute(m: number): number { m = Math.round(m / 10) * 10; if (m < 0) return 0; if (m > 50) return 50; return m; } private hmToValue(h: number, m: number): number | null { if (h < 0 || h > 23) return null; m = this.roundMinute(m); return h * 6 + m / 10; } private adjustMinute(i: number, delta: number) { const { h, m } = this.valueToHM(this.values[i]); let nm = m + delta; if (nm > 50) nm = 50; if (nm < 0) nm = 0; const v = this.hmToValue(h, nm); if (v !== null) { this.values[i] = v; this.emit(); } } private adjustHour(i: number, delta: number) { const { h, m } = this.valueToHM(this.values[i]); let nh = h + delta; if (nh < 0) nh = 23; if (nh > 23) nh = 0; const v = this.hmToValue(nh, m); if (v !== null) { this.values[i] = v; this.emit(); } } render() { if (!this.entity) return html `loading...` return html`
{ this.globalEnabled = !this.globalEnabled; this.emit(); }}>
${this.days.map((day, i) => html`
{ this.dayEnabled[i] = !this.dayEnabled[i]; this.emit(); }}>
{ let h = Number(e.target.value); if (isNaN(h)) h = 0; if (h < 0) h = 0; if (h > 23) h = 23; const { m } = this.valueToHM(this.values[i]); const v = this.hmToValue(h, m); if (v !== null) { this.values[i] = v; e.target.value = this.pad2(h); this.emit(); } }}/>
:
{ let m = Number(e.target.value); if (isNaN(m)) m = 0; const { h } = this.valueToHM(this.values[i]); const v = this.hmToValue(h, m); if (v !== null) { const { m: rounded } = this.valueToHM(v); this.values[i] = v; e.target.value = this.pad2(rounded); this.emit(); } }}/>
`)}`; } } ================================================ FILE: webserver/packages/neato/src/esp-switch.ts ================================================ import { html, css, LitElement } from "lit"; import { customElement, property } from "lit/decorators.js"; import cssReset from "./css/reset"; import { stateOff, stateOn } from "./custom-table"; const checkboxID: string = "checkbox-lever"; @customElement("esp-switch") export class EspSwitch extends LitElement { private checkbox: HTMLInputElement | null = null; // Use arrays - or slots @property({ type: String }) stateOn = stateOn; @property({ type: String }) stateOff = stateOff; @property({ type: String }) state = stateOff; @property({ type: String }) color = "currentColor"; @property({ type: Boolean }) disabled = false; protected firstUpdated( _changedProperties: Map ): void { this.checkbox = this.shadowRoot?.getElementById( checkboxID ) as HTMLInputElement; } private isOn(): boolean { return this.state === this.stateOn; } toggle(ev: Event): void { const newState = this.isOn() ? this.stateOff : this.stateOn; let event = new CustomEvent("state", { detail: { state: newState, id: this.id, }, }); this.dispatchEvent(event); } render() { return html`
`; } static get styles() { return [ cssReset, css` .sw, .sw * { -webkit-tap-highlight-color: transparent; user-select: none; cursor: pointer; } input[type="checkbox"] { opacity: 0; width: 0; height: 0; } input[type="checkbox"]:checked + .lever { background-color: currentColor; background-image: linear-gradient( 0deg, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0.5) 100% ); } input[type="checkbox"]:checked + .lever:before, input[type="checkbox"]:checked + .lever:after { left: 18px; } input[type="checkbox"]:checked + .lever:after { background-color: currentColor; } input[type="checkbox"]:not(:checked) + .lever:after { background-color: rgba(127, 127, 127, 0.5); } .lever { content: ""; display: inline-block; position: relative; width: 36px; height: 14px; background-image: linear-gradient( 0deg, rgba(127, 127, 127, 0.5) 0%, rgba(127, 127, 127, 0.5) 100% ); background-color: inherit; border-radius: 15px; transition: background 0.3s ease; vertical-align: middle; } .lever:before, .lever:after { content: ""; position: absolute; display: inline-block; width: 20px; height: 20px; border-radius: 50%; left: 0; top: -3px; transition: left 0.3s ease, background 0.3s ease, box-shadow 0.1s ease, transform 0.1s ease; } .lever:before { background-color: currentColor; background-image: linear-gradient( 0deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.9) 100% ); } .lever:after { background-color: #f1f1f1; box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); } input[type="checkbox"]:checked:not(:disabled) ~ .lever:active::before, input[type="checkbox"]:checked:not(:disabled).tabbed:focus ~ .lever::before { transform: scale(2.4); background-color: rgba(255, 255, 255, 0.9) 0%; background-image: linear-gradient( 0deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.9) 100% ); } input[type="checkbox"]:not(:disabled) ~ .lever:active:before, input[type="checkbox"]:not(:disabled).tabbed:focus ~ .lever::before { transform: scale(2.4); background-color: rgba(0, 0, 0, 0.08); } input[type="checkbox"][disabled] + .lever { cursor: default; background-color: rgba(0, 0, 0, 0.12); } input[type="checkbox"][disabled] + .lever:after, input[type="checkbox"][disabled]:checked + .lever:after { background-color: #949494; } `, ]; } } ================================================ FILE: webserver/packages/neato/src/main.css ================================================ /* First, declare your dark mode colors */ :root { --c-bg: #fff; --c-text: #000; --c-primary: #26a69a; --color:0, 100%; --l:50%; --color-primary: #26a69a; --color-primary-darker: hsl(var(--color),calc(var(--l) - 5%)); --color-primary-darkest: hsl(var(--color),calc(var(--l) - 10%)); --color-text: #5b3e81; --color-text-rgb: 47, 6, 100; --color-primary-lighter: rgba(var(--color-text-rgb), 50%); --color-slider-thingy: 38, 166, 154; --primary-color: hsla(323, 18%, 49%, 0.924); --dark-primary-color: #0288d1; --light-primary-color: #b3e5fC; --c-pri-rgb: 3, 169, 244; --c-pri: rgba(var(--c-pri-rgb),100%); --c-pri-l: rgba(var(--c-pri-rgb), 50%); --c-pri-d: hsl(var(--c-pri-rgb),calc(var(--l) - 5%); --color-primary-lighter2: rgba(var(--c-pri), 50%)); font-size: 62.5%!important; } @media (prefers-color-scheme: dark) { :root { --c-bg: #1c1c1c; --c-text: #fff; } } html[color-scheme="dark"] img { filter: invert(100%); } /* For browsers that don’t support `color-scheme` and therefore don't handle system dark mode for you automatically (Firefox), handle it for them. */ @supports not (color-scheme: light dark) { html { background: var(--c-bg); color: var(--c-text); } } /* For browsers that support automatic dark/light mode As well as system colors, set those */ @supports (color-scheme: light dark) and (background-color: Canvas) and (color: CanvasText) { :root { --c-bg: Canvas; --c-text: ButtonText; } } /* For Safari on iOS. Hacky, but it works. */ @supports (background-color: -apple-system-control-background) and (color: text) { :root { --c-bg: -apple-system-control-background; --c-text: text; } } html { color-scheme: light dark; font-family: ui-monospace, system-ui, "Helvetica", "Arial Narrow", "Roboto", "Oxygen", "Ubuntu", sans-serif; } html button, html .btn { cursor: pointer; border-radius: 1rem; background-color: inherit; background-image: linear-gradient(0deg, rgba(127, 127, 127, 0.5) 0%, rgba(127, 127, 127, 0.5) 100%); color: inherit; border: 1px solid rgba(127, 127, 127, 0.5); height: 1.2rem; } html * { transition-property: color; transition-duration: 450ms !important; transition-timing-function: ease !important; transition-delay: 0s !important; } ================================================ FILE: webserver/packages/neato/src/main.ts ================================================ import './neato-entry'; // const pickerHTML = '

Which webserver would you like to see?

'; // const urls: Record = { // "2": "https://oi.esphome.io/v2/www.js", // "3": "https://oi.esphome.io/v3/www.js", // }; // function resetHTML() { // document.head.innerHTML = ''; // document.body.innerHTML = ''; // } // function loadCustomJS(js: string) { // resetHTML(); // const script = document.createElement("script"); // script.src = js; // document.body.appendChild(script); // } // async function loadNeato() { // resetHTML(); // await import('./neato-entry'); // } // function showPicker() { // document.head.innerHTML = ''; // document.body.innerHTML = pickerHTML; // // 3️⃣ Hook buttons // document.getElementById("btnNeato")!.addEventListener("click", () => { // localStorage.removeItem("pickWebserver") // loadNeato(); // }); // document.querySelectorAll(".esphome").forEach(btn => { // btn.addEventListener("click", () => { // const pick = (btn as HTMLElement).dataset.pick!; // const remember = (document.getElementById("rememberChoice") as HTMLInputElement)?.checked; // if (remember) localStorage.pickWebserver = pick; // loadCustomJS(urls[pick]); // }); // }); // document.getElementById("btnCustom")!.addEventListener("click", () => { // const url = (document.getElementById("customUrl") as HTMLInputElement).value; // if (!url) return alert("Please enter a URL"); // const remember = (document.getElementById("rememberChoice") as HTMLInputElement)?.checked; // if (remember) localStorage.pickWebserver = url; // optional: store full URL // loadCustomJS(url); // }); // } // // If nothing, load neato // // If "0" we show picker, if "2" or "3" load those versions // // if anything else, load that url // if (!localStorage.pickWebserver) loadNeato(); // else if (localStorage.pickWebserver === "0") showPicker(); // else if (localStorage.pickWebserver === "2") loadCustomJS(urls["2"]); // else if (localStorage.pickWebserver === "3") loadCustomJS(urls["3"]); // else loadCustomJS(localStorage.pickWebserver); ================================================ FILE: webserver/packages/neato/src/manual-driving.ts ================================================ import { LitElement, html, css } from "lit"; import { customElement } from "lit/decorators.js"; import { Button_Gen3 } from "./neato-enums"; @customElement("manual-driving") export class ManualDriving extends LitElement { static styles = css` .manual-driving { display: flex; justify-content: center; align-items: center; flex-direction: column; } .manual-driving span { width: 75%; padding: 1rem; margin: 1.5rem 0; background-color: rgba(127, 127, 127, 0.3); border-radius: 0.5rem; } `; render() { return html`
You need to turn manual cleaning on by pressing "Start", once the vacuum is in manual cleaning mode you can hold down the different actions! Timeout will stop the current action.
` } } ================================================ FILE: webserver/packages/neato/src/neato-entry.ts ================================================ import "./esp-app" ================================================ FILE: webserver/packages/neato/src/neato-enums.ts ================================================ export const Button = { house_clean: "button-house_clean", spot_clean: "button-spot_clean", spot_clean__height___width_: "button-spot_clean__height___width_", stop_cleaning: "button-stop_cleaning", pause_cleaning: "button-pause_cleaning", resume_cleaning: "button-resume_cleaning", locate_robot: "button-locate_robot", update_status: "button-update_status", clear_errors: "button-clear_errors", shutdown: "button-shutdown", powercycle: "button-powercycle", reboot_esp: "button-reboot_esp", } export const Button_Gen3 = { ...Button, send_to_base: "button-send_to_base", start_manual_cleaning: "button-start_manual_cleaning", manual_drive_forward_up: "button-manual_drive_forward_up", manual_drive_backwards_up: "button-manual_drive_backwards_up", manual_drive_turn_left_up: "button-manual_drive_turn_left_up", manual_drive_turn_right_up: "button-manual_drive_turn_right_up", manual_drive_arc_left_up: "button-manual_drive_arc_left_up", manual_drive_arc_right_up: "button-manual_drive_arc_right_up", manual_drive_forward_down: "button-manual_drive_forward_down", manual_drive_backwards_down: "button-manual_drive_backwards_down", manual_drive_turn_left_down: "button-manual_drive_turn_left_down", manual_drive_turn_right_down: "button-manual_drive_turn_right_down", manual_drive_arc_left_down: "button-manual_drive_arc_left_down", manual_drive_arc_right_down: "button-manual_drive_arc_right_down", manual_drive_button_timeout: "button-manual_drive_button_timeout", } export const Button_Gen2 = { ...Button, send_to_start: "button-send_to_start", } export const Select = { logger: "select-logger_select", } export const Select_gen3 = { ...Select, navigation_mode: "select-navigation_mode", } export const BinarySensor = { usb_connected: "binary_sensor-usb_connected", battery_over_temp: "binary_sensor-battery_over_temp", charging_active: "binary_sensor-charging_active", charging_enabled: "binary_sensor-charging_enabled", confident_on_fuel: "binary_sensor-confident_on_fuel", on_reserved_fuel: "binary_sensor-on_reserved_fuel", empty_fuel: "binary_sensor-empty_fuel", battery_failure: "binary_sensor-battery_failure", ext_power_present: "binary_sensor-ext_power_present", thermistor_present: "binary_sensor-thermistor_present", } export const Sensor = { fuel_percent: "sensor-fuel_percent", battery_temp_c_avg: "sensor-battery_temp_c_avg", battery_voltage_v: "sensor-battery_voltage_v", external_voltage_v: "sensor-external_voltage_v", charger_mah: "sensor-charger_mah", discharge_mah: "sensor-discharge_mah", filter_change_time: "sensor-filter_change_time", brush_change_time: "sensor-brush_change_time", dirt_bin_alert_reminder: "sensor-dirt_bin_alert_reminder", current_dirt_bin_runtime: "sensor-current_dirt_bin_runtime", number_of_full_dust_bin_cleanings: "sensor-number_of_full_dust_bin_cleanings", battery_cycles: "sensor-battery_cycles", last_cleaning_duration: "sensor-last_cleaning_duration", } export const ESPNumber = { spot_clean_width: "number-spot_clean_width", spot_clean_height: "number-spot_clean_height", } export const ESPText = { timezone: "text-timezone", schedule: "text-scheduleset", } export const Switch = { test_mode: "switch-test_mode", play_extra_sounds: "switch-play_extra_sounds", click_sounds: "switch-click_sounds", led: "switch-led", wall_enable: "switch-wall_enable", eco_mode: "switch-eco_mode", intenseclean: "switch-intenseclean", wifi: "switch-wifi", melody_sounds: "switch-melody_sounds", warning_sounds: "switch-warning_sounds", bin_full_detect: "switch-bin_full_detect", } export const Switch_gen2 = { ...Switch, stealthled: "switch-stealthled", autoshutdown: "switch-autoshutdown", robot_schedule: "switch-robot_schedule" } export const TextSensor = { last_cleaning_time: "text_sensor-last_cleaning_time", last_cleaning_type: "text_sensor-last_cleaning_type", robot_error: "text_sensor-robot_error", robot_alert: "text_sensor-robot_alert", serial_number: "text_sensor-serial_number", model: "text_sensor-model", software: "text_sensor-software", ui_state: "text_sensor-ui_state", nbs_time: "text_sensor-nbs_time", } ================================================ FILE: webserver/packages/neato/src/timezone-selector.ts ================================================ import { LitElement, html, css } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { setText } from "./api"; import { entityStore } from "./entity-store"; import { entityConfig } from "./types"; @customElement("timezone-selector") export class TimezoneSelector extends LitElement { @property({ type: String }) value: string = ""; @state() query: string = ""; @state() filtered: string[] = []; @state() selected: string = ""; @property({ type: String }) entityId = "text-timezone"; private unsubscribe?: () => void; private entity?: entityConfig; connectedCallback() { super.connectedCallback(); this.unsubscribe = entityStore.subscribe((entity) => { if (entity.unique_id === this.entityId) { this.entity = entity; this.requestUpdate(); } }); } disconnectedCallback() { this.unsubscribe?.(); super.disconnectedCallback(); } firstUpdated() { this.filtered = Object.keys(this.zones); if (this.value) this.select(this.value); } updated() { // if external code changes "value" if (this.value && this.value !== this.selected) { this.select(this.value); } } filter() { const q = this.query.toLowerCase(); this.filtered = Object.keys(this.zones).filter(k => k.toLowerCase().includes(q) ); } select(zone: string) { this.selected = zone; this.query = zone; this.dispatchEvent(new CustomEvent("timezone-changed", { detail: zone })); if (this.entity) setText(this.entity, this.zones[zone]); } render() { if (!this.entity) return html`loading...` return html` In case the "NBS Time" is not the correct timezone, select your timezone here.
{ this.query = e.target.value; this.filter(); }} placeholder="Type to search timezones…" />
${this.filtered.slice(0, 50).map(zone => html`
this.select(zone)} > ${zone} ${this.zones[zone]}
`)}
`; } static styles = css` .wrap { margin-top: 1rem; display: flex; flex-direction: column; gap: 8px; } input { padding: 10px; border-radius: 8px; border: 1px solid #444; } .list { max-height: 240px; overflow: auto; border: 1px solid #444; border-radius: 8px; } .item { display: flex; justify-content: space-between; padding: 8px; cursor: pointer; } .item:hover { background: rgba(127, 127, 127, 0.7); } .item.sel { background: rgba(82, 82, 82, 0.7); } .code { opacity: 0.6; font-size: 0.8rem; } `; zones: Record = { "Africa/Abidjan": "GMT0", "Africa/Accra": "GMT0", "Africa/Addis_Ababa": "EAT-3", "Africa/Algiers": "CET-1", "Africa/Asmara": "EAT-3", "Africa/Bamako": "GMT0", "Africa/Bangui": "WAT-1", "Africa/Banjul": "GMT0", "Africa/Bissau": "GMT0", "Africa/Blantyre": "CAT-2", "Africa/Brazzaville": "WAT-1", "Africa/Bujumbura": "CAT-2", "Africa/Cairo": "EET-2EEST,M4.5.5/0,M10.5.4/24", "Africa/Casablanca": "<+01>-1", "Africa/Ceuta": "CET-1CEST,M3.5.0,M10.5.0/3", "Africa/Conakry": "GMT0", "Africa/Dakar": "GMT0", "Africa/Dar_es_Salaam": "EAT-3", "Africa/Djibouti": "EAT-3", "Africa/Douala": "WAT-1", "Africa/El_Aaiun": "<+01>-1", "Africa/Freetown": "GMT0", "Africa/Gaborone": "CAT-2", "Africa/Harare": "CAT-2", "Africa/Johannesburg": "SAST-2", "Africa/Juba": "CAT-2", "Africa/Kampala": "EAT-3", "Africa/Khartoum": "CAT-2", "Africa/Kigali": "CAT-2", "Africa/Kinshasa": "WAT-1", "Africa/Lagos": "WAT-1", "Africa/Libreville": "WAT-1", "Africa/Lome": "GMT0", "Africa/Luanda": "WAT-1", "Africa/Lubumbashi": "CAT-2", "Africa/Lusaka": "CAT-2", "Africa/Malabo": "WAT-1", "Africa/Maputo": "CAT-2", "Africa/Maseru": "SAST-2", "Africa/Mbabane": "SAST-2", "Africa/Mogadishu": "EAT-3", "Africa/Monrovia": "GMT0", "Africa/Nairobi": "EAT-3", "Africa/Ndjamena": "WAT-1", "Africa/Niamey": "WAT-1", "Africa/Nouakchott": "GMT0", "Africa/Ouagadougou": "GMT0", "Africa/Porto-Novo": "WAT-1", "Africa/Sao_Tome": "GMT0", "Africa/Tripoli": "EET-2", "Africa/Tunis": "CET-1", "Africa/Windhoek": "CAT-2", "America/Adak": "HST10HDT,M3.2.0,M11.1.0", "America/Anchorage": "AKST9AKDT,M3.2.0,M11.1.0", "America/Anguilla": "AST4", "America/Antigua": "AST4", "America/Araguaina": "<-03>3", "America/Argentina/Buenos_Aires": "<-03>3", "America/Argentina/Catamarca": "<-03>3", "America/Argentina/Cordoba": "<-03>3", "America/Argentina/Jujuy": "<-03>3", "America/Argentina/La_Rioja": "<-03>3", "America/Argentina/Mendoza": "<-03>3", "America/Argentina/Rio_Gallegos": "<-03>3", "America/Argentina/Salta": "<-03>3", "America/Argentina/San_Juan": "<-03>3", "America/Argentina/San_Luis": "<-03>3", "America/Argentina/Tucuman": "<-03>3", "America/Argentina/Ushuaia": "<-03>3", "America/Aruba": "AST4", "America/Asuncion": "<-03>3", "America/Atikokan": "EST5", "America/Bahia": "<-03>3", "America/Bahia_Banderas": "CST6", "America/Barbados": "AST4", "America/Belem": "<-03>3", "America/Belize": "CST6", "America/Blanc-Sablon": "AST4", "America/Boa_Vista": "<-04>4", "America/Bogota": "<-05>5", "America/Boise": "MST7MDT,M3.2.0,M11.1.0", "America/Cambridge_Bay": "MST7MDT,M3.2.0,M11.1.0", "America/Campo_Grande": "<-04>4", "America/Cancun": "EST5", "America/Caracas": "<-04>4", "America/Cayenne": "<-03>3", "America/Cayman": "EST5", "America/Chicago": "CST6CDT,M3.2.0,M11.1.0", "America/Chihuahua": "CST6", "America/Costa_Rica": "CST6", "America/Creston": "MST7", "America/Cuiaba": "<-04>4", "America/Curacao": "AST4", "America/Danmarkshavn": "GMT0", "America/Dawson": "MST7", "America/Dawson_Creek": "MST7", "America/Denver": "MST7MDT,M3.2.0,M11.1.0", "America/Detroit": "EST5EDT,M3.2.0,M11.1.0", "America/Dominica": "AST4", "America/Edmonton": "MST7MDT,M3.2.0,M11.1.0", "America/Eirunepe": "<-05>5", "America/El_Salvador": "CST6", "America/Fort_Nelson": "MST7", "America/Fortaleza": "<-03>3", "America/Glace_Bay": "AST4ADT,M3.2.0,M11.1.0", "America/Godthab": "<-02>2<-01>,M3.5.0/-1,M10.5.0/0", "America/Goose_Bay": "AST4ADT,M3.2.0,M11.1.0", "America/Grand_Turk": "EST5EDT,M3.2.0,M11.1.0", "America/Grenada": "AST4", "America/Guadeloupe": "AST4", "America/Guatemala": "CST6", "America/Guayaquil": "<-05>5", "America/Guyana": "<-04>4", "America/Halifax": "AST4ADT,M3.2.0,M11.1.0", "America/Havana": "CST5CDT,M3.2.0/0,M11.1.0/1", "America/Hermosillo": "MST7", "America/Indiana/Indianapolis": "EST5EDT,M3.2.0,M11.1.0", "America/Indiana/Knox": "CST6CDT,M3.2.0,M11.1.0", "America/Indiana/Marengo": "EST5EDT,M3.2.0,M11.1.0", "America/Indiana/Petersburg": "EST5EDT,M3.2.0,M11.1.0", "America/Indiana/Tell_City": "CST6CDT,M3.2.0,M11.1.0", "America/Indiana/Vevay": "EST5EDT,M3.2.0,M11.1.0", "America/Indiana/Vincennes": "EST5EDT,M3.2.0,M11.1.0", "America/Indiana/Winamac": "EST5EDT,M3.2.0,M11.1.0", "America/Inuvik": "MST7MDT,M3.2.0,M11.1.0", "America/Iqaluit": "EST5EDT,M3.2.0,M11.1.0", "America/Jamaica": "EST5", "America/Juneau": "AKST9AKDT,M3.2.0,M11.1.0", "America/Kentucky/Louisville": "EST5EDT,M3.2.0,M11.1.0", "America/Kentucky/Monticello": "EST5EDT,M3.2.0,M11.1.0", "America/Kralendijk": "AST4", "America/La_Paz": "<-04>4", "America/Lima": "<-05>5", "America/Los_Angeles": "PST8PDT,M3.2.0,M11.1.0", "America/Lower_Princes": "AST4", "America/Maceio": "<-03>3", "America/Managua": "CST6", "America/Manaus": "<-04>4", "America/Marigot": "AST4", "America/Martinique": "AST4", "America/Matamoros": "CST6CDT,M3.2.0,M11.1.0", "America/Mazatlan": "MST7", "America/Menominee": "CST6CDT,M3.2.0,M11.1.0", "America/Merida": "CST6", "America/Metlakatla": "AKST9AKDT,M3.2.0,M11.1.0", "America/Mexico_City": "CST6", "America/Miquelon": "<-03>3<-02>,M3.2.0,M11.1.0", "America/Moncton": "AST4ADT,M3.2.0,M11.1.0", "America/Monterrey": "CST6", "America/Montevideo": "<-03>3", "America/Montreal": "EST5EDT,M3.2.0,M11.1.0", "America/Montserrat": "AST4", "America/Nassau": "EST5EDT,M3.2.0,M11.1.0", "America/New_York": "EST5EDT,M3.2.0,M11.1.0", "America/Nipigon": "EST5EDT,M3.2.0,M11.1.0", "America/Nome": "AKST9AKDT,M3.2.0,M11.1.0", "America/Noronha": "<-02>2", "America/North_Dakota/Beulah": "CST6CDT,M3.2.0,M11.1.0", "America/North_Dakota/Center": "CST6CDT,M3.2.0,M11.1.0", "America/North_Dakota/New_Salem": "CST6CDT,M3.2.0,M11.1.0", "America/Nuuk": "<-02>2<-01>,M3.5.0/-1,M10.5.0/0", "America/Ojinaga": "CST6CDT,M3.2.0,M11.1.0", "America/Panama": "EST5", "America/Pangnirtung": "EST5EDT,M3.2.0,M11.1.0", "America/Paramaribo": "<-03>3", "America/Phoenix": "MST7", "America/Port-au-Prince": "EST5EDT,M3.2.0,M11.1.0", "America/Port_of_Spain": "AST4", "America/Porto_Velho": "<-04>4", "America/Puerto_Rico": "AST4", "America/Punta_Arenas": "<-03>3", "America/Rainy_River": "CST6CDT,M3.2.0,M11.1.0", "America/Rankin_Inlet": "CST6CDT,M3.2.0,M11.1.0", "America/Recife": "<-03>3", "America/Regina": "CST6", "America/Resolute": "CST6CDT,M3.2.0,M11.1.0", "America/Rio_Branco": "<-05>5", "America/Santarem": "<-03>3", "America/Santiago": "<-04>4<-03>,M9.1.6/24,M4.1.6/24", "America/Santo_Domingo": "AST4", "America/Sao_Paulo": "<-03>3", "America/Scoresbysund": "<-02>2<-01>,M3.5.0/-1,M10.5.0/0", "America/Sitka": "AKST9AKDT,M3.2.0,M11.1.0", "America/St_Barthelemy": "AST4", "America/St_Johns": "NST3:30NDT,M3.2.0,M11.1.0", "America/St_Kitts": "AST4", "America/St_Lucia": "AST4", "America/St_Thomas": "AST4", "America/St_Vincent": "AST4", "America/Swift_Current": "CST6", "America/Tegucigalpa": "CST6", "America/Thule": "AST4ADT,M3.2.0,M11.1.0", "America/Thunder_Bay": "EST5EDT,M3.2.0,M11.1.0", "America/Tijuana": "PST8PDT,M3.2.0,M11.1.0", "America/Toronto": "EST5EDT,M3.2.0,M11.1.0", "America/Tortola": "AST4", "America/Vancouver": "PST8PDT,M3.2.0,M11.1.0", "America/Whitehorse": "MST7", "America/Winnipeg": "CST6CDT,M3.2.0,M11.1.0", "America/Yakutat": "AKST9AKDT,M3.2.0,M11.1.0", "America/Yellowknife": "MST7MDT,M3.2.0,M11.1.0", "Antarctica/Casey": "<+08>-8", "Antarctica/Davis": "<+07>-7", "Antarctica/DumontDUrville": "<+10>-10", "Antarctica/Macquarie": "AEST-10AEDT,M10.1.0,M4.1.0/3", "Antarctica/Mawson": "<+05>-5", "Antarctica/McMurdo": "NZST-12NZDT,M9.5.0,M4.1.0/3", "Antarctica/Palmer": "<-03>3", "Antarctica/Rothera": "<-03>3", "Antarctica/Syowa": "<+03>-3", "Antarctica/Troll": "<+00>0<+02>-2,M3.5.0/1,M10.5.0/3", "Antarctica/Vostok": "<+05>-5", "Arctic/Longyearbyen": "CET-1CEST,M3.5.0,M10.5.0/3", "Asia/Aden": "<+03>-3", "Asia/Almaty": "<+05>-5", "Asia/Amman": "<+03>-3", "Asia/Anadyr": "<+12>-12", "Asia/Aqtau": "<+05>-5", "Asia/Aqtobe": "<+05>-5", "Asia/Ashgabat": "<+05>-5", "Asia/Atyrau": "<+05>-5", "Asia/Baghdad": "<+03>-3", "Asia/Bahrain": "<+03>-3", "Asia/Baku": "<+04>-4", "Asia/Bangkok": "<+07>-7", "Asia/Barnaul": "<+07>-7", "Asia/Beirut": "EET-2EEST,M3.5.0/0,M10.5.0/0", "Asia/Bishkek": "<+06>-6", "Asia/Brunei": "<+08>-8", "Asia/Chita": "<+09>-9", "Asia/Choibalsan": "<+08>-8", "Asia/Colombo": "<+0530>-5:30", "Asia/Damascus": "<+03>-3", "Asia/Dhaka": "<+06>-6", "Asia/Dili": "<+09>-9", "Asia/Dubai": "<+04>-4", "Asia/Dushanbe": "<+05>-5", "Asia/Famagusta": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Asia/Gaza": "EET-2EEST,M3.4.4/50,M10.4.4/50", "Asia/Hebron": "EET-2EEST,M3.4.4/50,M10.4.4/50", "Asia/Ho_Chi_Minh": "<+07>-7", "Asia/Hong_Kong": "HKT-8", "Asia/Hovd": "<+07>-7", "Asia/Irkutsk": "<+08>-8", "Asia/Jakarta": "WIB-7", "Asia/Jayapura": "WIT-9", "Asia/Jerusalem": "IST-2IDT,M3.4.4/26,M10.5.0", "Asia/Kabul": "<+0430>-4:30", "Asia/Kamchatka": "<+12>-12", "Asia/Karachi": "PKT-5", "Asia/Kathmandu": "<+0545>-5:45", "Asia/Khandyga": "<+09>-9", "Asia/Kolkata": "IST-5:30", "Asia/Krasnoyarsk": "<+07>-7", "Asia/Kuala_Lumpur": "<+08>-8", "Asia/Kuching": "<+08>-8", "Asia/Kuwait": "<+03>-3", "Asia/Macau": "CST-8", "Asia/Magadan": "<+11>-11", "Asia/Makassar": "WITA-8", "Asia/Manila": "PST-8", "Asia/Muscat": "<+04>-4", "Asia/Nicosia": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Asia/Novokuznetsk": "<+07>-7", "Asia/Novosibirsk": "<+07>-7", "Asia/Omsk": "<+06>-6", "Asia/Oral": "<+05>-5", "Asia/Phnom_Penh": "<+07>-7", "Asia/Pontianak": "WIB-7", "Asia/Pyongyang": "KST-9", "Asia/Qatar": "<+03>-3", "Asia/Qyzylorda": "<+05>-5", "Asia/Riyadh": "<+03>-3", "Asia/Sakhalin": "<+11>-11", "Asia/Samarkand": "<+05>-5", "Asia/Seoul": "KST-9", "Asia/Shanghai": "CST-8", "Asia/Singapore": "<+08>-8", "Asia/Srednekolymsk": "<+11>-11", "Asia/Taipei": "CST-8", "Asia/Tashkent": "<+05>-5", "Asia/Tbilisi": "<+04>-4", "Asia/Tehran": "<+0330>-3:30", "Asia/Thimphu": "<+06>-6", "Asia/Tokyo": "JST-9", "Asia/Tomsk": "<+07>-7", "Asia/Ulaanbaatar": "<+08>-8", "Asia/Urumqi": "<+06>-6", "Asia/Ust-Nera": "<+10>-10", "Asia/Vientiane": "<+07>-7", "Asia/Vladivostok": "<+10>-10", "Asia/Yakutsk": "<+09>-9", "Asia/Yangon": "<+0630>-6:30", "Asia/Yekaterinburg": "<+05>-5", "Asia/Yerevan": "<+04>-4", "Atlantic/Azores": "<-01>1<+00>,M3.5.0/0,M10.5.0/1", "Atlantic/Bermuda": "AST4ADT,M3.2.0,M11.1.0", "Atlantic/Canary": "WET0WEST,M3.5.0/1,M10.5.0", "Atlantic/Cape_Verde": "<-01>1", "Atlantic/Faroe": "WET0WEST,M3.5.0/1,M10.5.0", "Atlantic/Madeira": "WET0WEST,M3.5.0/1,M10.5.0", "Atlantic/Reykjavik": "GMT0", "Atlantic/South_Georgia": "<-02>2", "Atlantic/St_Helena": "GMT0", "Atlantic/Stanley": "<-03>3", "Australia/Adelaide": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", "Australia/Brisbane": "AEST-10", "Australia/Broken_Hill": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", "Australia/Currie": "AEST-10AEDT,M10.1.0,M4.1.0/3", "Australia/Darwin": "ACST-9:30", "Australia/Eucla": "<+0845>-8:45", "Australia/Hobart": "AEST-10AEDT,M10.1.0,M4.1.0/3", "Australia/Lindeman": "AEST-10", "Australia/Lord_Howe": "<+1030>-10:30<+11>-11,M10.1.0,M4.1.0", "Australia/Melbourne": "AEST-10AEDT,M10.1.0,M4.1.0/3", "Australia/Perth": "AWST-8", "Australia/Sydney": "AEST-10AEDT,M10.1.0,M4.1.0/3", "Etc/GMT": "GMT0", "Etc/GMT+0": "GMT0", "Etc/GMT+1": "<-01>1", "Etc/GMT+10": "<-10>10", "Etc/GMT+11": "<-11>11", "Etc/GMT+12": "<-12>12", "Etc/GMT+2": "<-02>2", "Etc/GMT+3": "<-03>3", "Etc/GMT+4": "<-04>4", "Etc/GMT+5": "<-05>5", "Etc/GMT+6": "<-06>6", "Etc/GMT+7": "<-07>7", "Etc/GMT+8": "<-08>8", "Etc/GMT+9": "<-09>9", "Etc/GMT-0": "GMT0", "Etc/GMT-1": "<+01>-1", "Etc/GMT-10": "<+10>-10", "Etc/GMT-11": "<+11>-11", "Etc/GMT-12": "<+12>-12", "Etc/GMT-13": "<+13>-13", "Etc/GMT-14": "<+14>-14", "Etc/GMT-2": "<+02>-2", "Etc/GMT-3": "<+03>-3", "Etc/GMT-4": "<+04>-4", "Etc/GMT-5": "<+05>-5", "Etc/GMT-6": "<+06>-6", "Etc/GMT-7": "<+07>-7", "Etc/GMT-8": "<+08>-8", "Etc/GMT-9": "<+09>-9", "Etc/GMT0": "GMT0", "Etc/Greenwich": "GMT0", "Etc/UCT": "UTC0", "Etc/UTC": "UTC0", "Etc/Universal": "UTC0", "Etc/Zulu": "UTC0", "Europe/Amsterdam": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Andorra": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Astrakhan": "<+04>-4", "Europe/Athens": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Belgrade": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Berlin": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Bratislava": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Brussels": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Bucharest": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Budapest": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Busingen": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Chisinau": "EET-2EEST,M3.5.0,M10.5.0/3", "Europe/Copenhagen": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Dublin": "IST-1GMT0,M10.5.0,M3.5.0/1", "Europe/Gibraltar": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Guernsey": "GMT0BST,M3.5.0/1,M10.5.0", "Europe/Helsinki": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Isle_of_Man": "GMT0BST,M3.5.0/1,M10.5.0", "Europe/Istanbul": "<+03>-3", "Europe/Jersey": "GMT0BST,M3.5.0/1,M10.5.0", "Europe/Kaliningrad": "EET-2", "Europe/Kiev": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Kirov": "MSK-3", "Europe/Lisbon": "WET0WEST,M3.5.0/1,M10.5.0", "Europe/Ljubljana": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/London": "GMT0BST,M3.5.0/1,M10.5.0", "Europe/Luxembourg": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Madrid": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Malta": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Mariehamn": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Minsk": "<+03>-3", "Europe/Monaco": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Moscow": "MSK-3", "Europe/Oslo": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Paris": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Podgorica": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Prague": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Riga": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Rome": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Samara": "<+04>-4", "Europe/San_Marino": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Sarajevo": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Saratov": "<+04>-4", "Europe/Simferopol": "MSK-3", "Europe/Skopje": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Sofia": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Stockholm": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Tallinn": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Tirane": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Ulyanovsk": "<+04>-4", "Europe/Uzhgorod": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Vaduz": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Vatican": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Vienna": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Vilnius": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Volgograd": "MSK-3", "Europe/Warsaw": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Zagreb": "CET-1CEST,M3.5.0,M10.5.0/3", "Europe/Zaporozhye": "EET-2EEST,M3.5.0/3,M10.5.0/4", "Europe/Zurich": "CET-1CEST,M3.5.0,M10.5.0/3", "Indian/Antananarivo": "EAT-3", "Indian/Chagos": "<+06>-6", "Indian/Christmas": "<+07>-7", "Indian/Cocos": "<+0630>-6:30", "Indian/Comoro": "EAT-3", "Indian/Kerguelen": "<+05>-5", "Indian/Mahe": "<+04>-4", "Indian/Maldives": "<+05>-5", "Indian/Mauritius": "<+04>-4", "Indian/Mayotte": "EAT-3", "Indian/Reunion": "<+04>-4", "Pacific/Apia": "<+13>-13", "Pacific/Auckland": "NZST-12NZDT,M9.5.0,M4.1.0/3", "Pacific/Bougainville": "<+11>-11", "Pacific/Chatham": "<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45", "Pacific/Chuuk": "<+10>-10", "Pacific/Easter": "<-06>6<-05>,M9.1.6/22,M4.1.6/22", "Pacific/Efate": "<+11>-11", "Pacific/Enderbury": "<+13>-13", "Pacific/Fakaofo": "<+13>-13", "Pacific/Fiji": "<+12>-12", "Pacific/Funafuti": "<+12>-12", "Pacific/Galapagos": "<-06>6", "Pacific/Gambier": "<-09>9", "Pacific/Guadalcanal": "<+11>-11", "Pacific/Guam": "ChST-10", "Pacific/Honolulu": "HST10", "Pacific/Kiritimati": "<+14>-14", "Pacific/Kosrae": "<+11>-11", "Pacific/Kwajalein": "<+12>-12", "Pacific/Majuro": "<+12>-12", "Pacific/Marquesas": "<-0930>9:30", "Pacific/Midway": "SST11", "Pacific/Nauru": "<+12>-12", "Pacific/Niue": "<-11>11", "Pacific/Norfolk": "<+11>-11<+12>,M10.1.0,M4.1.0/3", "Pacific/Noumea": "<+11>-11", "Pacific/Pago_Pago": "SST11", "Pacific/Palau": "<+09>-9", "Pacific/Pitcairn": "<-08>8", "Pacific/Pohnpei": "<+11>-11", "Pacific/Port_Moresby": "<+10>-10", "Pacific/Rarotonga": "<-10>10", "Pacific/Saipan": "ChST-10", "Pacific/Tahiti": "<-10>10", "Pacific/Tarawa": "<+12>-12", "Pacific/Tongatapu": "<+13>-13", "Pacific/Wake": "<+12>-12", "Pacific/Wallis": "<+12>-12" } } ================================================ FILE: webserver/packages/neato/src/types.d.ts ================================================ declare global { interface Window { source: EventSource; apiBasePath: string; entities: entityConfig[]; } } export interface entityConfig { unique_id: string; sorting_weight: number; sorting_group?: string; domain: string; id: string; state: string; detail: string; value: string; name: string; entity_category?: number; when: string; icon?: string; option?: string[]; assumed_state?: boolean; brightness?: number; color_mode?: string; color: object; target_temperature?: number; target_temperature_low?: number; target_temperature_high?: number; min_temp?: number; max_temp?: number; min_value?: string; max_value?: string; step?: number; min_length?: number; max_length?: number; pattern?: string; current_temperature?: number; modes?: number[]; mode?: number; speed_count?: number; speed_level?: number; speed: string; effects?: string[]; effect?: string; has_action?: boolean; value_numeric_history: number[]; uom?: string; is_disabled_by_default?: boolean; } ================================================ FILE: webserver/packages/neato/src/utils.ts ================================================ export function getBasePath() { let str = window.location.pathname; return str.endsWith("/") ? str.slice(0, -1) : str; } ================================================ FILE: webserver/packages/neato/vite.config.ts ================================================ import { defineConfig } from "vite"; import gzipPlugin from "rollup-plugin-gzip"; import minifyHTML from "rollup-plugin-minify-html-template-literals"; import { brotliCompressSync } from "zlib"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import loadVersion from "vite-plugin-package-version"; import { viteSingleFile } from "vite-plugin-singlefile"; import { minifyHtml as ViteMinifyHtml } from "vite-plugin-html"; import stripBanner from "rollup-plugin-strip-banner"; import replace from "@rollup/plugin-replace"; const proxy_target = process.env.PROXY_TARGET || "http://nodemcu.local"; export default defineConfig({ clearScreen: false, plugins: [ { ...nodeResolve({ exportConditions: ["development"] }), enforce: "pre", apply: "start", }, stripBanner(), loadVersion(), { ...minifyHTML(), enforce: "pre", apply: "build" }, // { ...ViteMinifyHtml({ removeComments: true }), enforce: "post", apply: "build", }, replace({ "@license": "license", "Value passed to 'css' function must be a 'css' function result:": "use css function", "Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.": "Use unsafeCSS", delimiters: ["", ""], preventAssignment: true, }), viteSingleFile(), { ...gzipPlugin({ filter: /\.(js|css|html|svg)$/, additionalFiles: [], customCompression: (content) => brotliCompressSync(Buffer.from(content)), fileName: ".br", }), enforce: "post", apply: "build", }, { ...gzipPlugin({ filter: /\.(js|css|html|svg)$/ }), enforce: "post", apply: "build", }, ], build: { brotliSize: false, // cssCodeSplit: true, outDir: "../../_static/neato", // polyfillModulePreload: false, rollupOptions: { output: { inlineDynamicImports: false, manualChunks: (chunk) => { return "vendor"; }, // create one js bundle, chunkFileNames: "[name].js", assetFileNames: "www[extname]", entryFileNames: "www.js", }, }, }, server: { open: "/", // auto open browser in dev mode host: true, // dev on local and network port: 5001, strictPort: true, proxy: { "/light": proxy_target, "/select": proxy_target, "/cover": proxy_target, "/switch": proxy_target, "/button": proxy_target, "/fan": proxy_target, "/lock": proxy_target, "/number": proxy_target, "/climate": proxy_target, "/events": proxy_target, "/text": proxy_target, "/date": proxy_target, "/time": proxy_target, "/valve": proxy_target, }, }, }); ================================================ FILE: webserver/tsconfig.json ================================================ { "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "es2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ // "outDir": "./", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ /* Module Resolution Options */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ } }