[
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: Device integration and bug\n    url: https://dev.duboc.pro/homebridge-tahoma\n    about: \n      Start here for unsupported device or any command,state unexpected behaviour\n  - name: Gestion d'un équipement et anomalies de fonctionnement\n    url: https://dev.duboc.pro/homebridge-tahoma\n    about: \n      Pour un équipement non pris en charge ou pour un problème de pilotage/affichage de l'état de votre produit, c'est par ici\n"
  },
  {
    "path": ".github/workflows/new-release.yml",
    "content": "name: Create Release\non:\n  push:\n    tags:  \n      - v*\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Create GitHub release\n        uses: Roang-zero1/github-create-release-action@master\n        with:\n          version_regex: ^v[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}"
  },
  {
    "path": ".github/workflows/stale-issue.yml",
    "content": "name: Mark stale issues and pull requests\n\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/stale@v3\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          stale-issue-message: >\n            There hasn't been any activity on this issue recently.\n\n            Please make sure to update to the latest Homebridge TaHoma version to see\n            if that solves the issue.\n\n            This issue will be closed in case of inactivity.\n          days-before-stale: 90\n          days-before-close: 120\n          stale-issue-label: \"inactive\"\n          exempt-issue-labels: \"blocked,waiting\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# Ignore compiled code\ndist\nold\n\n# ------------- Defaults ------------- #\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.pnp.*\n.DS_Store\n"
  },
  {
    "path": ".npmignore",
    "content": "# Ignore source code\nsrc\n\n# ------------- Defaults ------------- #\n\n# eslint\n.eslintrc\n\n# typescript\ntsconfig.json\n\n# vscode\n.vscode\n\n# nodemon\nnodemon.json\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.pnp.*"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"dbaeumer.vscode-eslint\"\n  ]\n}"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"files.eol\": \"\\n\",\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\"\n  },\n  \"editor.rulers\": [ 140 ],\n  \"eslint.enable\": true\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## 2.2.54 (2022-12-20)\n\n### Bug Fixes\n\n* Fix nodejs compatibility to node v12.4.0"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS"
  },
  {
    "path": "README.md",
    "content": "# Overkiz (Somfy) - Homebridge-TaHoma\n\nHomebridge plugin supporting Overkiz based platforms :\n\n| Service code\t\t\t\t| Vendor\t\t\t\t\t| Product compatibility\t\t\t\t\t\t\t\t\t\t\t\t|\n|---------------------------|---------------------------|-------------------------------------------------------------------|\n| `local`\t\t\t\t\t| Somfy local API\t\t\t| TaHoma, TaHoma Switch ([configure local API](#configure-local-api))\t\t\t\t\t\t\t\t\t\t\t\t|\n| `somfy_europe`\t\t\t| Somfy Europe\t\t\t \t| TaHoma, TaHoma Switch, Connexoon IO, Kit de connectivité Orange\t|\n| `somfy_australia`\t\t\t| Somfy Australia\t \t\t| Connexoon RTS and other products in Australia\t\t\t\t\t\t|\n| `somfy_north_america`\t\t| Somfy North America \t\t| TaHoma and other product in North America\t\t\t\t\t\t\t|\n| `cozytouch`\t\t\t\t| Atlantic, Thermor, Sauter | Cozytouch\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\n| `flexom`\t\t\t\t\t| Bouygues \t\t\t\t\t| Flexom\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\n| `hi_kumo`\t\t\t\t\t| Hitachi \t\t\t\t\t| Hi hi_kumo\t\t\t\t\t\t\t\t\t\t\t\t\t\t|\n| `rexel`\t\t\t\t\t| Rexel\t\t\t\t\t \t| Energeasy connectivité\t\t\t\t\t\t\t\t\t\t\t|\n\n\n# Installation\n\n1. Install homebridge using: `npm install -g homebridge`\n2. Install this plugin using: `npm install -g homebridge-tahoma`\n3. Update your configuration file. See bellow for a sample.\n\n# Configuration\n\nMinimal configuration sample:\n```\n{\n\t\"bridge\": {\n\t\t...\n\t},\n\n\t\"description\": \"...\",\n\n\t\"accessories\": [],\n\n\t\"platforms\":[\n\t\t{\n\t\t\t\"platform\": \"Tahoma\",\n\t\t\t\"name\": \"My TaHoma Box\",\n\n\t\t\t\"service\": \"somfy_europe\",\n\t\t\t\"user\": \"user@me.com\",\n\t\t\t\"password\": \"MyPassw0rd\",\n\t\t}\n\t]\n}\n```\n\nConfiguration parameters:\n\n| Parameter\t\t\t\t\t| Type\t\t\t| Default\t\t\t| Note\t\t\t\t\t\t\t\t\t\t\t|\n|---------------------------|---------------|-------------------|-----------------------------------------------|\n| `service`\t\t\t\t\t| String\t\t| 'somfy_europe'\t| optional, service name  (see above)\t\t\t|\n| `user`\t\t\t\t\t| String\t\t| null\t\t\t\t| mandatory, your service account username (or [gateway PIN / IP address](#configure-local-api))\t|\n| `password`\t\t\t\t| String\t\t| null\t\t\t\t| mandatory, your service account password (or [API token](#configure-local-api))\t|\n| `pollingPeriod`\t\t\t| Integer\t\t| 30\t\t\t\t| optional, bridge polling period in seconds\t|\n| `refreshPeriod`\t\t\t| Integer\t\t| 30\t\t\t\t| optional, device states refresh period in minutes\t|\n| `exclude`\t\t\t\t\t| String[]\t\t| []\t\t\t\t| optional, protocol, ui, widget or device name to exclude\t|\n| `exposeScenarios`\t\t\t| Boolean\t\t| false\t\t\t\t| optional, expose scenarios as HomeKit switches. Could also specify a list of string corresponding to scenarios names to expose\t|\n| `devicesConfig`\t\t\t| Object[]\t\t| []\t\t\t\t| optional list of device specific configuration (see below)\t|\n\n### Configure Local API\nLocal API service is available on TaHoma and TaHoma switch gateways.\n\n**WARNING: Switching to local API will break your HomeKit configuration (automations) as local API device identifiers actually differs.**\n\nTo use Local API you will have to:\n1. Activate `developer mode` ([www.somfy.com](https://www.somfy.com) > My Account > Activate developer mode) \n2. Generate API credentials at [https://dev.duboc.pro/homebridge-tahoma](https://dev.duboc.pro/homebridge-tahoma)\n3. Configure the plugin service to Local API: `\"service\":\"local\"`\n\nWhen using Local API service, please fill `user` with your gateway PIN number or IPv4 address and `password` with the token generated at [step 2](https://dev.duboc.pro/homebridge-tahoma)\n\nFor more information, browse [https://developer.somfy.com/developer-mode](https://developer.somfy.com/developer-mode)\n\n# Specific device configuration\n\nThis option allows you to apply a specific configuation to device or group of device.\nOne configuration is composed of a `key` attribute containing device name, widget, uiClass, protocol or unique identifier and as many parameter depending of device type.\n\n```\n{\n\t\"key\": \"Bedroom door\",\n\t\"param1\": \"value1\"\n\t...\n}\n```\n\n| Alarm parameters\t| Type\t\t| Default\t\t| Note\t\t\t\t\t|\n|-------------------|-----------|---------------|-----------------------|\n| `stayZones`\t\t| String\t| 'A'\t\t\t| optional, active zones (A,B,C) in 'Stay' mode\t|\n| `nightZones`\t\t| String\t| 'B'\t\t\t| optional, active zones (A,B,C) in 'Night' mode\t|\n| `occupancySensor`\t| Boolean\t| false\t\t\t| optional, add an occupancy widget linked to the alarm\t|\n\n| WindowCovering parameters\t| Type\t\t| Default\t\t| Note\t\t\t|\n|---------------------------|-----------|---------------|---------------|\n| `defaultPosition`\t\t\t| Integer\t| 0\t\t\t\t| optional, final position for UpDown rollershutter after any command\t|\n| `reverse`\t\t\t\t\t| Boolean\t| false\t\t\t| optional, reverse up/down in case of bad mounting\t|\n| `lowSpeed`\t\t\t\t| Boolean / String\t| false\t\t\t| optional, use low speed for roller shutter supporting it. If string, specify slot time with format \"HH:MM-HH:MM\". Low speed will be enabled between them.\t|\n| `blindMode`\t\t\t\t| String\t| null\t\t\t| optional, change main slider action to orientation. By default, both closure and orientation will be set. When setting ``blindMode: true`` the blinds work in the following way: Opening the blinds or setting them to 100% will fully open them. Closing the blinds or setting them to 0% will fully close them. Setting the blinds to a value between 1% and 99% will first close the blinds and then adjust thier horizontal tilt in a way that 99% means fully horizonal = more light, and 1% means nearly closed = less light. |\n| `blindsOnRollerShutter`\t| Boolean\t| false\t\t\t\t| optional, when blinds are installed on roller shutter motors allow slats to stay horizontal (opened) at intermediate position |\n| `movementDuration`\t\t| Integer\t| 0\t\t\t\t| optional, duration of a full shutter movement from 'open' to 'close' in seconds. Will be used to approximate shutter intermediate position. (0 = disable feature) |\n\n| GarageDoorOpener parameters\t| Type\t\t\t| Default\t\t| Note\t\t\t\t|\n|-------------------------------|---------------|---------------|-------------------|\n| `cyclic`\t\t\t\t\t\t| Boolean\t\t| false\t\t\t| optional, restore closed state after `cycleDuration` seconds for stateless devices with cyclic behaviour\t|\n| `cycleDuration`\t\t\t\t| Integer\t\t| false\t\t\t| optional, cycle duration (in seconds) for cyclic mode (default: 5 sec)\t\t\t\t|\n| `reverse`\t\t\t\t\t\t| Boolean\t\t| false\t\t\t| optional, reverse up/down in case of bad mounting\t|\n| `pedestrianDuration`\t\t\t| Integer\t\t| 0\t\t\t\t| optional, duration for pedestrian position for RTS gates\t|\n\n| HeatingSystem parameters\t\t| Type\t\t\t| Default\t\t| Note\t\t\t\t|\n|-------------------------------|---------------|---------------|-------------------|\n| `derogationDuration`\t\t\t| Integer\t\t| 1\t\t\t\t| optional, duration (in hours) for derogation orders\t|\n| `comfort`\t\t\t\t\t\t| Integer\t\t| 19\t\t\t| optional, comfort temperature used as display for heaters controled by pilot wire\t|\n| `eco`\t\t\t\t\t\t\t| Integer\t\t| 17\t\t\t| optional, comfort temperature used as display for heaters controled by pilot wire\t\t|\n\n\nFull configuration example:\n```\n{\n\t\"bridge\": {\n\t\t...\n\t},\n\n\t\"description\": \"...\",\n\n\t\"accessories\": [],\n\n\t\"platforms\":[\n\t\t{\n\t\t\t\"platform\": \"Tahoma\",\n\t\t\t\"name\": \"My Tahoma Bridge\",\n\n\t\t\t\"user\": \"user@me.com\",\n\t\t\t\"password\": \"MyPassw0rd\",\n\t\t\t\"service\": \"somfy_europe\",\n\t\t\t\"exclude\": [\"hue\", \"rts\", \"Main door\", \"Main door\", \"PositionableHorizontalAwning\"],\n\n\t\t\t\"devicesConfig\": [\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"Alarm\",\n\t\t\t\t\t\"stayZones\": \"A,C\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"Bedroom blind\",\n\t\t\t\t\t\"blindMode\": true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"GarageDoor\",\n\t\t\t\t\t\"reverse\": true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"UpDownRollerShutter\",\n\t\t\t\t\t\"defaultPosition\": 50\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}\n```\n\n# Contribute\n\nYou are welcome to contribute to this plugin development by opening an issue in case of unexpected behaviour or unsupported device.\n\nI do not expect any reward concerning this plugin, however, some users ask me for a [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=L4X489MG7FUCN) button as sign of contribution. Feel free to use it.\n"
  },
  {
    "path": "config.schema.json",
    "content": "{\n  \"pluginAlias\": \"Tahoma\",\n  \"pluginType\": \"platform\",\n  \"singular\": false,\n  \"schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"name\": {\n        \"title\": \"Name\",\n        \"type\": \"string\",\n        \"required\": true,\n        \"default\": \"TaHoma\"\n      },\n      \"service\": {\n        \"title\": \"Service\",\n        \"type\": \"string\",\n        \"default\": \"somfy_europe\",\n        \"oneOf\": [\n          {\n            \"title\": \"Local API (TaHoma / Switch)\",\n            \"enum\": [\n              \"local\"\n            ]\n          },\n          {\n            \"title\": \"Somfy Europe (TaHoma / Switch / Connexoon IO)\",\n            \"enum\": [\n              \"somfy_europe\"\n            ]\n          },\n          {\n            \"title\": \"Somfy Australia (Connexoon RTS)\",\n            \"enum\": [\n              \"somfy_australia\"\n            ]\n          },\n          {\n            \"title\": \"Somfy North America\",\n            \"enum\": [\n              \"somfy_north_america\"\n            ]\n          },\n          {\n            \"title\": \"Cozytouch (Atlantic / Thermor / Sauter)\",\n            \"enum\": [\n              \"cozytouch\"\n            ]\n          },\n          {\n            \"title\": \"Energeasy Connect (Rexel)\",\n            \"enum\": [\n              \"rexel\"\n            ]\n          },\n          {\n            \"title\": \"Hi Kumo (Hitachi)\",\n            \"enum\": [\n              \"hi_kumo\"\n            ]\n          },\n          {\n            \"title\": \"Flexom (Bouygues)\",\n            \"enum\": [\n              \"flexom\"\n            ]\n          },\n          {\n            \"title\": \"Flexom (Bouygues)\",\n            \"enum\": [\n              \"flexom\"\n            ]\n          }\n        ],\n        \"required\": true\n      },\n      \"user\": {\n        \"title\": \"User\",\n        \"type\": \"string\",\n        \"required\": true,\n        \"description\": \"Your username for selected service (email, gateway Pin or IP)\"\n      },\n      \"password\": {\n        \"title\": \"Password\",\n        \"type\": \"string\",\n        \"required\": true,\n        \"description\": \"Your password/token for selected service\"\n      },\n      \"pollingPeriod\": {\n        \"title\": \"Polling period\",\n        \"type\": \"number\",\n        \"minimum\": 10,\n        \"placeholder\": 60,\n        \"description\": \"Period (in seconds) for fetching device changes made from other controller (with TaHoma app for eg.)\"\n      },\n      \"refreshPeriod\": {\n        \"title\": \"Refresh period\",\n        \"type\": \"number\",\n        \"minimum\": 10,\n        \"placeholder\": 30,\n        \"description\": \"Period (in minutes) for refreshing device changes made locally (with remote control for eg.)\"\n      },\n      \"exposeScenarios\": {\n        \"title\": \"Expose scenarios\",\n        \"type\": \"boolean\",\n        \"description\": \"Expose scenarios as HomeKit switch to trigger them\"\n      },\n      \"exclude\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"string\"\n        }\n      },\n      \"devicesConfig\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"key\": {\n              \"title\": \"Device name\",\n              \"type\": \"string\",\n              \"required\": true,\n              \"description\": \"Device name, widget or uiClass type\"\n            },\n            \"blindMode\": {\n              \"title\": \"Blind mode\",\n              \"type\": \"boolean\",\n              \"description\": \"Manage blind orientation with main slider\"\n            },\n            \"blindsOnRollerShutter\": {\n              \"title\": \"Blinds on roller shutter\",\n              \"type\": \"boolean\",\n              \"description\": \"Manage blinds installed on roller shutter motors\"\n            },\n            \"reverse\": {\n              \"title\": \"Reverse\",\n              \"type\": \"boolean\",\n              \"description\": \"Reverse behaviour for open/close commands\"\n            },\n            \"lowSpeed\": {\n              \"title\": \"Low speed mode\",\n              \"type\": \"boolean\",\n              \"description\": \"Low speed for supported roller shutter\"\n            },\n            \"defaultPosition\": {\n              \"title\": \"Default position\",\n              \"type\": \"number\",\n              \"minimum\": 0,\n              \"maximum\": 100,\n              \"description\": \"Restore specific default position for stateless covering\"\n            },\n            \"movementDuration\": {\n              \"title\": \"Movement Duration\",\n              \"type\": \"integer\",\n              \"minimum\": 0,\n              \"description\": \"Duration from 'opened' to 'closed' position to estimate intermediate positions\"\n            },\n            \"cyclic\": {\n              \"title\": \"Cyclic\",\n              \"type\": \"boolean\",\n              \"description\": \"Emulate cyclic door\"\n            },\n            \"cycleDuration\": {\n              \"title\": \"Cycle Duration\",\n              \"type\": \"integer\",\n              \"description\": \"Cycle duration if cyclic mode enabled\"\n            },\n            \"occupancySensor\": {\n              \"title\": \"Occupancy sensor\",\n              \"type\": \"boolean\",\n              \"description\": \"Expose an occupancy sensor, active when alarm trigered\"\n            },\n            \"stayZones\": {\n              \"title\": \"Stay Zones\",\n              \"type\": \"string\",\n              \"description\": \"Zones to activate in Presence mode\"\n            },\n            \"nightZones\": {\n              \"title\": \"Night Zones\",\n              \"type\": \"string\",\n              \"description\": \"Zones to activate in Night mode\"\n            }\n          }\n        }\n      }\n    }\n  },\n  \"layout\": [\n    \"name\",\n    \"service\",\n    \"user\",\n    \"password\",\n    {\n      \"type\": \"fieldset\",\n      \"title\": \"What\",\n      \"description\": \"Select what kind of ressources to expose.\",\n      \"expandable\": true,\n      \"expanded\": false,\n      \"items\": [\n        \"exposeScenarios\",\n        {\n          \"title\": \"Exclude devices or scenarios\",\n          \"description\": \"Exclude devices or scenarios from being exposed\",\n          \"key\": \"exclude\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\",\n            \"description\": \"Device or scenarios name, widget, uiClass or protocol.\"\n          }\n        }\n      ]\n    },\n    {\n      \"type\": \"fieldset\",\n      \"title\": \"Device specific config\",\n      \"description\": \"Apply specific config for some devices or kind of devices.\",\n      \"expandable\": true,\n      \"expanded\": false,\n      \"items\": [\n        {\n          \"key\": \"devicesConfig\",\n          \"type\": \"array\",\n          \"items\": [\n            {\n              \"type\": \"div\",\n              \"items\": [\n                \"devicesConfig[].key\",\n                {\n                  \"title\": \"Window Covering\",\n                  \"type\": \"section\",\n                  \"expandable\": true,\n                  \"expanded\": false,\n                  \"items\": [\n                    \"devicesConfig[].reverse\",\n                    \"devicesConfig[].defaultPosition\",\n                    \"devicesConfig[].blindMode\",\n                    \"devicesConfig[].lowSpeed\",\n                    \"devicesConfig[].blindsOnRollerShutter\",\n                    \"devicesConfig[].movementDuration\"\n                  ]\n                },\n                {\n                  \"title\": \"Garage Door\",\n                  \"type\": \"section\",\n                  \"expandable\": true,\n                  \"expanded\": false,\n                  \"items\": [\n                    \"devicesConfig[].reverse\",\n                    \"devicesConfig[].cyclic\",\n                    \"devicesConfig[].cycleDuration\"\n                  ]\n                },\n                {\n                  \"title\": \"Alarm\",\n                  \"type\": \"section\",\n                  \"expandable\": true,\n                  \"expanded\": false,\n                  \"items\": [\n                    \"devicesConfig[].occupancySensor\",\n                    \"devicesConfig[].stayZones\",\n                    \"devicesConfig[].nightZones\"\n                  ]\n                }\n              ]\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"type\": \"fieldset\",\n      \"title\": \"Advanced Settings\",\n      \"description\": \"Don't change these, unless you understand what you're doing.\",\n      \"expandable\": true,\n      \"expanded\": false,\n      \"items\": [\n        \"pollingPeriod\",\n        \"refreshPeriod\"\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import tsParser from \"@typescript-eslint/parser\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport js from \"@eslint/js\";\nimport { FlatCompat } from \"@eslint/eslintrc\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst compat = new FlatCompat({\n    baseDirectory: __dirname,\n    recommendedConfig: js.configs.recommended,\n    allConfig: js.configs.all\n});\n\nexport default [{\n    ignores: [\"**/dist\"],\n}, ...compat.extends(\n    \"eslint:recommended\",\n    \"plugin:@typescript-eslint/eslint-recommended\",\n    \"plugin:@typescript-eslint/recommended\",\n), {\n    languageOptions: {\n        parser: tsParser,\n        ecmaVersion: 2018,\n        sourceType: \"module\",\n    },\n\n    rules: {\n        quotes: [\"warn\", \"single\"],\n\n        indent: [\"warn\", 4, {\n            SwitchCase: 1,\n        }],\n\n        semi: [\"warn\", \"always\"],\n        \"comma-dangle\": [\"warn\", \"always-multiline\"],\n        \"dot-notation\": \"off\",\n        eqeqeq: \"warn\",\n        curly: [\"warn\", \"all\"],\n        \"brace-style\": [\"warn\"],\n        \"prefer-arrow-callback\": [\"warn\"],\n        \"max-len\": [\"warn\", 140],\n        \"no-console\": [\"warn\"],\n        \"no-non-null-assertion\": [\"off\"],\n        \"comma-spacing\": [\"error\"],\n\n        \"no-multi-spaces\": [\"warn\", {\n            ignoreEOLComments: true,\n        }],\n\n        \"lines-between-class-members\": [\"warn\", \"always\", {\n            exceptAfterSingleLine: true,\n        }],\n\n        \"@typescript-eslint/explicit-function-return-type\": \"off\",\n        \"@typescript-eslint/no-non-null-assertion\": \"off\",\n        \"@typescript-eslint/explicit-module-boundary-types\": \"off\",\n        \"@typescript-eslint/no-explicit-any\": \"off\",\n    },\n}];\n"
  },
  {
    "path": "nodemon.json",
    "content": "{\n  \"watch\": [\n    \"src\"\n  ],\n  \"ext\": \"ts\",\n  \"ignore\": [],\n  \"exec\": \"tsc && homebridge -I -D\",\n  \"signal\": \"SIGTERM\",\n  \"env\": {\n    \"NODE_OPTIONS\": \"--trace-warnings\"\n  }\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"homebridge-tahoma\",\n  \"displayName\": \"Homebridge TaHoma\",\n  \"version\": \"2.2.61\",\n  \"description\": \"Sample Platform plugin for TaHoma and Cozytouch services (Somfy,Atlantic,Thermor,Sauter): https://github.com/dubocr/homebridge-tahoma\",\n  \"author\": \"Romain DUBOC <dubocr@gmail.com>\",\n  \"license\": \"Apache-2.0\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dubocr/homebridge-tahoma.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/dubocr/homebridge-tahoma/issues\"\n  },\n  \"engines\": {\n    \"node\": \">=12.4.0\",\n    \"homebridge\": \">=1.3.0\"\n  },\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"lint\": \"eslint src/**.ts\",\n    \"watch\": \"npm run build && npm link && nodemon\",\n    \"clean\": \"rimraf ./dist\",\n    \"build\": \"rimraf ./dist && tsc\",\n    \"prepublishOnly\": \"npm run lint && npm run build && npm version patch --m 'Release %s'\",\n    \"postpublish\": \"npm run clean\"\n  },\n  \"keywords\": [\n    \"homebridge-plugin\",\n    \"tahoma\",\n    \"cozytouch\",\n    \"somfy\",\n    \"connexoon\"\n  ],\n  \"homepage\": \"https://github.com/dubocr/homebridge-tahoma#readme\",\n  \"dependencies\": {\n    \"moment\": \"^2.30.1\",\n    \"overkiz-client\": \"^1.0.20\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^22.8.7\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.13.0\",\n    \"@typescript-eslint/parser\": \"^8.13.0\",\n    \"eslint\": \"^9.14.0\",\n    \"homebridge\": \"^1.8.5\",\n    \"nodemon\": \"^3.1.7\",\n    \"rimraf\": \"^6.0.1\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5.6.3\"\n  },\n  \"funding\": {\n    \"url\": \"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=L4X489MG7FUCN\"\n  }\n}\n"
  },
  {
    "path": "platform.schema.json",
    "content": "{\n  \"plugin_alias\": \"Tahoma\",\n  \"schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"platform\": {\n        \"title\": \"Platform\",\n        \"type\": \"string\",\n        \"const\": \"Tahoma\",\n        \"readOnly\": true\n      },\n      \"name\": {\n        \"title\": \"Name\",\n        \"type\": \"string\",\n        \"required\": true,\n        \"default\": \"TaHoma\",\n        \"description\": \"The name of this platform in HomeKit\"\n      },\n      \"service\": {\n        \"title\": \"Service\",\n        \"type\": \"string\",\n        \"default\": \"somfy_europe\",\n        \"oneOf\": [\n          {\n            \"title\": \"Local API (TaHoma / Switch)\",\n            \"enum\": [\n              \"local\"\n            ]\n          },\n          {\n            \"title\": \"Somfy Europe (TaHoma / Switch / Connexoon IO)\",\n            \"enum\": [\n              \"somfy_europe\"\n            ]\n          },\n          {\n            \"title\": \"Somfy Australia (Connexoon RTS)\",\n            \"enum\": [\n              \"somfy_australia\"\n            ]\n          },\n          {\n            \"title\": \"Somfy North America\",\n            \"enum\": [\n              \"somfy_north_america\"\n            ]\n          },\n          {\n            \"title\": \"Cozytouch (Atlantic / Thermor / Sauter)\",\n            \"enum\": [\n              \"cozytouch\"\n            ]\n          },\n          {\n            \"title\": \"Energeasy Connect (Rexel)\",\n            \"enum\": [\n              \"rexel\"\n            ]\n          },\n          {\n            \"title\": \"Hi Kumo (Hitachi)\",\n            \"enum\": [\n              \"hi_kumo\"\n            ]\n          },\n          {\n            \"title\": \"Flexom (Bouygues)\",\n            \"enum\": [\n              \"flexom\"\n            ]\n          }\n        ],\n        \"required\": true,\n        \"description\": \"Service name\"\n      },\n      \"user\": {\n        \"title\": \"Username\",\n        \"type\": \"string\",\n        \"description\": \"Your username for selected service (email, gateway Pin or IP)\"\n      },\n      \"password\": {\n        \"title\": \"Password\",\n        \"type\": \"string\",\n        \"options\": {\n          \"hidden\": true\n        },\n        \"description\": \"Your password/token for selected service\"\n      },\n      \"exposeScenarios\": {\n        \"title\": \"Expose scenarios\",\n        \"type\": \"boolean\",\n        \"description\": \"Expose scenarios as HomeKit switch to trigger them\"\n      },\n      \"exclude\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"type\": \"string\"\n        },\n        \"description\": \"List of device or scenario to exclude (should be a name, widget, uiClass or protocol)\"\n      }\n    }\n  }\n}"
  },
  {
    "path": "src/CustomCharacteristics.ts",
    "content": "import { HAP, Characteristic, Perms, Formats, WithUUID } from 'homebridge';\n\nexport let CurrentShowerCharacteristic: WithUUID<{ new(): Characteristic }>;\nexport let TargetShowerCharacteristic: WithUUID<{ new(): Characteristic }>;\nexport let MyPositionCharacteristic: WithUUID<{ new(): Characteristic }>;\nexport let ProgCharacteristic: WithUUID<{ new(): Characteristic }>;\nexport let EcoCharacteristic: WithUUID<{ new(): Characteristic }>;\nexport let TotalConsumptionCharacteristic: WithUUID<{ new(): Characteristic }>;\nexport let CurrentConsumptionCharacteristic: WithUUID<{ new(): Characteristic }>;\n\nexport class CustomCharacteristics {\n    constructor(hap: HAP) {\n        CurrentShowerCharacteristic = class extends hap.Characteristic {\n\n            public static readonly UUID: string = '10000001-0000-1000-8000-0026BB765291';\n\n            constructor() {\n                super('Current Shower', CurrentShowerCharacteristic.UUID, {\n                    format: Formats.INT,\n                    minValue: 0,\n                    maxValue: 8,\n                    minStep: 1,\n                    perms: [Perms.NOTIFY, Perms.PAIRED_READ],\n                });\n                this.value = this.getDefaultValue();\n            }\n        };\n\n        TargetShowerCharacteristic = class extends hap.Characteristic {\n\n            public static readonly UUID: string = '10000002-0000-1000-8000-0026BB765291';\n\n            constructor() {\n                super('Target Shower', TargetShowerCharacteristic.UUID, {\n                    format: Formats.INT,\n                    minValue: 0,\n                    maxValue: 8,\n                    minStep: 1,\n                    perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],\n                });\n                this.value = this.getDefaultValue();\n            }\n        };\n\n        MyPositionCharacteristic = class extends hap.Characteristic {\n\n            public static readonly UUID: string = '10000003-0000-1000-8000-0026BB765291';\n\n            constructor() {\n                super('My', MyPositionCharacteristic.UUID, {\n                    format: Formats.BOOL,\n                    perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],\n                });\n                this.value = this.getDefaultValue();\n            }\n        };\n\n        ProgCharacteristic = class extends hap.Characteristic {\n\n            public static readonly UUID: string = '10000004-0000-1000-8000-0026BB765291';\n\n            constructor() {\n                super('Prog', ProgCharacteristic.UUID, {\n                    format: Formats.BOOL,\n                    perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],\n                });\n                this.value = this.getDefaultValue();\n            }\n        };\n\n        EcoCharacteristic = class extends hap.Characteristic {\n\n            public static readonly UUID: string = '10000005-0000-1000-8000-0026BB765291';\n\n            constructor() {\n                super('Eco', EcoCharacteristic.UUID, {\n                    format: Formats.BOOL,\n                    perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],\n                });\n                this.value = this.getDefaultValue();\n            }\n        };\n\n        TotalConsumptionCharacteristic = class extends hap.Characteristic {\n\n            public static readonly UUID: string = 'E863F10C-079E-48FF-8F27-9C2605A29F52';\n\n            constructor() {\n                super('Total Consumption', TotalConsumptionCharacteristic.UUID, {\n                    format: Formats.FLOAT,\n                    unit: 'kWh',\n                    minValue: 0,\n                    maxValue: 1000000,\n                    minStep: 0.1,\n                    perms: [Perms.NOTIFY, Perms.PAIRED_READ],\n                });\n                this.value = this.getDefaultValue();\n            }\n        };\n\n        CurrentConsumptionCharacteristic = class extends hap.Characteristic {\n\n            public static readonly UUID: string = 'E863F10D-079E-48FF-8F27-9C2605A29F52';\n\n            constructor() {\n                super('Current Consumption', CurrentConsumptionCharacteristic.UUID, {\n                    format: Formats.FLOAT,\n                    unit: 'W',\n                    minValue: 0,\n                    maxValue: 12000,\n                    minStep: 0.1,\n                    perms: [Perms.NOTIFY, Perms.PAIRED_READ],\n                });\n                this.value = this.getDefaultValue();\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "src/Mapper.ts",
    "content": "import { Characteristics, Services } from './Platform';\nimport { CharacteristicValue, HAPStatus, Logger, PlatformAccessory, Service } from 'homebridge';\nimport { Device, State, Command, Action, ExecutionState } from 'overkiz-client';\nimport { Platform } from './Platform';\nimport { GREY } from './colors';\n\nexport default abstract class Mapper {\n    protected log: Logger;\n    private postponeTimer;\n    private debounceTimer;\n    protected stateless = false;\n    //protected config: Record<string, string | boolean | number> = {};\n    private executionId;\n    private actionPromise;\n    protected expectedStates: Array<string> = [];\n\n    constructor(\n        protected readonly platform: Platform,\n        protected readonly accessory: PlatformAccessory,\n        protected readonly device: Device,\n    ) {\n        this.log = this.platform.log;\n    }\n\n    public build() {\n        const config = Object.assign({},\n            this.platform.devicesConfig[this.device.definition.uiClass],\n            this.platform.devicesConfig[this.device.definition.widgetName],\n            this.platform.devicesConfig[this.device.label],\n            this.platform.devicesConfig[this.device.uuid],\n        );\n        this.stateless = this.device.states.length === 0 ||\n            (this.expectedStates.length > 0 && !this.expectedStates.some((state) => this.device.hasState(state)));\n        this.applyConfig(config);\n        if (Object.keys(config).length > 0) {\n            delete config.key;\n            if (this.platform.config.debug) {\n                this.log.info(`${GREY}  Config: `, JSON.stringify(config));\n            } else {\n                this.log.debug('  Config: ', JSON.stringify(config));\n            }\n        }\n\n        const services = this.registerServices();\n\n        const info = this.accessory.getService(Services.AccessoryInformation);\n        if (info) {\n            info.setCharacteristic(Characteristics.Manufacturer, this.device.manufacturer);\n            info.setCharacteristic(Characteristics.Model, this.device.model);\n            info.setCharacteristic(Characteristics.SerialNumber, this.device.address.substring(0, 64));\n            services.push(info);\n        }\n\n        this.accessory.services.forEach((service) => {\n            if (!services.find((s) => s.UUID === service.UUID && s.subtype === service.subtype)) {\n                this.accessory.removeService(service);\n            }\n        });\n\n        if (!this.stateless) {\n            // Init and register states changes\n            this.onStatesChanged(this.device.states, true);\n            this.device.on('states', states => this.onStatesChanged(states));\n\n            // Init and register sensors states changes\n            this.device.sensors.forEach((sensor) => {\n                this.onStatesChanged(sensor.states, true);\n                sensor.on('states', states => this.onStatesChanged(states));\n            });\n        }\n\n        // TODO: instanciate mapper for device sensors\n        // Configure accessory sensors\n        // this.device.sensors.forEach((sensor) => new mapper(platform, accessory, sensor)))\n    }\n\n    /**\n     * Helper methods\n     */\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    protected applyConfig(config) {\n        //\n    }\n\n    protected registerService(type: any, subtype?: string): Service {\n        let service: Service;\n        const name = subtype ? this.translate(subtype) : this.device.label;\n        if (subtype) {\n            service = this.accessory.getServiceById(type, subtype) || this.accessory.addService(type, name, subtype);\n        } else {\n            service = this.accessory.getService(type) || this.accessory.addService(type);\n        }\n        service.setCharacteristic(Characteristics.Name, name);\n        /*\n        service.getCharacteristic(Characteristics.Name)\n            .updateValue(name)\n            .onSet((value) => {\n                this.debug('Will rename ' + name + ' to ' + value);\n                this.platform.client.setDeviceName(this.device.deviceURL, value);\n            });\n        */\n        return service;\n    }\n\n    private translate(value: string) {\n        switch (value) {\n            case 'boost': return 'Boost';\n            case 'drying': return 'Séchage';\n            default: return value.charAt(0).toUpperCase() + value.slice(1);\n        }\n    }\n\n    protected debounce(task, immediate: Array<CharacteristicValue> = []) {\n        return async (value: CharacteristicValue) => {\n            if (this.debounceTimer !== null) {\n                clearTimeout(this.debounceTimer);\n            }\n            if (immediate.includes(value)) {\n                await task.bind(this, value)();\n            } else {\n                this.debounceTimer = setTimeout(async () => {\n                    this.debounceTimer = null;\n                    task.bind(this, value)().catch(() => null);\n                }, 500);\n            }\n        };\n    }\n\n    protected postpone(task, ...args) {\n        if (this.postponeTimer !== null) {\n            clearTimeout(this.postponeTimer);\n        }\n        this.postponeTimer = setTimeout(() => {\n            this.postponeTimer = null;\n            task.bind(this, ...args)();\n        }, 500);\n    }\n\n    protected async executeCommands(commands: Command | Array<Command> | undefined, standalone = false): Promise<Action> {\n        if (commands === undefined || (Array.isArray(commands) && commands.length === 0)) {\n            this.error('No target command for', this.device.label);\n            throw HAPStatus.RESOURCE_DOES_NOT_EXIST;\n        } else if (Array.isArray(commands)) {\n            for (const c of commands) {\n                this.info(c.name + JSON.stringify(c.parameters));\n            }\n        } else {\n            this.info(commands.name + JSON.stringify(commands.parameters));\n            commands = [commands];\n        }\n\n        const commandName = commands[0].name;\n        const localizedName = this.platform.translate(\n            commands[0].name + (commands[0].parameters.length > 0 ? '.' + commands[0].parameters[0] : ''),\n        );\n        /*\n        if (!this.isIdle) {\n            this.cancelExecution();\n        }\n        */\n\n        const highPriority = this.device.hasState('io:PriorityLockLevelState') ? true : false;\n        const label = this.device.label + ' - ' + localizedName;\n\n        if (this.actionPromise) {\n            this.actionPromise.action.addCommands(commands);\n        } else {\n            this.actionPromise = new Promise((resolve, reject) => {\n                setTimeout(async () => {\n                    try {\n                        this.executionId = await this.platform.executeAction(label, this.actionPromise.action, highPriority, standalone);\n                        resolve(this.actionPromise.action);\n                    } catch (error: any) {\n                        this.error(commandName + ' ' + error.message);\n                        reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);\n                    }\n                    this.actionPromise = null;\n                }, 100);\n\n            });\n            this.actionPromise.action = new Action(this.device.deviceURL, commands);\n            this.actionPromise.action.on('update', (state, event) => {\n                if (state === ExecutionState.FAILED) {\n                    this.error(commandName, event.failureType);\n                } else if (state === ExecutionState.COMPLETED) {\n                    this.info(commandName, state);\n                } else {\n                    this.debug(commandName, state);\n                }\n            });\n        }\n        return this.actionPromise;\n    }\n\n    private async delay(duration) {\n        return new Promise(resolve => setTimeout(resolve, duration));\n    }\n\n    protected async requestStatesUpdate(defer?: number) {\n        if (defer) {\n            await this.delay(defer * 1000);\n        }\n        await this.platform.client.refreshDeviceStates(this.device.deviceURL);\n    }\n\n    /**\n     * Logging methods\n     */\n\n    protected debug(...args) {\n        if (this.platform.config.debug) {\n            this.platform.log.info(`${GREY}[${this.device.label}]`, ...args);\n        } else {\n            this.platform.log.debug(`[${this.device.label}]`, ...args);\n        }\n    }\n\n    protected info(...args) {\n        this.platform.log.info(`[${this.device.label}]`, ...args);\n    }\n\n    protected warn(...args) {\n        this.platform.log.warn(`[${this.device.label}]`, ...args);\n    }\n\n    protected error(...args) {\n        this.platform.log.error(`[${this.device.label}]`, ...args);\n    }\n\n    protected registerServices(): Array<Service> {\n        if (typeof this.registerMainService === 'function') {\n            try {\n                return [this.registerMainService()];\n            } catch (error: any) {\n                this.log.warn(error.message);\n            }\n        } else {\n            this.log.warn(this.device.definition.widgetName + ' not supported.');\n        }\n        return [];\n    }\n\n    protected onStatesChanged(states: Array<State>, init = false) {\n        states.forEach((state: State) => {\n            if (!init) {\n                this.debug(state.name + ' => ' + state.value);\n            }\n            if (typeof this.onStateChanged === 'function') {\n                this.onStateChanged(state.name, state.value);\n            }\n        });\n    }\n\n    // OLD\n    get isIdle() {\n        return !this.platform.client.hasExecution(this.executionId);\n    }\n\n    async cancelExecution() {\n        await this.platform.client.cancelExecution(this.executionId);\n    }\n\n    /**\n     * Abstract methods to be implemented\n     */\n\n    /**\n     * Build the main device service\n     * @return the main service\n     */\n    protected abstract registerMainService(): Service;\n\n    /**\n     * Triggered when device state change\n     * @param name State name\n     * @param value State value\n     */\n    protected abstract onStateChanged(name: string, value);\n}\n"
  },
  {
    "path": "src/Platform.ts",
    "content": "import { API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service } from 'homebridge';\n\nimport { PLATFORM_NAME, PLUGIN_NAME } from './settings';\nimport { Client, Execution, Action } from 'overkiz-client';\nimport Mapper from './Mapper';\nimport SceneMapper from './SceneMapper';\nimport { CustomCharacteristics } from './CustomCharacteristics';\nimport { BLUE, GREY, RESET } from './colors';\n\n\nexport let Services: typeof Service;\nexport let Characteristics: typeof Characteristic;\n\nconst DEFAULT_RETRY_DELAY = 60;\n\n/**\n * HomebridgePlatform\n * This class is the main constructor for your plugin, this is where you should\n * parse the user config and discover/register accessories with Homebridge.\n */\nexport class Platform implements DynamicPlatformPlugin {\n    // this is used to track restored cached accessories\n    private readonly accessories: PlatformAccessory[] = [];\n    public readonly client: Client;\n\n    private readonly exclude: Array<string>;\n    private readonly exposeScenarios: boolean | Array<string>;\n    public readonly devicesConfig: Array<unknown> = [];\n\n    private translations;\n    private executionPromise;\n    private retryDelay = DEFAULT_RETRY_DELAY;\n\n    constructor(public readonly log: Logger, public readonly config: PlatformConfig, public readonly api: API) {\n        Services = this.api.hap.Service;\n        Characteristics = this.api.hap.Characteristic;\n        new CustomCharacteristics(this.api.hap);\n        this.log.debug('Finished initializing platform:', this.config.name);\n\n        process.on('unhandledRejection', (error: any) => this.log.error(error));\n        process.on('uncaughtException', (error: any) => this.log.error(error));\n\n        this.exclude = config.exclude || [];\n        this.exclude.push('Pod', 'ConfigurationComponent', 'NetworkComponent', 'ProtocolGateway', 'ConsumptionSensor',\n            'OnOffHeatingSystem', 'Wifi', 'RemoteController',\n            // AtlanticElectricalTowelDryer bad sensors\n            'io:LightIOSystemDeviceSensor', 'io:RelativeHumidityIOSystemDeviceSensor', 'WeatherForecastSensor',\n        );\n        this.exposeScenarios = config.exposeScenarios;\n        config.devicesConfig?.forEach(x => this.devicesConfig[x.key] = x);\n\n        const logger = Object.assign({}, log, {\n            debug: (...args) => {\n                if (config['debug']) {\n                    log.info('\\x1b[90m', ...args)\n                } else {\n                    log.debug(args.shift(), ...args)\n                }\n            },\n        });\n        this.client = new Client(logger, config);\n\n        // When this event is fired it means Homebridge has restored all cached accessories from disk.\n        // Dynamic Platform plugins should only register new accessories after this event was fired,\n        // in order to ensure they weren't added to homebridge already. This event can also be used\n        // to start discovery of new accessories.\n        this.api.on('didFinishLaunching', () => {\n            log.debug('Executed didFinishLaunching callback');\n            // run the method to discover / register your devices as accessories\n            this.discoverDevices();\n            if (this.config['service'] !== 'local') {\n                this.loadLocation();\n            }\n        });\n    }\n\n    /**\n     * This function is invoked when homebridge restores cached accessories from disk at startup.\n     * It should be used to setup event handlers for characteristics and update respective values.\n     */\n    async loadLocation() {\n        let countryCode = 'en';\n        const location = await this.client.getSetupLocation().catch((error) => this.log.warn('Fail to load lang file:', error));\n        if (location?.countryCode) {\n            countryCode = location.countryCode.toLowerCase().trim();\n        }\n        this.translations = await import(`./lang/${countryCode}.json`)\n            .catch(() => import('./lang/en.json'))\n            .then((c) => c.default);\n\n    }\n\n    /**\n     * This function is invoked when homebridge restores cached accessories from disk at startup.\n     * It should be used to setup event handlers for characteristics and update respective values.\n     */\n    async configureAccessory(accessory: PlatformAccessory) {\n        if (!this.accessories.map((a) => a.UUID).includes(accessory.UUID)) {\n            this.accessories.push(accessory);\n        }\n    }\n\n    /**\n     * This is an example method showing how to register discovered accessories.\n     * Accessories must only be registered once, previously created accessories\n     * must not be registered again to prevent \"duplicate UUID\" errors.\n     */\n    async discoverDevices() {\n        try {\n            const uuids = Array<string>();\n            const devices = await this.client.getDevices();\n            this.log.debug(devices.length + ' devices discovered');\n\n            // loop over the discovered devices and register each one if it has not already been registered\n            for (const device of devices) {\n                if (\n                    this.exclude.includes(device.definition.uiClass) ||\n                    this.exclude.includes(device.definition.widgetName) ||\n                    this.exclude.includes(device.controllableName) ||\n                    this.exclude.includes(device.label) ||\n                    this.exclude.includes(device.protocol)\n                ) {\n                    continue;\n                }\n\n                // see if an accessory with the same uuid has already been registered and restored from\n                // the cached devices we stored in the `configureAccessory` method above\n                let accessory = this.accessories.find(accessory => accessory.UUID === device.uuid);\n\n                if (accessory) {\n                    // the accessory already exists\n                    //this.log.info('Updating accessory:', accessory.displayName);\n                    /*\n                    const newaccessory = new this.api.platformAccessory(device.label, device.uuid);\n                    newaccessory.context.device = device;\n                    await this.configureAccessory(newaccessory);\n                    const services = newaccessory.services.map((service) => service.UUID);\n                    accessory.services\n                        .filter((service) => !services.includes(service.UUID))\n                        .forEach((services) => accessory?.removeService(services));\n                    this.api.updatePlatformAccessories([accessory]);\n                    */\n                } else {\n                    // the accessory does not yet exist, so we need to create it\n                    this.log.info('Create accessory:', device.label);\n                    accessory = new this.api.platformAccessory(device.label, device.uuid);\n                    //accessory.context.device = device;\n                    await this.configureAccessory(accessory);\n                    this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);\n                }\n\n                this.log.info(`Configure device ${BLUE}${accessory.displayName}${RESET}`);\n                this.log.info(`${GREY}  ${device.definition.uiClass} > ${device.definition.widgetName}`);\n\n                const mapper = await import(`./mappers/${device.definition.uiClass}/${device.definition.widgetName}/${device.uniqueName}`)\n                    .catch(() => import(`./mappers/${device.definition.uiClass}/${device.definition.widgetName}`))\n                    .catch(() => import(`./mappers/${device.definition.uiClass}`))\n                    .then((c) => c.default)\n                    .catch(() => Mapper);\n                new mapper(this, accessory, device).build();\n\n                uuids.push(device.uuid);\n            }\n\n\n            if (this.exposeScenarios) {\n                const actionGroups = await this.client.getActionGroups();\n\n                for (const actionGroup of actionGroups) {\n                    if (this.exclude.includes(actionGroup.label) || actionGroup.label.startsWith('internal:') || actionGroup.label === '') {\n                        continue;\n                    }\n\n                    let accessory = this.accessories.find(accessory => accessory.UUID === actionGroup.oid);\n\n                    if (!accessory) {\n                        // the accessory does not yet exist, so we need to create it\n                        this.log.info('Create accessory', actionGroup.label);\n                        accessory = new this.api.platformAccessory(actionGroup.label, actionGroup.oid);\n                        await this.configureAccessory(accessory);\n                        this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);\n                    }\n\n                    this.log.info('Map scene', accessory.displayName);\n\n                    new SceneMapper(this, accessory, actionGroup);\n                    uuids.push(actionGroup.oid);\n                }\n            }\n\n            const deleted = this.accessories.filter((accessory) => !uuids.includes(accessory.UUID));\n            this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, deleted);\n            this.retryDelay = DEFAULT_RETRY_DELAY;\n        } catch (error: any) {\n            this.log.error(error);\n            this.log.error('Retry in ' + this.retryDelay + ' sec...');\n            setTimeout(this.discoverDevices.bind(this), this.retryDelay * 1000);\n            this.retryDelay *= 2;\n        }\n    }\n\n    /*\n        action: The action to execute\n    */\n    public executeAction(label: string, action: Action, highPriority = false, standalone = false) {\n        if (standalone) {\n            // Run action in standalone execution\n            return this.client.execute(highPriority ? 'apply/highPriority' : 'apply', new Execution(label + ' - HomeKit', action));\n        } else {\n            if (this.executionPromise) {\n                this.executionPromise.execution.addAction(action);\n                this.executionPromise.execution.label = 'Execute scene (' +\n                    this.executionPromise.execution.actions.length + ' devices) - HomeKit';\n            } else {\n                this.executionPromise = new Promise((resolve, reject) => {\n                    setTimeout(() => {\n                        this.client.execute(highPriority ? 'apply/highPriority' : 'apply', this.executionPromise.execution)\n                            .then(resolve)\n                            .catch(reject);\n                        this.executionPromise = null;\n                    }, 100);\n                });\n                this.executionPromise.execution = new Execution(label + ' - HomeKit', action);\n            }\n            return this.executionPromise;\n        }\n    }\n\n    /**\n     * Translate\n     * @param path \n     * @returns string\n     */\n    public translate(label: string): string | null {\n        const path = label.split('.');\n        let translation = this.translations;\n        for (const key of path) {\n            if (typeof translation === 'object' && key in translation) {\n                translation = translation[key];\n            } else if (typeof translation === 'string') {\n                if (translation.includes(':param')) {\n                    translation = translation.replace(':param', key);\n                }\n                return translation;\n            }\n        }\n        return label;\n    }\n}\n"
  },
  {
    "path": "src/SceneMapper.ts",
    "content": "import { Characteristics, Services } from './Platform';\nimport { Characteristic, Logger, PlatformAccessory, Service } from 'homebridge';\nimport { ExecutionState, ActionGroup, Execution } from 'overkiz-client';\nimport { Platform } from './Platform';\n\nexport default class Mapper {\n    protected log: Logger;\n    protected services: Array<Service> = [];\n    protected on: Characteristic | undefined;\n    private lastExecId;\n\n    constructor(\n        protected readonly platform: Platform,\n        protected readonly accessory: PlatformAccessory,\n        protected readonly action: ActionGroup,\n    ) {\n        this.log = platform.log;\n\n        const service = this.accessory.getService(Services.Switch) || this.accessory.addService(Services.Switch);\n        this.on = service.getCharacteristic(Characteristics.On);\n\n        this.on.onSet(this.setOn.bind(this));\n        this.on.updateValue(0);\n    }\n\n    private get isInProgress() {\n        return this.platform.client.hasExecution(this.lastExecId);\n    }\n\n    protected async setOn(value) {\n        if (value) {\n            const execution = new Execution('');\n            this.lastExecId = await this.platform.client.execute(this.action.oid, execution);\n            execution.on('update', (state, event) => {\n                switch (state) {\n                    case ExecutionState.COMPLETED:\n                    case ExecutionState.FAILED:\n                        this.log.info('[Scene] ' + this.action.label + ' ' + (state === ExecutionState.FAILED ? event.failureType : state));\n                        this.on?.updateValue(0);\n                        break;\n                }\n            });\n        } else if (this.isInProgress) {\n            await this.platform.client.cancelExecution(this.lastExecId);\n        }\n    }\n}"
  },
  {
    "path": "src/colors.ts",
    "content": "export const RESET = '\\x1b[0m';\nexport const BRIGHT = '\\x1b[1m';\nexport const DIM = '\\x1b[2m';\nexport const UNDERSCORE = '\\x1b[4m';\nexport const BLINK = '\\x1b[5m';\nexport const REVERSE = '\\x1b[7m';\nexport const HIDDEN = '\\x1b[8m';\n\nexport const BLACK = '\\x1b[30m';\nexport const RED = '\\x1b[31m';\nexport const GREEN = '\\x1b[32m';\nexport const YELLOW = '\\x1b[33m';\nexport const BLUE = '\\x1b[34m';\nexport const MAGENTA = '\\x1b[35m';\nexport const CYAN = '\\x1b[36m';\nexport const LIGHT_GREY = '\\x1b[37m';\nexport const GREY = '\\x1b[90m';\nexport const WHITE = '\\x1b[97m';"
  },
  {
    "path": "src/index.ts",
    "content": "import { API } from 'homebridge';\n\nimport { PLATFORM_NAME } from './settings';\nimport { Platform } from './Platform';\n\n/**\n * This method registers the platform with Homebridge\n */\nexport = (api: API) => {\n    api.registerPlatform(PLATFORM_NAME, Platform);\n}"
  },
  {
    "path": "src/lang/en.json",
    "content": "{\n    \"others\": \":param other(s)\",\n    \"setClosure\": \"Close :param%\",\n    \"setHeatingLevel\": {\n        \"comfort\": \"Comfort mode\",\n        \"eco\": \"Eco mode\",\n        \"frostprotection\": \"Frost protection mode\",\n        \"off\": \"Stop\"\n    },\n    \"open\": \"Open\",\n    \"close\": \"Close\",\n    \"setPedestrianPosition\": \"Pedestrian position\",\n    \"partialPosition\": \"Partial position\"\n}"
  },
  {
    "path": "src/lang/fr.json",
    "content": "{\n    \"others\": \":param autre(s)\",\n    \"setClosure\": \"Fermeture :param%\",\n    \"setHeatingLevel\": {\n        \"comfort\": \"Mode confort\",\n        \"eco\": \"Mode eco\",\n        \"frostprotection\": \"Mode hors gel\",\n        \"off\": \"Arrêt\"\n    },\n    \"open\": \"Ouverture\",\n    \"close\": \"Fermeture\",\n    \"setPedestrianPosition\": \"Ouverture piéton\",\n    \"partialPosition\": \"Ouverture partielle\"\n}"
  },
  {
    "path": "src/mappers/AdjustableSlatsRollerShutter.ts",
    "content": "import { Command } from 'overkiz-client';\nimport VenetianBlind from './VenetianBlind';\n\nexport default class AdjustableSlatsRollerShutter extends VenetianBlind {\n\n    protected getTargetCommands(value) {\n        if(this.blindMode) {\n            if(value === 100) {\n                return new Command('setClosure', 0);\n            } else {\n                return new Command('setClosureOrOrientation', [100, this.reversedValue(value)]);\n            }\n        } else {\n            return new Command('setClosureOrOrientation', [\n                this.reversedValue(value),\n                this.angleToOrientation(this.targetAngle?.value),\n            ]);\n        }\n    }\n\n    protected getTargetAngleCommands(value) {\n        return new Command('setClosureOrOrientation', [\n            this.reversedValue(this.targetPosition?.value),\n            this.angleToOrientation(value),\n        ]);\n    }\n\n    protected onStateChanged(name, value) {\n        super.onStateChanged(name, value);\n\n        switch(name) {\n            case 'core:ClosureOrRockerPositionState':\n                this.currentPosition?.updateValue(this.reversedValue(value));\n                if(!this.device.hasState('core:TargetClosureState')) {\n                    this.targetPosition?.updateValue(this.reversedValue(value));\n                }\n                break;\n            default: break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/AirSensor/CO2Sensor.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Characteristic } from 'homebridge';\nimport AirSensor from '../AirSensor';\n\nexport default class RelativeHumiditySensor extends AirSensor {\n    protected co2: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = super.registerMainService();\n        service.addOptionalCharacteristic(Characteristics.CarbonDioxideLevel);\n        this.co2 = service.getCharacteristic(Characteristics.CarbonDioxideLevel);\n        return service;\n    }\n\n    \n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:CO2ConcentrationState':\n                this.co2?.updateValue(value);\n                this.quality?.updateValue(this.co2ToQuality(value));\n                break;\n        }\n    }\n\n    private co2ToQuality(value) {\n        if (value < 350) {\n            return Characteristics.AirQuality.EXCELLENT;\n        } else if (value < 1000) {\n            return Characteristics.AirQuality.GOOD;\n        } else if (value < 2000) {\n            return Characteristics.AirQuality.FAIR;\n        } else if (value < 5000) {\n            return Characteristics.AirQuality.INFERIOR;\n        } else {\n            return Characteristics.AirQuality.POOR;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/AirSensor/RelativeHumiditySensor.ts",
    "content": "import HumiditySensor from '../HumiditySensor';\n\nexport default class RelativeHumiditySensor extends HumiditySensor {\n\n}"
  },
  {
    "path": "src/mappers/AirSensor.ts",
    "content": "\nimport { Characteristics, Services } from '../Platform';\nimport { Characteristic, Service } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class AirSensor extends Mapper {\n    protected quality: Characteristic | undefined;\n\n    protected registerMainService(): Service {\n        const service = this.registerService(Services.AirQualitySensor);\n        this.quality = service.getCharacteristic(Characteristics.AirQuality);\n        return service;\n    }\n    \n    protected onStateChanged(name: string, value) {\n        switch(name) {\n            default: this.quality?.updateValue(value);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Alarm/MyFoxAlarmController.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport Alarm from '../Alarm';\n\nexport default class MyFoxAlarmController extends Alarm {\n    protected getTargetCommands(value): Command | Array<Command> {\n        switch(value) {\n            default:\n            case Characteristics.SecuritySystemTargetState.STAY_ARM:\n                return [];\n            case Characteristics.SecuritySystemTargetState.NIGHT_ARM:\n                return new Command('partial');\n            case Characteristics.SecuritySystemTargetState.AWAY_ARM:\n                return new Command('arm');\n            case Characteristics.SecuritySystemTargetState.DISARM:\n                return new Command('disarm');\n        }\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch(name) {\n            case 'myfox:AlarmStatusState':\n                switch(value) {\n                    default:\n                    case 'disarmed': \n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.DISARMED);\n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.DISARM);\n                        break;\n                    case 'armed': \n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.AWAY_ARM);\n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.AWAY_ARM);\n                        break;\n                    case 'partial': \n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.NIGHT_ARM);\n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.NIGHT_ARM);\n                        break;\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Alarm/TSKAlarmController.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport Alarm from '../Alarm';\n\nexport default class TSKAlarmController extends Alarm {\n    protected getTargetCommands(value): Command | Array<Command> {\n        switch(value) {\n            default:\n            case Characteristics.SecuritySystemTargetState.STAY_ARM:\n                return new Command('alarmPartial1');\n            case Characteristics.SecuritySystemTargetState.NIGHT_ARM:\n                return new Command('alarmPartial2');\n            case Characteristics.SecuritySystemTargetState.AWAY_ARM:\n                return new Command('alarmOn');\n            case Characteristics.SecuritySystemTargetState.DISARM:\n                return new Command('alarmOff');\n        }\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch(name) {\n            case 'internal:CurrentAlarmModeState':\n                switch(value) {\n                    default:\n                    case 'off': \n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.DISARMED);\n                        break;\n                    case 'partial1':\n                    case 'zone1': \n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.STAY_ARM);\n                        break;\n                    case 'total': \n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.AWAY_ARM);\n                        break;\n                    case 'partial2':\n                    case 'zone2': \n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.NIGHT_ARM);\n                        break;\n                }\n                break;\n            \n            case 'internal:TargetAlarmModeState':\n                switch(value) {\n                    default:\n                    case 'off': \n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.DISARM);\n                        break;\n                    case 'partial1':\n                    case 'zone1': \n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.STAY_ARM);\n                        break;\n                    case 'total': \n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.AWAY_ARM);\n                        break;\n                    case 'partial2':\n                    case 'zone2': \n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.NIGHT_ARM);\n                        break;\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Alarm.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, CharacteristicSetCallback } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Mapper from '../Mapper';\n\nexport default class Alarm extends Mapper {\n    protected currentState: Characteristic | undefined;\n    protected targetState: Characteristic | undefined;\n\n    protected stayZones: unknown | undefined;\n    protected nightZones: unknown | undefined;\n    protected occupancySensor: unknown | undefined;\n\n    protected applyConfig(config) {\n        this.stayZones = config.stayZones || 'A';\n        this.nightZones = config.nightZones || 'B';\n        this.occupancySensor = config.occupancySensor || false;\n    }\n\n\n    protected registerMainService() {\n        const service = this.registerService(Services.SecuritySystem);\n        this.currentState = service.getCharacteristic(Characteristics.SecuritySystemCurrentState);\n        this.targetState = service.getCharacteristic(Characteristics.SecuritySystemTargetState);\n\n        this.targetState.onSet(this.setTargetState.bind(this));\n        return service;\n    }\n\n    protected getTargetCommands(value): Command | Array<Command> {\n        switch (value) {\n            default:\n            case Characteristics.SecuritySystemTargetState.STAY_ARM:\n                return new Command('alarmZoneOn', [this.stayZones]);\n            case Characteristics.SecuritySystemTargetState.NIGHT_ARM:\n                return new Command('alarmZoneOn', [this.nightZones]);\n            case Characteristics.SecuritySystemTargetState.AWAY_ARM:\n                return new Command('alarmOn');\n            case Characteristics.SecuritySystemTargetState.DISARM:\n                return new Command('alarmOff');\n        }\n    }\n\n    async setTargetState(value) {\n        const action = await this.executeCommands(this.getTargetCommands(value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    if (this.stateless) {\n                        this.currentState?.updateValue(value);\n                    }\n                    break;\n                case ExecutionState.FAILED:\n                    if (this.currentState &&\n                        this.currentState.value !== Characteristics.SecuritySystemCurrentState.ALARM_TRIGGERED) {\n                        this.targetState?.updateValue(this.currentState.value);\n                    }\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:ActiveZonesState':\n                switch (value) {\n                    default:\n                    case '':\n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.DISARMED);\n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.DISARM);\n                        break;\n                    case this.stayZones:\n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.STAY_ARM);\n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.STAY_ARM);\n                        break;\n                    case 'A,B,C':\n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.AWAY_ARM);\n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.AWAY_ARM);\n                        break;\n                    case this.nightZones:\n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.NIGHT_ARM);\n                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.NIGHT_ARM);\n                        break;\n                    case 'triggered':\n                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.ALARM_TRIGGERED);\n                        break;\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Awning/PositionableHorizontalAwningUno.ts",
    "content": "import Awning from '../Awning';\nexport default class PositionableHorizontalAwningUno extends Awning {\n    protected onStateChanged(name: string, value) {\n        switch(name) {\n            case 'core:TargetClosureState':\n                if(this.isIdle) {\n                    this.targetPosition?.updateValue(this.reversedValue(value));\n                }\n                this.currentPosition?.updateValue(this.reversedValue(value));\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Awning.ts",
    "content": "import RollerShutter from './RollerShutter';\nimport { Command } from 'overkiz-client';\n\nexport default class Awning extends RollerShutter {\n    /**\n\t* Triggered when Homekit try to modify the Characteristic.TargetPosition\n\t* HomeKit '0' (Close) => 0% Deployment\n\t* HomeKit '100' (Open) => 100% Deployment\n\t**/\n    protected getTargetCommands(value) {\n        if(this.stateless) {\n            if(value === 100) {\n                return new Command(this.reverse ? 'undeploy' : 'deploy');\n            } else if(value === 0) {\n                return new Command(this.reverse ? 'deploy' : 'undeploy');\n            } else {\n                if(this.movementDuration > 0) {\n                    const delta = value - Number(this.currentPosition!.value);\n                    if(this.reverse) {\n                        return new Command(delta > 0 ? 'undeploy' : 'deploy');\n                    } else {\n                        return new Command(delta > 0 ? 'deploy' : 'undeploy');\n                    }\n                } else {\n                    return new Command('my');\n                }\n            }\n        } else {\n            return new Command('setDeployment', this.reversedValue(value));\n        }\n    }\n\n    protected reversedValue(value) {\n        return this.reverse ? (100-value) : value;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch(name) {\n            case 'core:DeploymentState':\n                this.currentPosition?.updateValue(this.reversedValue(value));\n                if(!this.device.hasState('core:TargetClosureState')) {\n                    this.targetPosition?.updateValue(this.reversedValue(value));\n                }\n                break;\n            case 'core:ClosureState':\n                this.currentPosition?.updateValue(this.reversedValue(value));\n                if(!this.device.hasState('core:TargetClosureState')) {\n                    this.targetPosition?.updateValue(this.reversedValue(value));\n                }\n                break;\n            case 'core:TargetClosureState':\n                this.targetPosition?.updateValue(this.reversedValue(value));\n                if(!this.device.hasState('core:ClosureState')) {\n                    this.currentPosition?.updateValue(this.reversedValue(value));\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/ConsumptionSensor.ts",
    "content": "import { Service } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class ConsumptionSensor extends Mapper {\n    protected registerMainService(): Service {\n        throw new Error('ConsumptionSensor not implemented.');\n    }\n\n    protected onStateChanged(name: string, value: any) {\n        this.info(name + ' => ' + value);\n    }\n}"
  },
  {
    "path": "src/mappers/ContactSensor.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class ContactSensor extends Mapper {\n    protected state: Characteristic | undefined;\n    protected fault: Characteristic | undefined;\n    protected battery: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.ContactSensor);\n        this.state = service.getCharacteristic(Characteristics.ContactSensorState);\n        if (this.device.hasState('core:SensorDefectState')) {\n            this.fault = service.getCharacteristic(Characteristics.StatusFault);\n            this.battery = service.getCharacteristic(Characteristics.StatusLowBattery);\n        }\n        return service;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:ContactState':\n                switch (value) {\n                    case 'closed':\n                        this.state?.updateValue(Characteristics.ContactSensorState.CONTACT_DETECTED);\n                        break;\n                    case 'tilt':\n                    case 'open':\n                        this.state?.updateValue(Characteristics.ContactSensorState.CONTACT_NOT_DETECTED);\n                        break;\n                }\n                break;\n            case 'core:SensorDefectState':\n                switch (value) {\n                    case 'lowBattery':\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_LOW);\n                        break;\n                    case 'maintenanceRequired':\n                    case 'dead':\n                        this.fault?.updateValue(Characteristics.StatusFault.GENERAL_FAULT);\n                        break;\n                    case 'noDefect':\n                        this.fault?.updateValue(Characteristics.StatusFault.NO_FAULT);\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_NORMAL);\n                        break;\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Curtain.ts",
    "content": "import RollerShutter from './RollerShutter';\n\nexport default class Curtain extends RollerShutter {\n}"
  },
  {
    "path": "src/mappers/DoorLock.ts",
    "content": "\nimport { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Mapper from '../Mapper';\n\nexport default class VentilationSystem extends Mapper {\n    protected currentState: Characteristic | undefined;\n    protected targetState: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.LockMechanism);\n        this.currentState = service.getCharacteristic(Characteristics.LockCurrentState);\n        this.targetState = service.getCharacteristic(Characteristics.LockTargetState);\n\n        this.targetState?.onSet(this.setTargetState.bind(this));\n        return service;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        switch (value) {\n            case Characteristics.LockTargetState.SECURED:\n                return new Command('setLockedUnlocked', 'locked');\n            case Characteristics.LockTargetState.UNSECURED:\n            default:\n                return new Command('setLockedUnlocked', 'unlocked');\n        }\n    }\n\n    protected async setTargetState(value) {\n        const action = await this.executeCommands(this.getTargetStateCommands(value));\n        action.on('update', (state) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    if (this.stateless) {\n                        this.currentState?.updateValue(value);\n                    }\n                    break;\n                case ExecutionState.FAILED:\n                    if (this.currentState) {\n                        this.targetState?.updateValue(this.currentState.value);\n                    }\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:LockedUnlockedState':\n                switch (value) {\n                    case 'locked':\n                        this.currentState?.updateValue(Characteristics.LockCurrentState.SECURED);\n                        break;\n                    default:\n                        this.currentState?.updateValue(Characteristics.LockCurrentState.UNSECURED);\n                        break;\n                }\n                if (this.isIdle && this.currentState) {\n                    this.targetState?.updateValue(this.currentState.value);\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/ElectricitySensor/CumulativeElectricPowerConsumptionSensor.ts",
    "content": "import { Services } from '../../Platform';\nimport { Characteristic } from 'homebridge';\nimport { CurrentConsumptionCharacteristic, TotalConsumptionCharacteristic } from '../../CustomCharacteristics';\nimport ElectricitySensor from '../ElectricitySensor';\n\n\nexport default class CumulativeElectricPowerConsumptionSensor extends ElectricitySensor {\n\n    protected consumption: Characteristic | undefined;\n    protected power: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = super.registerMainService();\n        service.addOptionalCharacteristic(TotalConsumptionCharacteristic);\n        this.consumption = service.getCharacteristic(TotalConsumptionCharacteristic);\n        service.addOptionalCharacteristic(CurrentConsumptionCharacteristic);\n        this.power = service.getCharacteristic(CurrentConsumptionCharacteristic);\n        return service;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:ElectricEnergyConsumptionState':\n                this.consumption?.updateValue(value / 1000);\n                break;\n            case 'core:ElectricPowerConsumptionState':\n                this.power?.updateValue(value);\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/ElectricitySensor.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Service } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class ElectricitySensor extends Mapper {\n    protected registerMainService(): Service {\n        const service = this.registerService(Services.AccessoryMetrics);\n        return service;\n    }\n\n    protected onStateChanged(name: string, value: any) {\n        this.info(name + ' => ' + value);\n    }\n}"
  },
  {
    "path": "src/mappers/EvoHome/DHWSetPoint.ts",
    "content": "import HeatingSystem from '../HeatingSystem';\nimport TemperatureSensor from '../TemperatureSensor';\n\nexport default class DHWSetPoint extends TemperatureSensor {\n\n}"
  },
  {
    "path": "src/mappers/EvoHome/EvoHomeController.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class EvoHomeController extends HeatingSystem {\n    protected registerMainService() {\n        const service = super.registerMainService();\n        this.targetState?.setProps({ validValues: [\n            Characteristics.TargetHeatingCoolingState.AUTO,\n            Characteristics.TargetHeatingCoolingState.OFF,\n        ] });\n        return service;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> | undefined {\n        switch(value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                return new Command('setOperatingMode', 'auto');\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return new Command('setOperatingMode', 'off');\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/EvoHome/HeatingSetPoint.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class HeatingSetPoint extends HeatingSystem {\n    protected registerMainService() {\n        const service = super.registerMainService();\n        this.targetState?.setProps({ validValues: [\n            Characteristics.TargetHeatingCoolingState.AUTO,\n        ] });\n        this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);\n        return service;\n    }\n}"
  },
  {
    "path": "src/mappers/ExteriorHeatingSystem/DimmerExteriorHeating.ts",
    "content": "import { Characteristic, Service } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport { Characteristics, Services } from '../../Platform';\nimport ExteriorHeatingSystem from '../ExteriorHeatingSystem';\n\nexport default class DimmerExteriorHeating extends ExteriorHeatingSystem {\n    protected level: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.Lightbulb);\n        this.on = service.getCharacteristic(Characteristics.On);\n\n        this.on.onSet(this.setOn.bind(this));\n\n        this.level = service.getCharacteristic(Characteristics.Brightness);\n        this.level.onSet(this.debounce(this.setBrightness, [0, 100]));\n        return service;\n    }\n\n    protected async setBrightness(value) {\n        const action = await this.executeCommands(new Command('setLevel', 100 - value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    break;\n                case ExecutionState.FAILED:\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value): boolean {\n        value = 100 - value;\n        switch (name) {\n            case 'core:LevelState':\n                this.level?.updateValue(value);\n                this.on?.updateValue(value === 0 ? 0 : 1);\n                break;\n        }\n        return false;\n    }\n}"
  },
  {
    "path": "src/mappers/ExteriorHeatingSystem.ts",
    "content": "import { Command } from 'overkiz-client';\nimport HeatingSystem from './HeatingSystem';\n\nexport default class ExteriorHeatingSystem extends HeatingSystem {\n    protected registerMainService() {\n        return this.registerSwitchService();\n    }\n\n    protected getOnCommands(value): Command | Array<Command> {\n        return new Command(value ? 'on' : 'off');\n    }\n}"
  },
  {
    "path": "src/mappers/ExteriorScreen.ts",
    "content": "import RollerShutter from './RollerShutter';\n\nexport default class ExteriorScreen extends RollerShutter {\n\n}"
  },
  {
    "path": "src/mappers/ExteriorVenetianBlind.ts",
    "content": "import VenetianBlind from './VenetianBlind';\n\nexport default class ExteriorVenetianBlind extends VenetianBlind {\n    \n}"
  },
  {
    "path": "src/mappers/GarageDoor.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Mapper from '../Mapper';\n\nexport default class GarageDoor extends Mapper {\n    protected expectedStates = ['core:OpenClosedPartialState', 'core:OpenClosedUnknownState', 'core:OpenClosedState'];\n    protected currentState: Characteristic | undefined;\n    protected targetState: Characteristic | undefined;\n\n    protected cyclic;\n    protected cycleDuration;\n\n    protected applyConfig(config) {\n        this.cyclic = config['cyclic'] || false;\n        this.cycleDuration = (config['cycleDuration'] || 5) * 1000;\n    }\n\n    protected registerMainService() {\n        const service = this.registerService(Services.GarageDoorOpener);\n        this.currentState = service.getCharacteristic(Characteristics.CurrentDoorState);\n        this.targetState = service.getCharacteristic(Characteristics.TargetDoorState);\n        this.targetState.onSet(this.setTargetState.bind(this));\n\n        this.cyclic = this.cyclic || this.device.hasCommand('cycle');\n        if (this.stateless) {\n            this.currentState.updateValue(Characteristics.CurrentDoorState.CLOSED);\n            this.targetState.updateValue(Characteristics.TargetDoorState.CLOSED);\n        }\n        return service;\n    }\n\n    protected getTargetCommands(value) {\n        if (this.device.hasCommand('cycle')) {\n            return new Command('cycle');\n        } else {\n            return new Command(value ? 'close' : 'open');\n        }\n    }\n\n    protected async setTargetState(value) {\n        const previousTarget = this.targetState?.value;\n        const action = await this.executeCommands(this.getTargetCommands(value));\n        action.on('update', (state) => {\n            switch (state) {\n                case ExecutionState.IN_PROGRESS:\n                    if (value === Characteristics.TargetDoorState.OPEN) {\n                        this.currentState?.updateValue(Characteristics.CurrentDoorState.OPENING);\n                    } else {\n                        this.currentState?.updateValue(Characteristics.CurrentDoorState.CLOSING);\n                    }\n                    break;\n                case ExecutionState.COMPLETED:\n                    if (this.stateless) {\n                        this.onStateChanged(\n                            this.expectedStates[0],\n                            value === Characteristics.TargetDoorState.CLOSED ? 'closed' : 'open',\n                        );\n                        if (this.cyclic) {\n                            setTimeout(() => {\n                                this.onStateChanged(this.expectedStates[0], 'closed');\n                            }, this.cycleDuration);\n                        }\n                    } else if (this.cyclic) {\n                        this.requestStatesUpdate(60).catch((e) => this.warn(e));\n                    }\n                    break;\n                case ExecutionState.FAILED:\n                    if (previousTarget) {\n                        this.targetState?.updateValue(previousTarget);\n                    }\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value) {\n        let targetState;\n        if (this.expectedStates.includes(name)) {\n            switch (value) {\n                case 'open':\n                    this.currentState?.updateValue(Characteristics.CurrentDoorState.OPEN);\n                    targetState = Characteristics.TargetDoorState.OPEN;\n                    break;\n                case 'partial':\n                    this.currentState?.updateValue(Characteristics.CurrentDoorState.STOPPED);\n                    targetState = Characteristics.TargetDoorState.OPEN;\n                    break;\n                case 'closed':\n                    this.currentState?.updateValue(Characteristics.CurrentDoorState.CLOSED);\n                    targetState = Characteristics.TargetDoorState.CLOSED;\n                    break;\n                case 'unknown':\n                    break;\n            }\n        }\n\n        if (this.targetState && targetState !== undefined) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Gate.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, Service } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport GarageDoor from './GarageDoor';\n\nexport default class Gate extends GarageDoor {\n    protected expectedStates = ['core:OpenClosedPedestrianState'];\n\n    protected currentPedestrian: Characteristic | undefined;\n    protected targetPedestrian: Characteristic | undefined;\n    protected on: Characteristic | undefined;\n\n    protected pedestrianCommand;\n    protected pedestrianDuration;\n\n    protected cancelTimeout;\n\n    protected applyConfig(config) {\n        super.applyConfig(config);\n        this.pedestrianDuration = (config['pedestrianDuration'] || 0) * 1000;\n        this.pedestrianCommand = ['setPedestrianPosition', 'partialPosition', 'my']\n            .find((command: string) => this.device.hasCommand(command));\n    }\n\n    protected registerServices() {\n        const services = super.registerServices();\n        if (this.pedestrianCommand || this.pedestrianDuration) {\n            const pedestrian = this.registerLockService('pedestrian');\n            services.push(pedestrian);\n        }\n        if (this.stateless) {\n            this.currentPedestrian?.updateValue(Characteristics.LockCurrentState.SECURED);\n            this.targetPedestrian?.updateValue(Characteristics.LockCurrentState.SECURED);\n        }\n        return services;\n    }\n\n    protected registerSwitchService(subtype?: string): Service {\n        const service = this.registerService(Services.Switch, subtype);\n        this.on = service.getCharacteristic(Characteristics.On);\n\n        this.on?.onSet(this.setOn.bind(this));\n        return service;\n    }\n\n    protected registerLockService(subtype?: string): Service {\n        const service = this.registerService(Services.LockMechanism, subtype);\n        this.currentPedestrian = service.getCharacteristic(Characteristics.LockCurrentState);\n        this.targetPedestrian = service.getCharacteristic(Characteristics.LockTargetState);\n\n        this.targetPedestrian?.onSet(this.setLock.bind(this));\n        return service;\n    }\n\n    protected getLockCommands(value): Command | Array<Command> {\n        if (value === Characteristics.LockTargetState.UNSECURED && this.pedestrianCommand) {\n            return new Command(this.pedestrianCommand);\n        } else {\n            return new Command(value === Characteristics.LockTargetState.UNSECURED ? 'open' : 'close');\n        }\n    }\n\n    protected async setLock(value) {\n        if (this.cancelTimeout !== null) {\n            clearTimeout(this.cancelTimeout);\n        }\n        const action = await this.executeCommands(this.getLockCommands(value));\n        action.on('update', (state) => {\n            switch (state) {\n                case ExecutionState.IN_PROGRESS:\n                    if (this.stateless && !this.pedestrianCommand && this.pedestrianDuration) {\n                        this.info('Will stop movement in ' + this.pedestrianDuration + ' millisec');\n                        this.cancelTimeout = setTimeout(() => {\n                            this.cancelTimeout = null;\n                            if (this.isIdle) {\n                                this.executeCommands(new Command('stop'), true);\n                            } else {\n                                this.cancelExecution().catch(this.error.bind(this));\n                            }\n                        }, this.pedestrianDuration);\n                    }\n                    break;\n                case ExecutionState.COMPLETED:\n                    if (this.stateless) {\n                        this.onStateChanged(\n                            'core:OpenClosedPedestrianState',\n                            value === Characteristics.LockTargetState.SECURED ? 'closed' : 'pedestrian',\n                        );\n                        if (this.cyclic) {\n                            setTimeout(() => {\n                                this.onStateChanged('core:OpenClosedPedestrianState', 'closed');\n                            }, this.cycleDuration);\n                        }\n                    }\n                    break;\n            }\n        });\n    }\n\n    protected getOnCommands(value): Command | Array<Command> {\n        if (value && this.pedestrianCommand) {\n            return new Command(this.pedestrianCommand);\n        } else {\n            return new Command(value ? 'open' : 'close');\n        }\n    }\n\n    protected async setOn(value) {\n        const action = await this.executeCommands(this.getOnCommands(value));\n        action.on('update', (state) => {\n            switch (state) {\n                case ExecutionState.FAILED:\n                    this.on?.updateValue(!value);\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value) {\n        let targetState;\n        let targetPedestrian;\n        if (this.expectedStates.includes(name)) {\n            switch (value) {\n                case 'unknown':\n                case 'open':\n                    this.on?.updateValue(false);\n                    this.currentState?.updateValue(Characteristics.CurrentDoorState.OPEN);\n                    targetState = Characteristics.TargetDoorState.OPEN;\n                    this.currentPedestrian?.updateValue(Characteristics.LockCurrentState.UNKNOWN);\n                    targetPedestrian = Characteristics.LockTargetState.UNSECURED;\n                    break;\n                case 'pedestrian':\n                case 'partial':\n                    this.on?.updateValue(true);\n                    this.currentState?.updateValue(Characteristics.CurrentDoorState.STOPPED);\n                    targetState = Characteristics.TargetDoorState.OPEN;\n                    this.currentPedestrian?.updateValue(Characteristics.LockCurrentState.UNSECURED);\n                    targetPedestrian = Characteristics.LockTargetState.UNSECURED;\n                    break;\n                case 'closed':\n                    this.on?.updateValue(false);\n                    this.currentState?.updateValue(Characteristics.CurrentDoorState.CLOSED);\n                    targetState = Characteristics.TargetDoorState.CLOSED;\n                    this.currentPedestrian?.updateValue(Characteristics.LockCurrentState.SECURED);\n                    targetPedestrian = Characteristics.LockTargetState.SECURED;\n                    break;\n            }\n        }\n\n        if (this.targetState && targetState !== undefined) {\n            this.targetState.updateValue(targetState);\n        }\n        if (this.targetPedestrian && targetPedestrian !== undefined) {\n            this.targetPedestrian.updateValue(targetPedestrian);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Generic/CyclicGeneric.ts",
    "content": "import GarageDoor from '../GarageDoor';\n\nexport default class CyclicGeneric extends GarageDoor {\n\n}"
  },
  {
    "path": "src/mappers/Generic/DimmerOnOff.ts",
    "content": "import Light from '../Light';\n\nexport default class DimmerOnOff extends Light {\n\n}"
  },
  {
    "path": "src/mappers/Generic/RTSGeneric.ts",
    "content": "import { Command } from 'overkiz-client';\nimport RollerShutter from '../RollerShutter';\n\nexport default class RTSGeneric extends RollerShutter {\n    protected getTargetCommands(value) {\n        if(value === 0) {\n            return new Command('down');\n        } else {\n            return new Command('up');\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Generic/RTSGeneric4T.ts",
    "content": "import GarageDoor from '../GarageDoor';\n\nexport default class RTSGeneric extends GarageDoor {\n\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticElectricalHeater.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Perms } from 'homebridge';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nconst FROSTPROTECTION_TEMP = 7;\n\nexport default class AtlanticElectricalHeater extends HeatingSystem {\n    protected THERMOSTAT_CHARACTERISTICS = ['eco'];\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.HEAT,\n        Characteristics.TargetHeatingCoolingState.COOL,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected registerMainService() {\n        const service = super.registerMainService();\n        this.targetTemperature?.setProps({\n            minValue: FROSTPROTECTION_TEMP,\n            maxValue: this.comfortTemperature,\n            minStep: 1,\n            perms: [Perms.PAIRED_READ, Perms.EVENTS, Perms.PAIRED_WRITE],\n        });\n        return service;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                return new Command('setHeatingLevel', this?.eco?.value ? 'eco' : 'comfort');\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                return new Command('setHeatingLevel', 'comfort');\n            case Characteristics.TargetHeatingCoolingState.COOL:\n                return new Command('setHeatingLevel', 'eco');\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return new Command('setHeatingLevel', 'off');\n        }\n        return [];\n    }\n\n    protected async setTargetTemperature(value) {\n        if (this.targetState?.value === Characteristics.CurrentHeatingCoolingState.OFF) {\n            return;\n        }\n        const frostEcoLimit = FROSTPROTECTION_TEMP + (this.ecoTemperature - FROSTPROTECTION_TEMP) / 2;\n        const ecoComfortLimit = this.ecoTemperature + (this.comfortTemperature - this.ecoTemperature) / 2;\n        let newValue = value;\n        if (value <= frostEcoLimit) {\n            newValue = FROSTPROTECTION_TEMP;\n        } else if (value > frostEcoLimit && value <= this.ecoTemperature) {\n            newValue = this.ecoTemperature;\n        } else if (value > this.ecoTemperature && value <= ecoComfortLimit) {\n            newValue = this.comfortTemperature;\n        }\n        if (newValue !== value) {\n            this.targetTemperature?.updateValue(newValue);\n        }\n        await this.executeCommands(this.getTargetTemperatureCommands(newValue));\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {\n        if (value === FROSTPROTECTION_TEMP) {\n            this.targetState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n            return new Command('setHeatingLevel', 'frostprotection');\n        } else if (value === this.ecoTemperature) {\n            this.targetState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n            return new Command('setHeatingLevel', 'eco');\n        } else if (value === this.comfortTemperature) {\n            this.targetState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n            return new Command('setHeatingLevel', 'comfort');\n        }\n    }\n\n    protected getProgCommands(): Command | Array<Command> | undefined {\n        return new Command('setHeatingLevel', this?.eco?.value ? 'eco' : 'comfort');\n    }\n\n    protected onStateChanged(name, value) {\n        let targetState;\n        switch (name) {\n            case 'io:TargetHeatingLevelState':\n                //targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n                switch (value) {\n                    case 'off':\n                        targetState = Characteristics.TargetHeatingCoolingState.OFF;\n                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                        break;\n                    case 'frostprotection':\n                        targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                        this.currentTemperature?.updateValue(FROSTPROTECTION_TEMP);\n                        this.targetTemperature?.updateValue(FROSTPROTECTION_TEMP);\n                        break;\n                    case 'comfort':\n                    case 'comfort-1':\n                    case 'comfort-2':\n                        targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                        this.eco?.updateValue(false);\n                        this.currentTemperature?.updateValue(this.comfortTemperature);\n                        this.targetTemperature?.updateValue(this.comfortTemperature);\n                        break;\n                    case 'eco':\n                        targetState = Characteristics.TargetHeatingCoolingState.COOL;\n                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                        this.eco?.updateValue(true);\n                        this.currentTemperature?.updateValue(this.ecoTemperature);\n                        this.targetTemperature?.updateValue(this.ecoTemperature);\n                        break;\n                }\n                if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n                    this.targetState.updateValue(targetState);\n                }\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint extends HeatingSystem {\n    protected THERMOSTAT_CHARACTERISTICS = ['prog'];\n    protected MIN_TEMP = 7;\n    protected MAX_TEMP = 28;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected registerMainService() {\n        if (this.device.get('io:NativeFunctionalLevelState') === 'Top') {\n            this.TARGET_MODES.push(Characteristics.TargetHeatingCoolingState.HEAT);\n        }\n        return super.registerMainService();\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                if (this.device.get('io:NativeFunctionalLevelState') === 'Top') {\n                    return new Command('setOperatingMode', 'auto');\n                } else {\n                    return new Command('setOperatingMode', this.prog?.value ? 'internal' : 'basic');\n                }\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                if (this.device.get('io:NativeFunctionalLevelState') === 'Top') {\n                    return new Command('setOperatingMode', this.prog?.value ? 'internal' : 'basic');\n                }\n                break;\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return new Command('setOperatingMode', 'standby');\n        }\n        return [];\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {\n        if (this.prog?.value) {\n            return new Command('setDerogatedTargetTemperature', value);\n        } else {\n            return new Command('setTargetTemperature', value);\n        }\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:TemperatureState':\n                this.onTemperatureUpdate(value);\n                break;\n            case 'io:EffectiveTemperatureSetpointState':\n            case 'core:TargetTemperatureState':\n            case 'io:TargetHeatingLevelState':\n            case 'core:OperatingModeState':\n                this.postpone(this.computeStates);\n                break;\n\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n        let targetTemperature;\n        targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n        switch (this.device.get('core:OperatingModeState')) {\n            case 'off':\n            case 'away':\n            case 'frostprotection':\n            case 'standby':\n                targetState = Characteristics.TargetHeatingCoolingState.OFF;\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                targetTemperature = this.device.get('core:TargetTemperatureState');\n                break;\n            case 'auto':\n                this.prog?.updateValue(false);\n                if (this.device.get('io:TargetHeatingLevelState') === 'eco') {\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                } else {\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                }\n                targetTemperature = this.device.get('io:EffectiveTemperatureSetpointState');\n                break;\n            case 'prog':\n            case 'program':\n            case 'internal':\n            case 'comfort':\n            case 'eco':\n            case 'manual':\n            case 'basic':\n                if (this.device.get('io:NativeFunctionalLevelState') === 'Top') {\n                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                }\n                this.prog?.updateValue(['prog', 'program', 'internal'].includes(this.device.get('core:OperatingModeState')));\n                if (this.device.get('io:TargetHeatingLevelState') === 'eco') {\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                } else {\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                }\n                targetTemperature = this.device.get('io:EffectiveTemperatureSetpointState');\n                break;\n        }\n\n        if (this.targetTemperature !== undefined && targetTemperature !== undefined && targetTemperature !== null) {\n            this.targetTemperature.updateValue(targetTemperature);\n        }\n\n        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticElectricalTowelDryer.ts",
    "content": "import { Characteristics, Services } from '../../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class AtlanticElectricalTowelDryer extends HeatingSystem {\n    protected THERMOSTAT_CHARACTERISTICS = ['prog'];\n    protected MIN_TEMP = 7;\n    protected MAX_TEMP = 28;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected drying: Characteristic | undefined;\n\n    protected registerServices() {\n        const services = super.registerServices();\n        if (this.device.hasCommand('setTowelDryerBoostModeDuration')) {\n            const boost = this.registerSwitchService('boost');\n            services.push(boost);\n        }\n        if (this.device.hasCommand('setDryingDuration')) {\n            const drying = this.registerService(Services.Switch, 'drying');\n            this.drying = drying.getCharacteristic(Characteristics.On);\n\n            this.drying?.onSet(this.setDrying.bind(this));\n            services.push(drying);\n        }\n        return services;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                return new Command('setTowelDryerOperatingMode', this.prog?.value ? 'internal' : 'external');\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return new Command('setTowelDryerOperatingMode', 'standby');\n        }\n        return [];\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {\n        if (this.prog?.value) {\n            return new Command('setDerogatedTargetTemperature', value);\n        } else {\n            return new Command('setTargetTemperature', value);\n        }\n    }\n\n    protected getOnCommands(value): Command | Array<Command> {\n        const commands = new Array<Command>();\n        commands.push(new Command('setTowelDryerTemporaryState', value ? 'boost' : 'permanentHeating'));\n        if (value) {\n            commands.push(new Command('setTowelDryerBoostModeDuration', 10));\n        }\n        return commands;\n    }\n\n    protected async setDrying(value) {\n        const commands = new Array<Command>();\n        commands.push(new Command('setTowelDryerTemporaryState', value ? 'drying' : 'permanentHeating'));\n        if (value) {\n            commands.push(new Command('setDryingDuration', 60));\n        }\n        const action = await this.executeCommands(commands);\n        action.on('update', (state) => {\n            switch (state) {\n                case ExecutionState.FAILED:\n                    this.drying?.updateValue(!value);\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:TemperatureState': this.onTemperatureUpdate(value); break;\n            case 'io:TowelDryerTemporaryStateState':\n                this.on?.updateValue(value === 'boost');\n                this.drying?.updateValue(value === 'drying');\n                break;\n            case 'core:TargetTemperatureState':\n            case 'core:DerogatedTargetTemperatureState':\n            case 'core:ComfortRoomTemperatureState':\n            case 'core:EcoRoomTemperatureState':\n            case 'core:OperatingModeState':\n            case 'io:TargetHeatingLevelState':\n                this.postpone(this.computeStates);\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetTemperature = Number(this.device.get('core:ComfortRoomTemperatureState'));\n        switch (this.device.get('io:TargetHeatingLevelState')) {\n            case 'off':\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                this.targetTemperature?.updateValue(this.device.get('core:TargetTemperatureState'));\n                break;\n            case 'eco':\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                targetTemperature = targetTemperature - Number(this.device.get('core:EcoRoomTemperatureState'));\n                break;\n            case 'comfort':\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                break;\n        }\n\n        switch (this.device.get('core:OperatingModeState')) {\n            case 'standby':\n                this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.OFF);\n                break;\n            case 'internal':\n                this.prog?.updateValue(true);\n                this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);\n                if (Number(this.device.get('core:DerogatedTargetTemperatureState')) > 0) {\n                    this.targetTemperature?.updateValue(this.device.get('core:DerogatedTargetTemperatureState'));\n                } else {\n                    this.targetTemperature?.updateValue(targetTemperature);\n                }\n                break;\n            case 'external':\n                this.prog?.updateValue(false);\n                this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);\n                this.targetTemperature?.updateValue(targetTemperature);\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCBoiler.ts",
    "content": "import { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class AtlanticPassAPCBoiler extends HeatingSystem {\n    protected registerMainService() {\n        return this.registerSwitchService();\n    }\n\n    protected getOnCommands(value): Command | Array<Command> {\n        return new Command('setPassAPCOperatingMode', value ? 'heating' : 'stop');\n    }\n\n    protected onStateChanged(name, value) {\n        switch (name) {\n            case 'io:PassAPCOperatingModeState':\n                switch (value) {\n                    case 'stop':\n                        this.on?.updateValue(false);\n                        break;\n                    case 'heating':\n                    case 'drying':\n                    case 'cooling':\n                        this.on?.updateValue(true);\n                        break;\n                }\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCHeatPump.ts",
    "content": "import { Characteristic, Perms } from 'homebridge';\nimport { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\nimport { TotalConsumptionCharacteristic } from '../../CustomCharacteristics';\n\nexport default class AtlanticPassAPCHeatPump extends HeatingSystem {\n    protected MIN_TEMP = 0;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.HEAT,\n        Characteristics.TargetHeatingCoolingState.COOL,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n    protected consumption: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = super.registerMainService();\n        this.targetTemperature?.setProps({ perms: [Perms.PAIRED_READ, Perms.EVENTS] });\n        return service;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                return new Command('setPassAPCOperatingMode', 'heating');\n\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                return new Command('setPassAPCOperatingMode', 'heating');\n\n            case Characteristics.TargetHeatingCoolingState.COOL:\n                return new Command('setPassAPCOperatingMode', 'cooling');\n\n            default:\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return new Command('setPassAPCOperatingMode', 'stop');\n        }\n    }\n\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    protected getTargetTemperatureCommands(value): Command | Array<Command> {\n        return [];\n    }\n\n    protected onStateChanged(name, value) {\n        switch (name) {\n            case 'io:PassAPCOperatingModeState':\n                this.postpone(this.computeStates);\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n        switch (this.device.get('io:PassAPCOperatingModeState')) {\n            case 'heating':\n                targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                break;\n            case 'cooling':\n                targetState = Characteristics.TargetHeatingCoolingState.COOL;\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                break;\n            case 'stop':\n                targetState = Characteristics.TargetHeatingCoolingState.OFF;\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                break;\n        }\n\n        // eslint-disable-next-line eqeqeq\n        if (this.targetState !== undefined && targetState != null && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCHeatingAndCoolingZone.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class AtlanticPassAPCHeatingAndCoolingZone extends HeatingSystem {\n    protected THERMOSTAT_CHARACTERISTICS = ['prog'];\n    protected MIN_TEMP = 16;\n    protected MAX_TEMP = 30;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    private refreshStatesTimeout;\n\n    protected applyConfig(config) {\n        super.applyConfig(config);\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        const heatingCooling = this.getHeatingCooling();\n        const commands: Array<Command> = [];\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                commands.push(new Command('set' + heatingCooling + 'OnOffState', 'on'));\n                commands.push(new Command('setPassAPC' + heatingCooling + 'Mode', this.prog?.value ? 'internalScheduling' : 'manu'));\n                break;\n\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                commands.push(new Command('set' + heatingCooling + 'OnOffState', 'off'));\n                break;\n        }\n\n        return commands;\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> {\n        const heatingCooling = this.getHeatingCooling();\n        if (this.prog?.value) {\n            if (this.device.hasCommand('setDerogatedTargetTemperature')) {\n                // AtlanticPassAPCHeatPump\n                return [\n                    new Command('setDerogatedTargetTemperature', value),\n                    new Command('setDerogationTime', this.derogationDuration),\n                    new Command('setDerogationOnOffState', 'on'),\n                ];\n            } else {\n                const profile = this.getProfile();\n                return new Command(`set${profile}${heatingCooling}TargetTemperature`, value);\n            }\n        } else {\n            if (this.device.hasCommand(`set${heatingCooling}TargetTemperature`)) {\n                // AtlanticPassAPCZoneControl\n                return new Command(`set${heatingCooling}TargetTemperature`, value);\n            } else {\n                // AtlanticPassAPCHeatPump\n                return new Command(`setComfort${heatingCooling}TargetTemperature`, value);\n            }\n        }\n    }\n\n    protected onStateChanged(name, value) {\n        switch (name) {\n            case 'core:TemperatureState':\n                this.onTemperatureUpdate(value);\n                break;\n            case 'core:TargetTemperatureState':\n                if (value >= 16) {\n                    this.targetTemperature?.updateValue(value);\n                }\n                break;\n            case 'core:HeatingOnOffState':\n            case 'core:CoolingOnOffState':\n            case 'io:PassAPCHeatingModeState':\n            case 'io:PassAPCCoolingModeState':\n            case 'io:PassAPCHeatingProfileState':\n            case 'io:PassAPCCoolingProfileState':\n                this.postpone(this.computeStates);\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n        let targetTemperature;\n        const heatingCooling = this.getHeatingCooling();\n\n        if (this.device.get(`core:${heatingCooling}OnOffState`) === 'off') {\n            targetState = Characteristics.TargetHeatingCoolingState.OFF;\n            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n        } else {\n            targetTemperature = targetTemperature = this.device.get(`core:${heatingCooling}TargetTemperatureState`) ||\n                this.device.get('core:TargetTemperatureState');\n            const currentTemperature = this.currentTemperature?.value || targetTemperature;\n            if (heatingCooling === 'Heating') {\n                if (currentTemperature >= (targetTemperature + 0.5)) {\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                } else {\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                }\n            } else {\n                if (currentTemperature <= (targetTemperature - 0.5)) {\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                } else {\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                }\n            }\n            targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n        }\n\n        if (this.device.get(`io:PassAPC${heatingCooling}ModeState`) === 'internalScheduling') {\n            this.prog?.updateValue(true);\n        } else {\n            this.prog?.updateValue(false);\n        }\n\n\n\n        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n\n        if (this.targetTemperature !== undefined && targetTemperature >= 16 && this.isIdle) {\n            this.targetTemperature.updateValue(targetTemperature);\n        }\n    }\n\n    /**\n     * Helpers\n     */\n    private getHeatingCooling() {\n        const operatingMode = this.device.parent?.get('io:PassAPCOperatingModeState');\n        if (operatingMode === 'cooling') {\n            return 'Cooling';\n        } else {\n            return 'Heating';\n        }\n    }\n\n    private getProfile() {\n        const heatingCooling = this.getHeatingCooling();\n        if (this.device.get(`core:Eco${heatingCooling}TargetTemperatureState`) === this.device.get('core:TargetTemperatureState')) {\n            return 'Eco';\n        } else {\n            return 'Comfort';\n        }\n    }\n\n    private launchRefreshStates() {\n        clearTimeout(this.refreshStatesTimeout);\n        this.refreshStatesTimeout = setTimeout(() => {\n            const commands = [\n                new Command('refreshTargetTemperature'),\n                new Command('refreshPassAPCHeatingProfile'),\n            ];\n            this.executeCommands(commands);\n        }, 30 * 1000);\n    }\n\n    private launchRefreshTemperature() {\n        clearTimeout(this.refreshStatesTimeout);\n        this.refreshStatesTimeout = setTimeout(() => {\n            this.executeCommands(new Command('refreshTargetTemperature'));\n        }, 30 * 1000);\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCHeatingZone.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class AtlanticPassAPCHeatingZone extends HeatingSystem {\n    protected THERMOSTAT_CHARACTERISTICS = ['eco', 'prog'];\n    protected MIN_TEMP = 10;\n    protected MAX_TEMP = 35;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        const commands: Array<Command> = [];\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                commands.push(new Command('setHeatingOnOffState', 'on'));\n                if (this.prog?.value) {\n                    commands.push(new Command('setPassAPCHeatingMode', 'internalScheduling'));\n                } else {\n                    commands.push(new Command('setDerogationOnOffState', 'off'));\n                    if (this.eco?.value) {\n                        commands.push(new Command('setPassAPCHeatingMode', 'eco'));\n                    } else {\n                        commands.push(new Command('setPassAPCHeatingMode', 'comfort'));\n                    }\n                }\n                break;\n\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                commands.push(new Command('setHeatingOnOffState', 'off'));\n                //commands.push(new Command('setHeatingOnOffState', 'on'));\n                //commands.push(new Command('setPassAPCHeatingMode', 'absence'));\n                break;\n        }\n        return commands;\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> {\n        const duration = this.derogationDuration;\n        const commands: Array<Command> = [];\n        if (this.prog?.value) {\n            commands.push(new Command('setDerogatedTargetTemperature', value));\n            commands.push(new Command('setDerogationTime', duration));\n            commands.push(new Command('setDerogationOnOffState', 'on'));\n        } else {\n            if (this.eco?.value) {\n                commands.push(new Command('setEcoHeatingTargetTemperature', value));\n            } else {\n                commands.push(new Command('setComfortHeatingTargetTemperature', value));\n            }\n        }\n        return commands;\n    }\n\n    protected onStateChanged(name, value) {\n        switch (name) {\n            case 'core:TemperatureState': this.onTemperatureUpdate(value); break;\n            case 'core:TargetTemperatureState':\n            case 'core:HeatingOnOffState':\n            case 'io:PassAPCHeatingModeState':\n            case 'io:PassAPCHeatingProfileState':\n            case 'core:ComfortHeatingTargetTemperatureState':\n            case 'core:EcoHeatingTargetTemperatureState':\n                this.postpone(this.computeStates);\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n        if (this.device.get('core:HeatingOnOffState') === 'on') {\n            targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n            switch (this.device.get('io:PassAPCHeatingModeState')) {\n                case 'off':\n                case 'absence':\n                    targetState = Characteristics.TargetHeatingCoolingState.OFF;\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                    this.targetTemperature?.updateValue(this.device.get('core:TargetTemperatureState'));\n                    break;\n                case 'auto':\n                case 'internalScheduling':\n                case 'externalScheduling':\n                    this.prog?.updateValue(true);\n                    if (this.device.get('io:PassAPCHeatingProfileState') === 'comfort') {\n                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                    } else {\n                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                    }\n                    if (this.device.get('io:PassAPCHeatingProfileState') === 'derogation') {\n                        this.targetTemperature?.updateValue(this.device.get('core:DerogatedTargetTemperatureState'));\n                    } else if (this.device.get('io:PassAPCHeatingProfileState') === 'comfort') {\n                        this.targetTemperature?.updateValue(this.device.get('core:ComfortHeatingTargetTemperatureState'));\n                    } else if (this.device.get('io:PassAPCHeatingProfileState') === 'eco') {\n                        this.targetTemperature?.updateValue(this.device.get('core:EcoHeatingTargetTemperatureState'));\n                    } else {\n                        this.targetTemperature?.updateValue(this.device.get('core:TargetTemperatureState'));\n                    }\n                    break;\n                case 'comfort':\n                    this.prog?.updateValue(false);\n                    this.eco?.updateValue(false);\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                    this.targetTemperature?.updateValue(this.device.get('core:ComfortHeatingTargetTemperatureState'));\n                    break;\n                case 'eco':\n                    this.prog?.updateValue(false);\n                    this.eco?.updateValue(true);\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                    this.targetTemperature?.updateValue(this.device.get('core:EcoHeatingTargetTemperatureState'));\n                    break;\n            }\n        } else {\n            targetState = Characteristics.TargetHeatingCoolingState.OFF;\n            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n            this.targetTemperature?.updateValue(this.device.get('core:TargetTemperatureState'));\n        }\n        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCZoneControl.ts",
    "content": "import { Perms } from 'homebridge';\nimport { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class AtlanticPassAPCZoneControl extends HeatingSystem {\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.HEAT,\n        Characteristics.TargetHeatingCoolingState.COOL,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected registerMainService() {\n        const service = super.registerMainService();\n        this.targetTemperature?.setProps({ perms: [Perms.PAIRED_READ, Perms.EVENTS] });\n        return service;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                return [\n                    new Command('setPassAPCOperatingMode', 'heating'),\n                    new Command('setHeatingCoolingAutoSwitch', 'on'),\n                ];\n\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                return [\n                    new Command('setPassAPCOperatingMode', 'heating'),\n                    new Command('setHeatingCoolingAutoSwitch', 'off'),\n                ];\n\n            case Characteristics.TargetHeatingCoolingState.COOL:\n                return [\n                    new Command('setPassAPCOperatingMode', 'cooling'),\n                    new Command('setHeatingCoolingAutoSwitch', 'off'),\n                ];\n\n            default:\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return [\n                    new Command('setPassAPCOperatingMode', 'stop'),\n                ];\n        }\n    }\n\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    protected getTargetTemperatureCommands(value): Command | Array<Command> {\n        return [];\n    }\n\n    protected onStateChanged(name, value) {\n        switch (name) {\n            case 'io:PassAPCOperatingModeState':\n            case 'core:HeatingCoolingAutoSwitchState':\n                this.postpone(this.computeStates);\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n        switch (this.device.get('io:PassAPCOperatingModeState')) {\n            case 'heating':\n                if (this.device.get('core:HeatingCoolingAutoSwitchState') === 'on') {\n                    targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n                } else {\n                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                }\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                break;\n            case 'cooling':\n                if (this.device.get('core:HeatingCoolingAutoSwitchState') === 'on') {\n                    targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n                } else {\n                    targetState = Characteristics.TargetHeatingCoolingState.COOL;\n                }\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                break;\n            case 'stop':\n                targetState = Characteristics.TargetHeatingCoolingState.OFF;\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                break;\n        }\n\n        // eslint-disable-next-line eqeqeq\n        if (this.targetState !== undefined && targetState != null && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/ProgrammableAndProtectableThermostatSetPoint.ts",
    "content": "import ThermostatSetPoint from './ThermostatSetPoint';\n\nexport default class ProgrammableAndProtectableThermostatSetPoint extends ThermostatSetPoint {   \n    \n}"
  },
  {
    "path": "src/mappers/HeatingSystem/SomfyHeatingTemperatureInterface.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class SomfyHeatingTemperatureInterface extends HeatingSystem {\n    protected THERMOSTAT_CHARACTERISTICS = ['prog', 'eco'];\n    protected MIN_TEMP = 0;\n    protected MAX_TEMP = 26;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.HEAT,\n        Characteristics.TargetHeatingCoolingState.COOL,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected getTargetStateCommands(value): Command | Array<Command> | undefined {\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                return [\n                    new Command('setOnOff', 'on'),\n                    new Command('setOperatingMode', 'both'),\n                ];\n\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                return [\n                    new Command('setOnOff', 'on'),\n                    new Command('setOperatingMode', 'heating'),\n                ];\n\n            case Characteristics.TargetHeatingCoolingState.COOL:\n                return [\n                    new Command('setOnOff', 'on'),\n                    new Command('setOperatingMode', 'cooling'),\n                ];\n\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return new Command('setOnOff', 'off');\n        }\n    }\n\n    protected getProgCommands(): Command | Array<Command> | undefined {\n        if (this.prog?.value) {\n            return new Command('setActiveMode', 'auto');\n        } else {\n            if (this.eco?.value) {\n                return new Command('setManuAndSetPointModes', 'eco');\n            } else {\n                return new Command('setManuAndSetPointModes', 'comfort');\n            }\n        }\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {\n        if (this.device.get('ovp:HeatingTemperatureInterfaceSetPointModeState') === 'comfort') {\n            return new Command('setComfortTemperature', value);\n        } else {\n            return new Command('setEcoTemperature', value);\n        }\n    }\n\n    protected onStateChanged(name, value) {\n        switch (name) {\n            case 'core:OnOffState':\n            case 'ovp:HeatingTemperatureInterfaceOperatingModeState':\n                this.postpone(this.computeStates);\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n        if (this.device.get('core:OnOffState') === 'on') {\n            switch (this.device.get('ovp:HeatingTemperatureInterfaceOperatingModeState')) {\n                case 'both':\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                    targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n                    break;\n                case 'heating':\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                    break;\n                case 'cooling':\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                    targetState = Characteristics.TargetHeatingCoolingState.COOL;\n                    break;\n            }\n        } else {\n            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n            targetState = Characteristics.TargetHeatingCoolingState.OFF;\n        }\n        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/SomfyThermostat.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class SomfyThermostat extends HeatingSystem {\n    protected MIN_TEMP = 0;\n    protected MAX_TEMP = 26;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.HEAT,\n        Characteristics.TargetHeatingCoolingState.COOL,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n    private lastRefresh = Date.now();\n\n    protected registerMainService() {\n        const service = super.registerMainService();\n        this.targetState?.onGet(this.refreshStates.bind(this));\n        return service;\n    }\n\n    protected async refreshStates() {\n        if (this.lastRefresh < Date.now() - (60 * 1000)) {\n            this.lastRefresh = Date.now();\n            await this.executeCommands(new Command('refreshState'));\n        }\n        return this.targetState?.value ?? Characteristics.TargetHeatingCoolingState.OFF;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> | undefined {\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                return new Command('exitDerogation');\n\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                return new Command('setDerogation', ['atHomeMode', 'further_notice']);\n\n            case Characteristics.TargetHeatingCoolingState.COOL:\n                return new Command('setDerogation', ['sleepingMode', 'further_notice']);\n\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return new Command('setDerogation', ['awayMode', 'further_notice']);\n        }\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {\n        return new Command('setDerogation', [value, 'further_notice']);\n    }\n\n    protected onStateChanged(name, value) {\n        switch (name) {\n            case 'core:TargetTemperatureState':\n            case 'core:DerogatedTargetTemperatureState':\n            case 'core:DerogationActivationState':\n            case 'somfythermostat:DerogationHeatingModeState':\n                this.postpone(this.computeStates);\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n        let targetTemperature;\n\n        const derog = this.device.get('core:DerogationActivationState') === 'active';\n        const mode = this.device.get(derog ? 'somfythermostat:DerogationHeatingModeState' : 'somfythermostat:HeatingModeState');\n        switch (mode) {\n            case 'atHomeMode':\n            case 'geofencingMode':\n            case 'manualMode':\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                targetState = derog ? Characteristics.TargetHeatingCoolingState.HEAT : Characteristics.TargetHeatingCoolingState.AUTO;\n                break;\n            case 'sleepingMode':\n            case 'suddenDropMode':\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                targetState = derog ? Characteristics.TargetHeatingCoolingState.COOL : Characteristics.TargetHeatingCoolingState.AUTO;\n                break;\n            case 'awayMode':\n            case 'freezeMode':\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                targetState = Characteristics.TargetHeatingCoolingState.OFF;\n                break;\n        }\n\n        switch (mode) {\n            case 'atHomeMode':\n                targetTemperature = this.device.get('somfythermostat:AtHomeTargetTemperatureState');\n                break;\n            case 'geofencingMode':\n                targetTemperature = this.device.get('somfythermostat:GeofencingModeTargetTemperatureState');\n                break;\n            case 'manualMode':\n                targetTemperature = this.device.get('somfythermostat:ManualModeTargetTemperatureState');\n                break;\n            case 'sleepingMode':\n                targetTemperature = this.device.get('somfythermostat:SleepingModeTargetTemperatureState');\n                break;\n            case 'suddenDropMode':\n                targetTemperature = this.device.get('somfythermostat:SuddenDropModeTargetTemperatureState');\n                break;\n            case 'awayMode':\n                targetTemperature = this.device.get('somfythermostat:AwayModeTargetTemperatureState');\n                break;\n            case 'freezeMode':\n                targetTemperature = this.device.get('somfythermostat:FreezeModeTargetTemperatureState');\n                break;\n        }\n        if (targetTemperature === undefined || targetTemperature === null) {\n            targetTemperature = this.device.get(derog ? 'core:DerogatedTargetTemperatureState' : 'core:TargetTemperatureState');\n        }\n\n        if (this.targetTemperature !== undefined && targetTemperature !== undefined) {\n            this.targetTemperature.updateValue(targetTemperature);\n        }\n\n        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/ThermostatSetPoint.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class ThermostatSetPoint extends HeatingSystem {\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n    ];\n\n    protected registerMainService() {\n        const service = super.registerMainService();\n        this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);\n        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n        return service;\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> {\n        return new Command('setHeatingTargetTemperature', value);\n    }\n\n    protected onStateChanged(name, value) {\n        switch (name) {\n            case 'zwave:SetPointHeatingValueState':\n            case 'core:RoomTemperatureState':\n                this.onTemperatureUpdate(value);\n                break;\n            case 'core:HeatingTargetTemperatureState':\n                this.targetTemperature?.updateValue(value);\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/ValveHeatingTemperatureInterface.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class ValveHeatingTemperatureInterface extends HeatingSystem {\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.HEAT,\n        Characteristics.TargetHeatingCoolingState.COOL,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected getTargetStateCommands(value): Command | Array<Command> | undefined {\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                return new Command('exitDerogation');\n\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                return new Command('setDerogation', ['comfort', 'further_notice']);\n\n            case Characteristics.TargetHeatingCoolingState.COOL:\n                return new Command('setDerogation', ['eco', 'further_notice']);\n\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return new Command('setDerogation', ['away', 'further_notice']);\n        }\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {\n        return new Command('setDerogation', [value, 'further_notice']);\n    }\n\n    protected onStateChanged(name, value) {\n        switch (name) {\n            case 'core:OperatingModeState':\n            case 'io:CurrentHeatingModeState':\n                this.postpone(this.computeStates);\n                break;\n            case 'core:TargetRoomTemperatureState':\n                this.targetTemperature?.updateValue(value);\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n\n        const auto = ['auto', 'prog', 'program'].includes(this.device.get('core:OperatingModeState') || '');\n\n        switch (this.device.get('io:CurrentHeatingModeState')) {\n            case 'manual':\n            case 'comfort':\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                targetState = auto ? Characteristics.TargetHeatingCoolingState.AUTO : Characteristics.TargetHeatingCoolingState.HEAT;\n                break;\n            case 'eco':\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                targetState = auto ? Characteristics.TargetHeatingCoolingState.AUTO : Characteristics.TargetHeatingCoolingState.COOL;\n                break;\n            case 'off':\n            case 'awayMode':\n            case 'frostprotection':\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                targetState = Characteristics.TargetHeatingCoolingState.OFF;\n                break;\n        }\n        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HeatingSystem.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, Service } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Mapper from '../Mapper';\nimport { EcoCharacteristic, ProgCharacteristic, TotalConsumptionCharacteristic } from '../CustomCharacteristics';\n\nexport default class HeatingSystem extends Mapper {\n    protected THERMOSTAT_CHARACTERISTICS: string[] = [];\n    protected MIN_TEMP = 7;\n    protected MAX_TEMP = 30;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected currentTemperature: Characteristic | undefined;\n    protected targetTemperature: Characteristic | undefined;\n    protected currentState: Characteristic | undefined;\n    protected targetState: Characteristic | undefined;\n\n    protected on: Characteristic | undefined;\n\n    protected prog: Characteristic | undefined;\n    protected eco: Characteristic | undefined;\n\n    protected consumption: Characteristic | undefined;\n\n    protected derogationDuration;\n    protected comfortTemperature;\n    protected ecoTemperature;\n\n    protected applyConfig(config) {\n        this.derogationDuration = config['derogationDuration'] || 1;\n        this.comfortTemperature = config['comfort'] || 19;\n        this.ecoTemperature = config['eco'] || 17;\n    }\n\n    protected registerMainService(): Service {\n        const service = this.registerService(Services.Thermostat);\n        service.setPrimaryService(true);\n        service.addOptionalCharacteristic(ProgCharacteristic);\n        service.addOptionalCharacteristic(EcoCharacteristic);\n        this.currentTemperature = service.getCharacteristic(Characteristics.CurrentTemperature);\n        this.targetTemperature = service.getCharacteristic(Characteristics.TargetTemperature);\n        this.currentState = service.getCharacteristic(Characteristics.CurrentHeatingCoolingState);\n        this.targetState = service.getCharacteristic(Characteristics.TargetHeatingCoolingState);\n\n        this.currentTemperature.setProps({ minStep: 0.1 });\n\n        this.targetState?.setProps({ validValues: this.TARGET_MODES });\n        this.targetTemperature?.setProps({ minValue: this.MIN_TEMP, maxValue: this.MAX_TEMP, minStep: 0.5 });\n        const temp = Number(this.targetTemperature.value)\n        if (this.targetTemperature && temp < this.targetTemperature.props.minValue!) {\n            this.targetTemperature.value = this.targetTemperature.props.minValue!;\n        }\n        if (this.targetTemperature && temp > this.targetTemperature.props.maxValue!) {\n            this.targetTemperature.value = this.targetTemperature.props.maxValue!;\n        }\n\n        if (this.THERMOSTAT_CHARACTERISTICS.includes('prog')) {\n            this.prog = service.getCharacteristic(ProgCharacteristic);\n            this.prog.onSet((value) => {\n                this.prog?.updateValue(value);\n                this.sendProgCommands();\n            });\n        }\n\n        if (this.THERMOSTAT_CHARACTERISTICS.includes('eco')) {\n            this.eco = service.getCharacteristic(EcoCharacteristic);\n            this.eco.onSet((value) => {\n                this.eco?.updateValue(value);\n                this.sendProgCommands();\n            });\n        }\n\n        if (this.device.hasSensor('CumulativeElectricPowerConsumptionSensor')) {\n            service.addOptionalCharacteristic(TotalConsumptionCharacteristic);\n            this.consumption = service.getCharacteristic(TotalConsumptionCharacteristic);\n        }\n\n        this.targetState?.onSet(this.setTargetState.bind(this));\n        this.targetTemperature?.onSet(this.debounce(this.setTargetTemperature));\n        return service;\n    }\n\n    protected registerSwitchService(subtype?: string): Service {\n        const service = this.registerService(Services.Switch, subtype);\n        this.on = service.getCharacteristic(Characteristics.On);\n\n        this.on?.onSet(this.setOn.bind(this));\n        return service;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> | undefined {\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                return new Command('auto');\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                return new Command('heat');\n            case Characteristics.TargetHeatingCoolingState.COOL:\n                return new Command('cool');\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                return new Command('off');\n            default:\n                return new Command('auto');\n        }\n    }\n\n    protected async setTargetState(value) {\n        if (value === this.targetState?.value) {\n            return;\n        }\n        const action = await this.executeCommands(this.getTargetStateCommands(value));\n        action.on('update', (state) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    if (this.stateless) {\n                        this.currentState?.updateValue(value);\n                    }\n                    break;\n                case ExecutionState.FAILED:\n                    if (this.currentState) {\n                        this.targetState?.updateValue(this.currentState.value);\n                    }\n                    break;\n            }\n        });\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {\n        return new Command('setTargetTemperature', value);\n    }\n\n    protected async setTargetTemperature(value) {\n        await this.executeCommands(this.getTargetTemperatureCommands(value));\n    }\n\n    protected getOnCommands(value): Command | Array<Command> | undefined {\n        return new Command('setOn', value);\n    }\n\n    protected async setOn(value) {\n        const action = await this.executeCommands(this.getOnCommands(value));\n        action.on('update', (state) => {\n            switch (state) {\n                case ExecutionState.FAILED:\n                    this.on?.updateValue(!value);\n                    break;\n            }\n        });\n    }\n\n    protected getProgCommands(): Command | Array<Command> | undefined {\n        return this.getTargetStateCommands(this.targetState?.value);\n    }\n\n    protected sendProgCommands() {\n        if (this.targetState?.value !== Characteristics.TargetHeatingCoolingState.OFF) {\n            this.executeCommands(this.getProgCommands());\n        }\n    }\n\n    protected onTemperatureUpdate(value) {\n        this.currentTemperature?.updateValue(value > 273.15 ? (value - 273.15) : value);\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:TemperatureState': this.onTemperatureUpdate(value); break;\n            case 'core:TargetTemperatureState':\n                this.targetTemperature?.updateValue(value);\n                break;\n            case 'core:ElectricEnergyConsumptionState':\n                this.consumption?.updateValue(value / 1000);\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "src/mappers/HitachiHeatingSystem/HitachiAirToAirHeatPump.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class HitachiAirToAirHeatPump extends HeatingSystem {\n    protected MIN_TEMP = 16;\n    protected MAX_TEMP = 30;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.HEAT,\n        Characteristics.TargetHeatingCoolingState.COOL,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected getTargetStateCommands(value): Command | Array<Command> | undefined {\n        return this.getCommands(value, this.targetTemperature?.value);\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> {\n        return this.getCommands(this.targetState?.value, value);\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'ovp:ModeChangeState':\n            case 'ovp:MainOperationState':\n                if (this.device.get('ovp:MainOperationState') === 'Off' || this.device.get('ovp:MainOperationState') === 'off') {\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                    this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.OFF);\n                } else {\n                    switch (this.device.get('ovp:ModeChangeState')?.toLowerCase()) {\n                        case 'auto cooling':\n                            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                            this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);\n                            break;\n                        case 'auto heating':\n                            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                            this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);\n                            break;\n                        case 'cooling':\n                            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                            this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.COOL);\n                            break;\n                        case 'heating':\n                            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                            this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.HEAT);\n                            break;\n                    }\n                }\n                break;\n            case 'ovp:RoomTemperatureState':\n                this.onTemperatureUpdate(value);\n                break;\n            case 'core:TargetTemperatureState':\n                this.targetTemperature?.updateValue(value);\n                break;\n            /*\n            case 'ovp:TemperatureChangeState':\n                if(value <= 5 && this.currentTemperature) {\n                    this.targetTemperature?.updateValue(this.currentTemperature.value + value);\n                } else {\n                    this.targetTemperature?.updateValue(value);\n                }\n                break;\n            */\n        }\n    }\n\n    private getCommands(state, temperature) {\n        const currentState = this.currentState ? this.currentState.value : 0;\n        const currentTemperature = this.currentTemperature && this.currentTemperature.value !== null ? this.currentTemperature.value : 0;\n        let onOff = 'on';\n        const fanMode = 'auto';\n        const progMode = 'manu';\n        let heatMode = 'auto';\n        const autoTemp = Math.trunc(Math.max(Math.min(temperature - parseInt(currentTemperature.toString()), 5), -5));\n\n        switch (state) {\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                onOff = 'off';\n                switch (currentState) {\n                    case Characteristics.CurrentHeatingCoolingState.HEAT:\n                        heatMode = 'heating';\n                        break;\n                    case Characteristics.CurrentHeatingCoolingState.COOL:\n                        heatMode = 'cooling';\n                        break;\n                    default:\n                        temperature = autoTemp;\n                        break;\n                }\n                break;\n\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                heatMode = 'heating';\n                break;\n\n            case Characteristics.TargetHeatingCoolingState.COOL:\n                heatMode = 'cooling';\n                break;\n\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                heatMode = 'auto';\n                temperature = autoTemp;\n                break;\n\n            default:\n                temperature = autoTemp;\n                break;\n        }\n\n        temperature = Math.round(temperature);\n        this.debug('FROM ' + currentState + '/' + currentTemperature + ' TO ' + state + '/' + temperature);\n\n        return new Command('globalControl', [onOff, temperature, fanMode, heatMode, progMode]);\n    }\n}"
  },
  {
    "path": "src/mappers/HitachiHeatingSystem/HitachiAirToWaterHeatingZone.ts",
    "content": "import HeatingSystem from '../HeatingSystem';\n\nexport default class HitachiAirToWaterHeatingZone extends HeatingSystem {\n\n}"
  },
  {
    "path": "src/mappers/HitachiHeatingSystem/HitachiAirToWaterMainComponent.ts",
    "content": "import HeatingSystem from '../HeatingSystem';\n\nexport default class HitachiAirToWaterMainComponent extends HeatingSystem {\n    protected MIN_TEMP = 0;\n\n}"
  },
  {
    "path": "src/mappers/HitachiHeatingSystem/HitachiDHW.ts",
    "content": "import WaterHeatingSystem from '../WaterHeatingSystem';\n\nexport default class HitachiDHW extends WaterHeatingSystem {\n\n}"
  },
  {
    "path": "src/mappers/HumiditySensor/WaterDetectionSensor.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport ContactSensor from '../ContactSensor';\n\nexport default class WaterDetectionSensor extends ContactSensor {\n    protected onStateChanged(name: string, value) {\n        switch(name) {\n            case 'core:WaterDetectionState ':\n                this.state?.updateValue(\n                    value === 'detected' ? \n                        Characteristics.ContactSensorState.CONTACT_DETECTED :\n                        Characteristics.ContactSensorState.CONTACT_NOT_DETECTED\n                    );\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/HumiditySensor.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class HumiditySensor extends Mapper {\n    protected humidity: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.HumiditySensor);\n        this.humidity = service.getCharacteristic(Characteristics.CurrentRelativeHumidity);\n        return service;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:RelativeHumidityState':\n                this.humidity?.updateValue(value);\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Light.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, CharacteristicSetCallback } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Mapper from '../Mapper';\n\nexport default class Light extends Mapper {\n    protected on: Characteristic | undefined;\n    protected hue: Characteristic | undefined;\n    protected brightness: Characteristic | undefined;\n    protected saturation: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(this.device.hasCommand('setIntensity') ? Services.Lightbulb : Services.Switch);\n        this.on = service.getCharacteristic(Characteristics.On);\n\n        this.on.onSet(this.setOn.bind(this));\n\n        if (this.device.hasCommand('setIntensity')) {\n            this.brightness = service.getCharacteristic(Characteristics.Brightness);\n            this.brightness.onSet(this.setBrightness.bind(this));\n\n            if (this.device.hasCommand('setHueAndSaturation')) {\n                this.hue = service.getCharacteristic(Characteristics.Hue);\n                this.saturation = service.getCharacteristic(Characteristics.Saturation);\n                this.saturation.onSet(this.setSaturation.bind(this));\n            }\n        }\n        return service;\n    }\n\n    protected getOnOffCommands(value): Command | Array<Command> {\n        return new Command(value ? 'on' : 'off');\n    }\n\n    protected async setOn(value) {\n        const action = await this.executeCommands(this.getOnOffCommands(value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    break;\n                case ExecutionState.FAILED:\n                    break;\n            }\n        });\n    }\n\n    protected getBrightnessCommands(value): Command | Array<Command> {\n        return new Command('setIntensity', value);\n    }\n\n    protected async setBrightness(value) {\n        const action = await this.executeCommands(this.getBrightnessCommands(value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    break;\n                case ExecutionState.FAILED:\n                    break;\n            }\n        });\n    }\n\n    protected getSaturationCommands(value): Command | Array<Command> {\n        return new Command('setHueAndSaturation', [this.hue?.value, value]);\n    }\n\n    protected async setSaturation(value) {\n        const action = await this.executeCommands(this.getSaturationCommands(value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    break;\n                case ExecutionState.FAILED:\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value): boolean {\n        switch (name) {\n            case 'core:OnOffState':\n                this.on?.updateValue(value === 'on');\n                break;\n            case 'core:IntensityState':\n            case 'core:LightIntensityState':\n                this.brightness?.updateValue(value);\n                break;\n            case 'core:ColorHueState':\n                this.hue?.updateValue(value);\n                break;\n            case 'core:ColorSaturationState':\n                this.saturation?.updateValue(value);\n                break;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/mappers/LightSensor.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class LightSensor extends Mapper {\n    protected lightLevel: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.LightSensor);\n        this.lightLevel = service.getCharacteristic(Characteristics.CurrentAmbientLightLevel);\n        return service;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:LuminanceState':\n                this.lightLevel?.updateValue(value);\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/OccupancySensor.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class OccupancySensor extends Mapper {\n    protected occupancy: Characteristic | undefined;\n    protected fault: Characteristic | undefined;\n    protected battery: Characteristic | undefined;\n\n    protected registerMainService() {\n        const motion = this.device.definition.widgetName.startsWith('Motion');\n        const service = this.registerService(motion ? Services.MotionSensor : Services.OccupancySensor);\n        this.occupancy = service.getCharacteristic(motion ? Characteristics.MotionDetected : Characteristics.OccupancyDetected);\n        if (this.device.hasState('core:SensorDefectState')) {\n            this.fault = service.getCharacteristic(Characteristics.StatusFault);\n            this.battery = service.getCharacteristic(Characteristics.StatusLowBattery);\n        }\n        return service;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:OccupancyState':\n                this.occupancy?.updateValue(value === 'personInside');\n                break;\n            case 'core:SensorDefectState':\n                switch (value) {\n                    case 'lowBattery':\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_LOW);\n                        break;\n                    case 'maintenanceRequired':\n                    case 'dead':\n                        this.fault?.updateValue(Characteristics.StatusFault.GENERAL_FAULT);\n                        break;\n                    case 'noDefect':\n                        this.fault?.updateValue(Characteristics.StatusFault.NO_FAULT);\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_NORMAL);\n                        break;\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/OnOff.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, CharacteristicSetCallback } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Mapper from '../Mapper';\n\nexport default class OnOff extends Mapper {\n    protected on: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.Switch);\n        this.on = service.getCharacteristic(Characteristics.On);\n\n        this.on.onSet(this.setOn.bind(this));\n        return service;\n    }\n\n    protected getOnOffCommands(value): Command | Array<Command> {\n        return new Command(value ? 'on' : 'off');\n    }\n\n    protected async setOn(value) {\n        const action = await this.executeCommands(this.getOnOffCommands(value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    break;\n                case ExecutionState.FAILED:\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value): boolean {\n        switch (name) {\n            case 'core:OnOffState':\n                this.on?.updateValue(value === 'on');\n                break;\n        }\n        return false;\n    }\n}"
  },
  {
    "path": "src/mappers/Pergola/BioclimaticPergola.ts",
    "content": "import { Command } from 'overkiz-client';\nimport Pergola from '../Pergola';\n\nexport default class BioclimaticPergola extends Pergola {\n    protected getTargetCommands(value) {\n        return new Command('setOrientation', this.reversedValue(value));\n    }\n\n    protected onStateChanged(name, value) {\n        switch(name) {\n            case 'core:SlatsOrientationState':\n                this.currentPosition?.updateValue(this.reversedValue(value));\n                if(this.isIdle) {\n                    this.targetPosition?.updateValue(this.reversedValue(value));\n                }\n                break;\n            default: break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Pergola/PergolaHorizontalAwningUno.ts",
    "content": "import Pergola from '../Pergola';\n\nexport default class PergolaHorizontalAwningUno extends Pergola {\n    protected onStateChanged(name: string, value) {\n        // Fix (https://github.com/dubocr/homebridge-tahoma/issues/305)\n        value = 100 - value;\n        switch (name) {\n            case 'core:ClosureState':\n                this.currentPosition?.updateValue(this.reversedValue(value));\n                if (!this.device.hasState('core:TargetClosureState') && this.isIdle) {\n                    this.targetPosition?.updateValue(this.reversedValue(value));\n                }\n                break;\n            case 'core:TargetClosureState':\n                this.targetPosition?.updateValue(this.reversedValue(value));\n                if (!this.device.hasState('core:ClosureState')) {\n                    this.currentPosition?.updateValue(this.reversedValue(value));\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Pergola.ts",
    "content": "import RollerShutter from './RollerShutter';\n\nexport default class Pergola extends RollerShutter {\n}"
  },
  {
    "path": "src/mappers/RainSensor.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class RainSensor extends Mapper {\n    protected rain: Characteristic | undefined;\n    protected fault: Characteristic | undefined;\n    protected battery: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.ContactSensor);\n        this.rain = service.getCharacteristic(Characteristics.ContactSensorState);\n        if (this.device.hasState('core:SensorDefectState')) {\n            this.fault = service.getCharacteristic(Characteristics.StatusFault);\n            this.battery = service.getCharacteristic(Characteristics.StatusLowBattery);\n        }\n        return service;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:RainState':\n                this.rain?.updateValue(value === 'detected');\n                break;\n            case 'core:SensorDefectState':\n                switch (value) {\n                    case 'lowBattery':\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_LOW);\n                        break;\n                    case 'maintenanceRequired':\n                    case 'dead':\n                        this.fault?.updateValue(Characteristics.StatusFault.GENERAL_FAULT);\n                        break;\n                    case 'noDefect':\n                        this.fault?.updateValue(Characteristics.StatusFault.NO_FAULT);\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_NORMAL);\n                        break;\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/RemoteController.ts",
    "content": "import { Characteristic } from 'homebridge';\nimport Mapper from '../Mapper';\nimport { Characteristics, Services } from '../Platform';\n\nexport default class RemoteController extends Mapper {\n    protected event: Characteristic | undefined;\n\n    protected registerMainService() {\n        throw new Error('Service RemoteController not implemented');\n        const service = this.registerService(Services.StatelessProgrammableSwitch);\n        this.event = service.getCharacteristic(Characteristics.ProgrammableSwitchEvent);\n        return service;\n    }\n    \n    \n    protected onStateChanged(name: string, value) {\n        switch(name) {\n            default: this.event?.updateValue(value);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/RollerShutter/PositionableRollerShutterUno.ts",
    "content": "import RollerShutter from '../RollerShutter';\nexport default class PositionableRollerShutterUno extends RollerShutter {\n    protected onStateChanged(name: string, value) {\n        switch(name) {\n            case 'core:TargetClosureState':\n                if(this.isIdle) {\n                    this.targetPosition?.updateValue(this.reversedValue(value));\n                }\n                this.currentPosition?.updateValue(this.reversedValue(value));\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/RollerShutter/PositionableRollerShutterWithLowSpeedManagement.ts",
    "content": "import moment from 'moment';\nimport { Command } from 'overkiz-client';\nimport RollerShutter from '../RollerShutter';\nexport default class PositionableRollerShutterWithLowSpeedManagement extends RollerShutter {\n    protected lowSpeed;\n\n    protected applyConfig(config) {\n        this.lowSpeed = config['lowSpeed'] || false;\n    }\n\n    protected getTargetCommands(value) {\n        if (this.isLowSpeed) {\n            return new Command('setClosureAndLinearSpeed', [this.reversedValue(value), 'lowspeed']);\n        } else {\n            return new Command('setClosure', this.reversedValue(value));\n        }\n    }\n\n    protected get isLowSpeed() {\n        if (this.lowSpeed === true) {\n            return true;\n        } else if (typeof this.lowSpeed === 'string') {\n            const parts = this.lowSpeed.split(new RegExp('[-:]'));\n            const now = moment();\n            const start = moment();\n            const end = moment();\n            start.set({ 'hour': parseInt(parts[0]), 'minute': parseInt(parts[1]), 'second': 0, 'millisecond': 0 });\n            end.set({ 'hour': parseInt(parts[2]), 'minute': parseInt(parts[3]), 'second': 0, 'millisecond': 0 });\n            if (end.isBefore(start)) {\n                return now.isAfter(start) || now.isBefore(end);\n            } else {\n                return now.isBetween(start, end);\n            }\n        } else {\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/RollerShutter.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, Service } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Mapper from '../Mapper';\nimport { MyPositionCharacteristic } from '../CustomCharacteristics';\n\nexport default class RollerShutter extends Mapper {\n    protected expectedStates = ['core:ClosureState', 'core:TargetClosureState'];\n    protected windowService: Service | undefined;\n\n    protected currentPosition: Characteristic | undefined;\n    protected targetPosition: Characteristic | undefined;\n    protected positionState: Characteristic | undefined;\n    protected obstructionDetected: Characteristic | undefined;\n    protected my: Characteristic | undefined;\n\n    protected reverse;\n    protected initPosition;\n    protected defaultPosition;\n    protected blindsOnRollerShutter;\n    protected movementDuration;\n    protected offsetMovementDuration;\n\n    protected cancelTimeout;\n\n    protected applyConfig(config) {\n        this.defaultPosition = config['defaultPosition'] || 0;\n        this.initPosition = config['initPosition'] !== undefined ? config['initPosition'] : (config['defaultPosition'] || 50);\n        this.reverse = config['reverse'] || false;\n        this.movementDuration = config['movementDuration'] || 0;\n        this.offsetMovementDuration = config['offsetMovementDuration'] || 0;\n        this.blindsOnRollerShutter = config['blindsOnRollerShutter'] || false;\n    }\n\n    protected registerMainService() {\n        const service = this.registerService(Services.WindowCovering);\n        service.addOptionalCharacteristic(MyPositionCharacteristic);\n        this.currentPosition = service.getCharacteristic(Characteristics.CurrentPosition);\n        this.targetPosition = service.getCharacteristic(Characteristics.TargetPosition);\n        this.positionState = service.getCharacteristic(Characteristics.PositionState);\n        if (this.stateless) {\n            //this.currentPosition.updateValue(this.initPosition);\n            //this.targetPosition.updateValue(this.initPosition);\n            if (this.device.hasCommand('my')) {\n                this.my = service.getCharacteristic(MyPositionCharacteristic);\n                this.my.onSet(this.setMyPosition.bind(this));\n            }\n        } else {\n            this.obstructionDetected = service.getCharacteristic(Characteristics.ObstructionDetected);\n        }\n        if (service.testCharacteristic(Characteristics.On)) {\n            this.my = service.getCharacteristic(Characteristics.On);\n            service.removeCharacteristic(this.my);\n        }\n        this.positionState.updateValue(Characteristics.PositionState.STOPPED);\n        this.targetPosition.onSet(this.debounce(this.setTargetPosition, [0, 100]));\n        return service;\n    }\n\n    /**\n    * Triggered when Homekit try to modify the Characteristic.TargetPosition\n    * HomeKit '0' (Close) => 0% Deployment\n    * HomeKit '100' (Open) => 100% Deployment\n    **/\n    protected getTargetCommands(value): Command | Command[] {\n        if (this.stateless) {\n            if (value === 100) {\n                return new Command(this.reverse ? 'close' : 'open');\n            } else if (value === 0) {\n                return new Command(this.reverse ? 'open' : 'close');\n            } else {\n                if (this.movementDuration > 0) {\n                    const delta = value - Number(this.currentPosition!.value);\n                    if (this.reverse) {\n                        return new Command(delta > 0 ? 'close' : 'open');\n                    } else {\n                        return new Command(delta > 0 ? 'open' : 'close');\n                    }\n                } else {\n                    return new Command('my');\n                }\n            }\n        } else {\n            return new Command('setClosure', this.reversedValue(value));\n        }\n    }\n\n    /**\n    * Triggered when Homekit try to modify the Characteristic.TargetPosition\n    * HomeKit '0' (Close) => 100% Closure\n    * HomeKit '100' (Open) => 0% Closure\n    **/\n    async setTargetPosition(value) {\n        if (this.cancelTimeout !== null) {\n            clearTimeout(this.cancelTimeout);\n        }\n        const standalone = this.stateless && this.movementDuration > 0 && value !== 100 && value !== 0;\n        const action = await this.executeCommands(this.getTargetCommands(value), standalone);\n        action.on('update', (state, data) => {\n            const positionState = (value === 100 || value > (this.currentPosition?.value || 0)) ?\n                Characteristics.PositionState.INCREASING :\n                Characteristics.PositionState.DECREASING;\n            switch (state) {\n                case ExecutionState.IN_PROGRESS:\n                    if (standalone) {\n                        const delta = value - Number(this.currentPosition!.value);\n                        const duration = this.offsetMovementDuration * 1000 + Math.round(this.movementDuration * Math.abs(delta) * 1000 / 100);\n                        this.info('Will stop movement in ' + duration + ' millisec');\n                        this.cancelTimeout = setTimeout(() => {\n                            this.cancelTimeout = null;\n                            if (this.isIdle) {\n                                this.executeCommands(new Command('stop'), true);\n                            } else {\n                                this.cancelExecution().catch(this.error.bind(this));\n                            }\n                        }, duration);\n                    }\n                    this.positionState?.updateValue(positionState);\n                    break;\n                case ExecutionState.COMPLETED:\n                    this.positionState?.updateValue(Characteristics.PositionState.STOPPED);\n                    if (this.stateless) {\n                        if (this.defaultPosition) {\n                            this.currentPosition?.updateValue(this.defaultPosition);\n                            this.targetPosition?.updateValue(this.defaultPosition);\n                        } else {\n                            this.currentPosition?.updateValue(value);\n                        }\n                    } else {\n                        this.obstructionDetected?.updateValue(false);\n                    }\n                    if (this.blindsOnRollerShutter && value < 98) {\n                        this.executeCommands(new Command('setClosure', value + 2));\n                    }\n                    break;\n                case ExecutionState.FAILED:\n                    if (this.stateless && data.failureType === 'CMDCANCELLED' && this.movementDuration > 0) {\n                        if (this.defaultPosition) {\n                            this.currentPosition?.updateValue(this.defaultPosition);\n                            this.targetPosition?.updateValue(this.defaultPosition);\n                        } else {\n                            this.currentPosition?.updateValue(value);\n                        }\n                    }\n                    this.positionState?.updateValue(Characteristics.PositionState.STOPPED);\n                    this.obstructionDetected?.updateValue(data.failureType === 'WHILEEXEC_BLOCKED_BY_HAZARD');\n                    if (!this.device.hasState('core:TargetClosureState') && this.currentPosition) {\n                        this.targetPosition?.updateValue(this.currentPosition.value);\n                    }\n                    break;\n            }\n        });\n    }\n\n    /**\n    * Set My position\n    **/\n    async setMyPosition(value) {\n        if (!value) {\n            return;\n        }\n        const action = await this.executeCommands(new Command('my'));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    this.my?.updateValue(0);\n                    if (this.stateless) {\n                        if (this.defaultPosition) {\n                            this.currentPosition?.updateValue(this.defaultPosition);\n                            this.targetPosition?.updateValue(this.defaultPosition);\n                        } else {\n                            this.currentPosition?.updateValue(50);\n                            this.targetPosition?.updateValue(50);\n                        }\n                    }\n                    break;\n                case ExecutionState.FAILED:\n                    this.my?.updateValue(0);\n                    this.obstructionDetected?.updateValue(data.failureType === 'WHILEEXEC_BLOCKED_BY_HAZARD');\n                    if (!this.device.hasState('core:TargetClosureState') && this.currentPosition) {\n                        this.targetPosition?.updateValue(this.currentPosition.value);\n                    }\n                    break;\n            }\n        });\n    }\n\n    protected reversedValue(value) {\n        return this.reverse ? value : (100 - value);\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:ClosureState':\n                this.currentPosition?.updateValue(this.reversedValue(value));\n                if (!this.device.hasState('core:TargetClosureState') && this.isIdle) {\n                    this.targetPosition?.updateValue(this.reversedValue(value));\n                }\n                break;\n            case 'core:TargetClosureState':\n                this.targetPosition?.updateValue(this.reversedValue(value));\n                if (!this.device.hasState('core:ClosureState')) {\n                    this.currentPosition?.updateValue(this.reversedValue(value));\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Screen.ts",
    "content": "import RollerShutter from './RollerShutter';\n\nexport default class Screen extends RollerShutter {\n   \n}"
  },
  {
    "path": "src/mappers/Shutter.ts",
    "content": "import RollerShutter from './RollerShutter';\n\nexport default class Shutter extends RollerShutter {\n\n}"
  },
  {
    "path": "src/mappers/Siren.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, CharacteristicSetCallback } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Mapper from '../Mapper';\n\nexport default class Siren extends Mapper {\n    protected mute: Characteristic | undefined;\n    protected volume: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.Speaker);\n        this.mute = service.getCharacteristic(Characteristics.Mute);\n        this.volume = service.getCharacteristic(Characteristics.Volume);\n\n        this.mute.onSet(this.setMute.bind(this));\n        this.volume.onSet(this.setVolume.bind(this));\n\n        this.mute.updateValue(true);\n        return service;\n    }\n\n    protected getMuteCommands(value): Command | Array<Command> {\n        return new Command(value ? 'off' : 'on');\n    }\n\n    protected async setMute(value) {\n        const action = await this.executeCommands(this.getMuteCommands(value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    break;\n                case ExecutionState.FAILED:\n                    break;\n            }\n        });\n    }\n\n    protected getVolumeCommands(value): Command | Array<Command> {\n        return new Command('setVolume', value);\n    }\n\n    protected async setVolume(value) {\n        const action = await this.executeCommands(this.getVolumeCommands(value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    break;\n                case ExecutionState.FAILED:\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:OnOffState':\n                this.mute?.updateValue(value === 'off');\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/SmokeSensor.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class SmokeSensor extends Mapper {\n    protected smoke: Characteristic | undefined;\n    protected active: Characteristic | undefined;\n    protected fault: Characteristic | undefined;\n    protected battery: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.SmokeSensor);\n        this.smoke = service.getCharacteristic(Characteristics.SmokeDetected);\n        if (\n            this.device.hasState('core:SensorDefectState') ||\n            this.device.hasState('io:SensorDefMaintenanceSensorPartBatteryStateectState') ||\n            this.device.hasState('io:MaintenanceRadioPartBatteryState') ||\n            this.device.hasState('io:SensorRoomState')\n        ) {\n            this.fault = service.getCharacteristic(Characteristics.StatusFault);\n            this.battery = service.getCharacteristic(Characteristics.StatusLowBattery);\n        }\n        if (this.device.hasState('core:StatusState')) {\n            this.active = service.getCharacteristic(Characteristics.StatusActive);\n        }\n        return service;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:StatusState':\n                this.active?.updateValue(value === 'available');\n                break;\n            case 'core:SmokeState':\n                this.smoke?.updateValue(value === 'detected');\n                break;\n            case 'core:SensorDefectState':\n                switch (value) {\n                    case 'lowBattery':\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_LOW);\n                        break;\n                    case 'maintenanceRequired':\n                    case 'dead':\n                        this.fault?.updateValue(Characteristics.StatusFault.GENERAL_FAULT);\n                        break;\n                    case 'noDefect':\n                        this.fault?.updateValue(Characteristics.StatusFault.NO_FAULT);\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_NORMAL);\n                        break;\n                }\n                break;\n            case 'io:MaintenanceRadioPartBatteryState':\n            case 'io:MaintenanceSensorPartBatteryState':\n                switch (value) {\n                    case 'absence':\n                    case 'normal':\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_NORMAL);\n                        break;\n                    case 'low':\n                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_LOW);\n                        break;\n                }\n                break;\n            case 'io:SensorRoomState':\n                switch (value) {\n                    case 'clean':\n                        this.fault?.updateValue(Characteristics.StatusFault.NO_FAULT);\n                        break;\n                    case 'dirty':\n                        this.fault?.updateValue(Characteristics.StatusFault.GENERAL_FAULT);\n                        break;\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/SwingingShutter.ts",
    "content": "import RollerShutter from './RollerShutter';\n\nexport default class SwingingShutter extends RollerShutter {\n  \n}"
  },
  {
    "path": "src/mappers/TemperatureSensor.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class TemperatureSensor extends Mapper {\n    protected temperature: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.TemperatureSensor);\n        this.temperature = service.getCharacteristic(Characteristics.CurrentTemperature);\n        return service;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:TemperatureState':\n                this.temperature?.updateValue(value > 200 ? (value - 273.15) : value);\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/VenetianBlind.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport RollerShutter from './RollerShutter';\n\nexport default class VenetianBlind extends RollerShutter {\n    protected currentAngle: Characteristic | undefined;\n    protected targetAngle: Characteristic | undefined;\n\n    protected blindMode;\n\n    protected applyConfig(config) {\n        super.applyConfig(config);\n        this.blindMode = config['blindMode'] || false;\n    }\n\n    protected registerMainService() {\n        const service = super.registerMainService();\n\n        if (!this.stateless) {\n            this.currentAngle = service?.getCharacteristic(Characteristics.CurrentHorizontalTiltAngle);\n            this.targetAngle = service?.getCharacteristic(Characteristics.TargetHorizontalTiltAngle);\n            this.targetAngle?.setProps({ minStep: 10 });\n            this.targetAngle?.onSet(this.debounce(this.setTargetAnglePosition));\n\n            if (this.blindMode && this.currentAngle) {\n                service?.removeCharacteristic(this.currentAngle);\n            }\n            if (this.blindMode && this.targetAngle) {\n                service?.removeCharacteristic(this.targetAngle);\n            }\n        }\n        return service;\n    }\n\n    protected orientationToAngle(value) {\n        return Math.round((value * 1.8) - 90);\n    }\n\n    protected angleToOrientation(value) {\n        return Math.round((value + 90) / 1.8);\n    }\n\n    protected getTargetCommands(value): Command | Command[] {\n        if (this.stateless) {\n            if (value === 100) {\n                return new Command('open');\n            } else if (value === 0) {\n                return new Command('close');\n            } else {\n                if (this.movementDuration > 0) {\n                    const delta = value - Number(this.currentPosition!.value);\n                    return new Command(delta > 0 ? 'open' : 'close');\n                } else {\n                    return new Command('my');\n                }\n            }\n        } else if (this.blindMode) {\n            if (value === 100) {\n                return new Command('open');\n            } else if (this.device.hasCommand('setClosureAndOrientation')) {\n                return new Command('setClosureAndOrientation', [100, this.reversedValue(value)]);\n            } else {\n                return [\n                    new Command('setClosure', 100),\n                    new Command('setOrientation', this.reversedValue(value)),\n                ];\n            }\n        } else if (this.device.hasCommand('setClosureAndOrientation')) {\n            return new Command('setClosureAndOrientation', [\n                this.reversedValue(value),\n                this.angleToOrientation(this.targetAngle?.value),\n            ]);\n        } else {\n            const commands = [\n                new Command('setClosure', this.reversedValue(value)),\n            ];\n            if (this.device.hasCommand('setOrientation')) {\n                commands.push(new Command('setOrientation', this.angleToOrientation(this.targetAngle?.value)));\n            }\n            return commands;\n        }\n    }\n\n    protected getTargetAngleCommands(value) {\n        if (this.stateless) {\n            return [];\n        } else if (this.device.hasCommand('setClosureAndOrientation')) {\n            return new Command('setClosureAndOrientation', [\n                this.reversedValue(this.targetPosition?.value),\n                this.angleToOrientation(value),\n            ]);\n        } else {\n            const commands = [\n                new Command('setClosure', this.reversedValue(this.targetPosition?.value)),\n            ];\n            if (this.device.hasCommand('setOrientation')) {\n                commands.push(new Command('setOrientation', this.angleToOrientation(value)));\n            }\n            return commands;\n        }\n    }\n\n    async setTargetAnglePosition(value) {\n        const action = await this.executeCommands(this.getTargetAngleCommands(value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.FAILED:\n                    if (this.currentAngle) {\n                        this.targetAngle?.updateValue(this.currentAngle.value);\n                    }\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name, value) {\n        if (this.blindMode) {\n            switch (name) {\n                case 'core:OpenClosedState':\n                case 'core:SlateOrientationState':\n                    if (this.device.get('core:OpenClosedState') === 'closed') {\n                        const position = this.reversedValue(this.device.get('core:SlateOrientationState'));\n                        const target = Number(this.targetPosition?.value);\n                        if (Number.isInteger(position)) {\n                            this.currentPosition?.updateValue(position);\n                            if (this.isIdle || Math.round(position / 5) === Math.round(target / 5)) {\n                                this.targetPosition?.updateValue(position);\n                            }\n                        }\n                    } else {\n                        this.currentPosition?.updateValue(100);\n                        if (this.isIdle) {\n                            this.targetPosition?.updateValue(100);\n                        }\n                    }\n                    break;\n                default: break;\n            }\n        } else {\n            super.onStateChanged(name, value);\n\n            switch (name) {\n                case 'core:SlateOrientationState':\n                    this.currentAngle?.updateValue(this.orientationToAngle(value));\n                    if (this.isIdle) {\n                        this.targetAngle?.updateValue(this.orientationToAngle(value));\n                    }\n                    break;\n                default: break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/mappers/VentilationSystem/DimplexVentilationInletOutlet.ts",
    "content": "\nimport { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport VentilationSystem from '../VentilationSystem';\n\nexport default class DimplexVentilationInletOutlet extends VentilationSystem {\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        switch(value) {\n            case Characteristics.TargetAirPurifierState.AUTO:\n                return new Command('auto');\n            case Characteristics.TargetAirPurifierState.MANUAL:\n            default:\n                return new Command('max');\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/VentilationSystem.ts",
    "content": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Mapper from '../Mapper';\n\nexport default class VentilationSystem extends Mapper {\n    protected active: Characteristic | undefined;\n    protected currentState: Characteristic | undefined;\n    protected targetState: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.AirPurifier);\n        this.active = service.getCharacteristic(Characteristics.Active);\n        this.currentState = service.getCharacteristic(Characteristics.CurrentAirPurifierState);\n        this.targetState = service.getCharacteristic(Characteristics.TargetAirPurifierState);\n\n        this.targetState?.onSet(this.setTargetState.bind(this));\n        return service;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        switch (value) {\n            case Characteristics.TargetAirPurifierState.AUTO:\n                return new Command('setAirDemandMode', 'auto');\n            case Characteristics.TargetAirPurifierState.MANUAL:\n            default:\n                return new Command('setAirDemandMode', 'boost');\n        }\n    }\n\n    protected async setTargetState(value) {\n        const action = await this.executeCommands(this.getTargetStateCommands(value));\n        action.on('update', (state) => {\n            switch (state) {\n                case ExecutionState.COMPLETED:\n                    if (this.stateless) {\n                        this.currentState?.updateValue(value);\n                    }\n                    break;\n                case ExecutionState.FAILED:\n                    if (this.currentState) {\n                        this.targetState?.updateValue(this.currentState.value);\n                    }\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'io:AirDemandModeState':\n                switch (value) {\n                    case 'auto':\n                        this.targetState?.updateValue(Characteristics.TargetAirPurifierState.AUTO);\n                        break;\n                    default:\n                        this.targetState?.updateValue(Characteristics.TargetAirPurifierState.MANUAL);\n                        break;\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/WaterHeatingSystem/AtlanticPassAPCDHW.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport WaterHeatingSystem from '../WaterHeatingSystem';\n\nexport default class AtlanticPassAPCDHW extends WaterHeatingSystem {\n    protected THERMOSTAT_CHARACTERISTICS = ['eco', 'prog'];\n\n    protected registerServices() {\n        const services = super.registerServices();\n        if (this.device.hasCommand('setBoostOnOffState')) {\n            const boost = this.registerSwitchService('boost');\n            services.push(boost);\n        }\n        return services;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> {\n        const commands: Array<Command> = [];\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                commands.push(new Command('setDHWOnOffState', 'on'));\n                if (this.prog?.value) {\n                    commands.push(new Command('setPassAPCDHWMode', 'internalScheduling'));\n                } else {\n                    if (this.eco?.value) {\n                        commands.push(new Command('setPassAPCDHWMode', 'eco'));\n                    } else {\n                        commands.push(new Command('setPassAPCDHWMode', 'comfort'));\n                    }\n                }\n                break;\n\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                commands.push(new Command('setDHWOnOffState', 'off'));\n                break;\n        }\n        return commands;\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> {\n        if (this.targetState?.value === Characteristics.TargetHeatingCoolingState.COOL) {\n            return new Command('setEcoTargetDHWTemperature', value);\n        } else {\n            return new Command('setComfortTargetDHWTemperature', value);\n        }\n    }\n\n    protected getOnCommands(value): Command | Array<Command> {\n        return new Command('setBoostOnOffState', value ? 'on' : 'off');\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:TargetDHWTemperatureState':\n                this.onTemperatureUpdate(value);\n                //this.postpone(this.computeStates);\n                break;\n            case 'core:DHWOnOffState':\n            case 'io:PassAPCDHWModeState':\n            case 'io:PassAPCDHWProfileState':\n            case 'core:ComfortTargetDHWTemperatureState':\n            case 'core:EcoTargetDHWTemperatureState':\n                this.postpone(this.computeStates);\n                break;\n            case 'core:BoostOnOffState':\n                this.on?.updateValue(value === 'on');\n                break;\n            default:\n                super.onStateChanged(name, value);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n        if (this.device.get('core:DHWOnOffState') === 'on') {\n            targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n            switch (this.device.get('io:PassAPCDHWModeState')) {\n                case 'off':\n                case 'stop':\n                    targetState = Characteristics.TargetHeatingCoolingState.OFF;\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n                    this.targetTemperature?.updateValue(this.device.get('core:TargetDHWTemperatureState'));\n                    break;\n                case 'internalScheduling':\n                case 'externalScheduling':\n                    this.prog?.updateValue(true);\n                    if (this.device.get('io:PassAPCDHWProfileState') === 'comfort') {\n                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                        this.targetTemperature?.updateValue(this.device.get('core:ComfortTargetDHWTemperatureState'));\n                    } else {\n                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                        this.targetTemperature?.updateValue(this.device.get('core:EcoTargetDHWTemperatureState'));\n                    }\n                    break;\n                case 'comfort':\n                    this.prog?.updateValue(false);\n                    this.eco?.updateValue(false);\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                    this.targetTemperature?.updateValue(this.device.get('core:ComfortTargetDHWTemperatureState'));\n                    break;\n                case 'eco':\n                    this.prog?.updateValue(false);\n                    this.eco?.updateValue(true);\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                    this.targetTemperature?.updateValue(this.device.get('core:EcoTargetDHWTemperatureState'));\n                    break;\n            }\n        } else {\n            targetState = Characteristics.TargetHeatingCoolingState.OFF;\n            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n            this.targetTemperature?.updateValue(this.device.get('core:TargetDHWTemperatureState'));\n        }\n        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/WaterHeatingSystem/DomesticHotWaterProduction/AtlanticDomesticHotWaterProductionV2_SPLIT_IOComponent.ts",
    "content": "import { Service } from 'homebridge';\nimport { Command } from 'overkiz-client';\nimport { Characteristics } from '../../../Platform';\nimport DomesticHotWaterProduction from '../DomesticHotWaterProduction';\n\nexport default class AtlanticDomesticHotWaterProductionV2_SPLIT_IOComponent extends DomesticHotWaterProduction {\n    protected THERMOSTAT_CHARACTERISTICS = ['eco'];\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.HEAT,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected registerMainService(): Service {\n        const service = super.registerMainService();\n        this.targetTemperature?.setProps({\n            minValue: 50.0,\n            maxValue: 54.5,\n            validValues: [50, 52, 54, 54.5, 55],\n            minStep: 2,\n        });\n        return service;\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> {\n        const safeValue = value === 54 ? 54.5 : value;\n        return new Command('setTargetTemperature', safeValue);\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> | undefined {\n        const commands = Array<Command>();\n        if (this.targetState?.value === Characteristics.TargetHeatingCoolingState.OFF) {\n            commands.push(new Command('setCurrentOperatingMode', { 'relaunch': 'off', 'absence': 'off' }));\n        }\n        switch (value) {\n            case Characteristics.TargetHeatingCoolingState.AUTO:\n                commands.push(new Command('setDHWMode', 'autoMode'));\n                break;\n            case Characteristics.TargetHeatingCoolingState.HEAT:\n                if (this.eco?.value) {\n                    commands.push(new Command('setDHWMode', 'manualEcoActive'));\n                } else {\n                    commands.push(new Command('setDHWMode', 'manualEcoInactive'));\n                }\n                break;\n            case Characteristics.TargetHeatingCoolingState.OFF:\n                commands.push(new Command('setCurrentOperatingMode', { 'relaunch': 'off', 'absence': 'on' }));\n                break;\n        }\n        return commands;\n    }\n\n    protected getOnCommands(value): Command | Array<Command> {\n        return new Command('setCurrentOperatingMode', { 'relaunch': value ? 'on' : 'off', 'absence': 'off' });\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'io:MiddleWaterTemperatureState':\n                this.currentTemperature?.updateValue(value);\n                break;\n            case 'core:TargetTemperatureState':\n                this.targetTemperature?.updateValue(value);\n                break;\n            case 'io:DHWModeState':\n            case 'core:OperatingModeState':\n                this.postpone(this.computeStates);\n                break;\n        }\n    }\n\n    protected computeStates() {\n        let targetState;\n        const operatingMode = this.device.get('core:OperatingModeState');\n        this.on?.updateValue(operatingMode.relaunch !== 'off');\n        if (operatingMode.absence === 'off') {\n            switch (this.device.get('io:DHWModeState')) {\n                case 'autoMode':\n                    targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n                    break;\n                case 'manualEcoInactive':\n                    this.eco?.updateValue(false);\n                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                    break;\n                case 'manualEcoActive':\n                    this.eco?.updateValue(true);\n                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                    break;\n            }\n\n            const powerHeatPumpState = this.device.get('io:PowerHeatPumpState');\n            const powerHeatElectricalState = this.device.get('io:PowerHeatElectricalState');\n            if (powerHeatElectricalState > 100 || powerHeatPumpState > 100) {\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n            } else {\n                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n            }\n\n        } else {\n            targetState = Characteristics.TargetHeatingCoolingState.OFF;\n            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n        }\n        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}\n"
  },
  {
    "path": "src/mappers/WaterHeatingSystem/DomesticHotWaterProduction.ts",
    "content": "import { Characteristics } from '../../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport WaterHeatingSystem from '../WaterHeatingSystem';\nimport { CurrentShowerCharacteristic, TargetShowerCharacteristic } from '../../CustomCharacteristics';\n\nexport default class DomesticHotWaterProduction extends WaterHeatingSystem {\n    protected THERMOSTAT_CHARACTERISTICS = ['eco'];\n    protected currentShower: Characteristic | undefined;\n    protected targetShower: Characteristic | undefined;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.HEAT,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected registerMainService() {\n        const service = super.registerMainService();\n        service.addOptionalCharacteristic(TargetShowerCharacteristic);\n        service.addOptionalCharacteristic(CurrentShowerCharacteristic);\n        if (this.device.hasState('core:NumberOfShowerRemainingState')) {\n            this.currentShower = service.getCharacteristic(CurrentShowerCharacteristic);\n            this.targetShower = service.getCharacteristic(TargetShowerCharacteristic);\n            this.targetShower.setProps({\n                minValue: this.device.getNumber('core:MinimalShowerManualModeState'),\n                maxValue: this.device.getNumber('core:MaximalShowerManualModeState'),\n            });\n            this.targetShower.onSet(this.setTargetShower.bind(this));\n        }\n        return service;\n    }\n\n    protected registerServices() {\n        const services = super.registerServices();\n        const boost = this.registerSwitchService('boost');\n        services.push(boost);\n        return services;\n    }\n\n    protected getTargetStateCommands(value): Command | Array<Command> | undefined {\n        const commands = Array<Command>();\n        if (this.device.hasCommand('setDHWMode')) {\n            if (this.targetState?.value === Characteristics.TargetHeatingCoolingState.OFF) {\n                commands.push(new Command('setAbsenceMode', 'off'));\n            }\n            switch (value) {\n                case Characteristics.TargetHeatingCoolingState.AUTO:\n                    commands.push(new Command('setDHWMode', 'autoMode'));\n                    break;\n                case Characteristics.TargetHeatingCoolingState.HEAT:\n                    if (this.eco?.value) {\n                        commands.push(new Command('setDHWMode', 'manualEcoActive'));\n                    } else {\n                        commands.push(new Command('setDHWMode', 'manualEcoInactive'));\n                    }\n                    break;\n                case Characteristics.TargetHeatingCoolingState.OFF:\n                    commands.push(new Command('setAbsenceMode', 'on'));\n                    break;\n            }\n            return commands;\n        } else if (this.device.hasCommand('setCurrentOperatingMode')) {\n            switch (value) {\n                case Characteristics.TargetHeatingCoolingState.AUTO:\n                    return new Command('setCurrentOperatingMode', { 'relaunch': 'off', 'absence': 'off' });\n                case Characteristics.TargetHeatingCoolingState.HEAT:\n                    return new Command('setCurrentOperatingMode', { 'relaunch': 'on', 'absence': 'off' });\n                case Characteristics.TargetHeatingCoolingState.OFF:\n                    return new Command('setCurrentOperatingMode', { 'relaunch': 'off', 'absence': 'on' });\n            }\n        } else if (this.device.hasCommand('setBoostModeDuration')) {\n            switch (value) {\n                case Characteristics.TargetHeatingCoolingState.AUTO:\n                    return [\n                        new Command('setBoostModeDuration', 0),\n                        new Command('setAwayModeDuration', 0),\n                    ];\n                case Characteristics.TargetHeatingCoolingState.HEAT:\n                    return new Command('setBoostModeDuration', 1);\n                case Characteristics.TargetHeatingCoolingState.OFF:\n                    return new Command('setAwayModeDuration', 30);\n            }\n        } else if (this.device.hasCommand('setBoostMode')) {\n            switch (value) {\n                case Characteristics.TargetHeatingCoolingState.AUTO:\n                    return [\n                        new Command('setBoostMode', 'off'),\n                        new Command('setAbsenceMode', 'off'),\n                    ];\n                case Characteristics.TargetHeatingCoolingState.HEAT:\n                    return new Command('setBoostMode', 'on');\n                case Characteristics.TargetHeatingCoolingState.OFF:\n                    return new Command('setAbsenceMode', 'on');\n            }\n        }\n    }\n\n    protected getTargetTemperatureCommands(value): Command | Array<Command> {\n        return new Command('setWaterTargetTemperature', value);\n    }\n\n    protected getOnCommands(value): Command | Array<Command> {\n        return new Command('setBoostMode', value ? 'on' : 'off');\n    }\n\n    async setTargetShower(value) {\n        const previous = this.targetShower?.value;\n        const action = await this.executeCommands(new Command('setExpectedNumberOfShower', value));\n        action.on('update', (state, data) => {\n            switch (state) {\n                case ExecutionState.FAILED:\n                    if (previous) {\n                        this.targetShower?.updateValue(previous);\n                    }\n                    break;\n            }\n        });\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'io:DHWBoostModeState':\n            case 'modbuslink:DHWBoostModeState':\n                this.on?.updateValue(value !== 'off');\n                break;\n            case 'core:WaterTemperatureState':\n                if (!this.device.hasState('io:MiddleWaterTemperatureState')) {\n                    this.currentTemperature?.updateValue(value);\n                }\n                break;\n            case 'io:MiddleWaterTemperatureState':\n            case 'modbuslink:MiddleWaterTemperatureState':\n                this.currentTemperature?.updateValue(value);\n                break;\n            case 'core:TargetTemperatureState':\n            case 'core:WaterTargetTemperatureState':\n                this.targetTemperature?.updateValue(value);\n                break;\n            case 'io:DHWModeState':\n            case 'io:DHWAbsenceModeState':\n                this.postpone(this.computeStates, 'io');\n                break;\n            case 'modbuslink:DHWModeState':\n            case 'modbuslink:DHWAbsenceModeState':\n                this.postpone(this.computeStates, 'modbuslink');\n                break;\n            case 'core:NumberOfShowerRemainingState':\n                this.currentShower?.updateValue(value);\n                break;\n            case 'core:ExpectedNumberOfShowerState':\n                this.targetShower?.updateValue(value);\n                break;\n        }\n    }\n\n    protected computeStates(protcol) {\n        let targetState;\n        if (this.device.get(protcol+':DHWAbsenceModeState') === 'off') {\n            switch (this.device.get(protcol+':DHWModeState')) {\n                case 'autoMode':\n                    targetState = Characteristics.TargetHeatingCoolingState.AUTO;\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                    break;\n                case 'manualEcoInactive':\n                    this.eco?.updateValue(false);\n                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);\n                    break;\n                case 'manualEcoActive':\n                    this.eco?.updateValue(true);\n                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;\n                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);\n                    break;\n            }\n        } else {\n            targetState = Characteristics.TargetHeatingCoolingState.OFF;\n            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);\n        }\n        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {\n            this.targetState.updateValue(targetState);\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/WaterHeatingSystem/DomesticHotWaterTank.ts",
    "content": "import { Service } from 'homebridge';\nimport { Command } from 'overkiz-client';\nimport WaterHeatingSystem from '../WaterHeatingSystem';\n\nexport default class DomesticHotWaterTank extends WaterHeatingSystem {\n    protected registerMainService(): Service {\n        return this.registerSwitchService('boost');\n    }\n\n    protected getOnCommands(value): Command | Array<Command> {\n        return new Command('setForceHeating', value ? 'on' : 'off');\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'io:ForceHeatingState':\n                this.on?.updateValue(value === 'on');\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/WaterHeatingSystem.ts",
    "content": "import { Characteristics } from '../Platform';\nimport { Service } from 'homebridge';\nimport HeatingSystem from './HeatingSystem';\n\nexport default class WaterHeatingSystem extends HeatingSystem {\n    protected MIN_TEMP = 45;\n    protected MAX_TEMP = 65;\n    protected TARGET_MODES = [\n        Characteristics.TargetHeatingCoolingState.AUTO,\n        Characteristics.TargetHeatingCoolingState.OFF,\n    ];\n\n    protected registerMainService(): Service {\n        const service = super.registerMainService();\n        service.setPrimaryService(true);\n        this.targetTemperature?.setProps({ minStep: 1 });\n        return service;\n    }\n}"
  },
  {
    "path": "src/mappers/WaterSensor.ts",
    "content": "import { Characteristic } from 'homebridge';\nimport { Characteristics, Services } from '../Platform';\nimport Mapper from '../Mapper';\n\nexport default class WaterSensor extends Mapper {\n    protected leak: Characteristic | undefined;\n\n    protected registerMainService() {\n        const service = this.registerService(Services.LeakSensor);\n        this.leak = service.getCharacteristic(Characteristics.LeakDetected);\n        return service;\n    }\n\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:WaterDetectionState':\n                this.leak?.updateValue(\n                    value === 'detected' ? Characteristics.LeakDetected.LEAK_DETECTED : Characteristics.LeakDetected.LEAK_NOT_DETECTED\n                );\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/mappers/Window.ts",
    "content": "import RollerShutter from './RollerShutter';\nimport { Characteristics, Services } from '../Platform';\n\nexport default class Window extends RollerShutter {\n    protected registerMainService() {\n        const service = this.registerService(Services.Window);\n        this.currentPosition = service.getCharacteristic(Characteristics.CurrentPosition);\n        this.targetPosition = service.getCharacteristic(Characteristics.TargetPosition);\n        this.positionState = service.getCharacteristic(Characteristics.PositionState);\n        if (this.stateless) {\n            this.currentPosition.updateValue(this.initPosition);\n            this.targetPosition.updateValue(this.initPosition);\n        } else {\n            this.obstructionDetected = service.getCharacteristic(Characteristics.ObstructionDetected);\n        }\n        this.positionState.updateValue(Characteristics.PositionState.STOPPED);\n        this.targetPosition.onSet(this.debounce(this.setTargetPosition, [0, 100]));\n        return service;\n    }\n}"
  },
  {
    "path": "src/mappers/WindowHandle.ts",
    "content": "import { Characteristics } from '../Platform';\nimport ContactSensor from './ContactSensor';\n\nexport default class WindowHandle extends ContactSensor {\n    protected onStateChanged(name: string, value) {\n        switch (name) {\n            case 'core:ThreeWayHandleDirectionState':\n                switch (value) {\n                    case 'closed':\n                        this.state?.updateValue(Characteristics.ContactSensorState.CONTACT_DETECTED);\n                        break;\n                    case 'tilt':\n                    case 'open':\n                        this.state?.updateValue(Characteristics.ContactSensorState.CONTACT_NOT_DETECTED);\n                        break;\n                }\n                break;\n            case 'core:OpenClosedState':\n                switch (value) {\n                    case 'closed':\n                        this.state?.updateValue(Characteristics.ContactSensorState.CONTACT_DETECTED);\n                        break;\n                    case 'open':\n                        this.state?.updateValue(Characteristics.ContactSensorState.CONTACT_NOT_DETECTED);\n                        break;\n                }\n                break;\n        }\n    }\n}"
  },
  {
    "path": "src/settings.ts",
    "content": "\n/**\n * This is the name of the platform that users will use to register the plugin in the Homebridge config.json\n */\nexport const PLATFORM_NAME = 'Tahoma';\n\n/**\n * This must match the name of your plugin as defined the package.json\n */\nexport const PLUGIN_NAME = 'homebridge-tahoma';"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2018\", // ~node10\n    \"module\": \"commonjs\",\n    \"lib\": [\n      \"es2015\",\n      \"es2016\",\n      \"es2017\",\n      \"es2018\"\n    ],\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"noImplicitAny\": false,\n    \"resolveJsonModule\": true\n  },\n  \"include\": [\n    \"src/\",\n    \"src/lang/*.json\"\n  ],\n  \"exclude\": [\n    \"**/*.spec.ts\"\n  ]\n}"
  }
]