Full Code of dubocr/homebridge-tahoma for AI

master 9d8e14f898a1 cached
102 files
224.9 KB
48.5k tokens
352 symbols
1 requests
Download .txt
Showing preview only (250K chars total). Download the full file or copy to clipboard to get everything.
Repository: dubocr/homebridge-tahoma
Branch: master
Commit: 9d8e14f898a1
Files: 102
Total size: 224.9 KB

Directory structure:
gitextract_f_m6ihgp/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── config.yml
│   └── workflows/
│       ├── new-release.yml
│       └── stale-issue.yml
├── .gitignore
├── .npmignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── config.schema.json
├── eslint.config.mjs
├── nodemon.json
├── package.json
├── platform.schema.json
├── src/
│   ├── CustomCharacteristics.ts
│   ├── Mapper.ts
│   ├── Platform.ts
│   ├── SceneMapper.ts
│   ├── colors.ts
│   ├── index.ts
│   ├── lang/
│   │   ├── en.json
│   │   └── fr.json
│   ├── mappers/
│   │   ├── AdjustableSlatsRollerShutter.ts
│   │   ├── AirSensor/
│   │   │   ├── CO2Sensor.ts
│   │   │   └── RelativeHumiditySensor.ts
│   │   ├── AirSensor.ts
│   │   ├── Alarm/
│   │   │   ├── MyFoxAlarmController.ts
│   │   │   └── TSKAlarmController.ts
│   │   ├── Alarm.ts
│   │   ├── Awning/
│   │   │   └── PositionableHorizontalAwningUno.ts
│   │   ├── Awning.ts
│   │   ├── ConsumptionSensor.ts
│   │   ├── ContactSensor.ts
│   │   ├── Curtain.ts
│   │   ├── DoorLock.ts
│   │   ├── ElectricitySensor/
│   │   │   └── CumulativeElectricPowerConsumptionSensor.ts
│   │   ├── ElectricitySensor.ts
│   │   ├── EvoHome/
│   │   │   ├── DHWSetPoint.ts
│   │   │   ├── EvoHomeController.ts
│   │   │   └── HeatingSetPoint.ts
│   │   ├── ExteriorHeatingSystem/
│   │   │   └── DimmerExteriorHeating.ts
│   │   ├── ExteriorHeatingSystem.ts
│   │   ├── ExteriorScreen.ts
│   │   ├── ExteriorVenetianBlind.ts
│   │   ├── GarageDoor.ts
│   │   ├── Gate.ts
│   │   ├── Generic/
│   │   │   ├── CyclicGeneric.ts
│   │   │   ├── DimmerOnOff.ts
│   │   │   ├── RTSGeneric.ts
│   │   │   └── RTSGeneric4T.ts
│   │   ├── HeatingSystem/
│   │   │   ├── AtlanticElectricalHeater.ts
│   │   │   ├── AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint.ts
│   │   │   ├── AtlanticElectricalTowelDryer.ts
│   │   │   ├── AtlanticPassAPCBoiler.ts
│   │   │   ├── AtlanticPassAPCHeatPump.ts
│   │   │   ├── AtlanticPassAPCHeatingAndCoolingZone.ts
│   │   │   ├── AtlanticPassAPCHeatingZone.ts
│   │   │   ├── AtlanticPassAPCZoneControl.ts
│   │   │   ├── ProgrammableAndProtectableThermostatSetPoint.ts
│   │   │   ├── SomfyHeatingTemperatureInterface.ts
│   │   │   ├── SomfyThermostat.ts
│   │   │   ├── ThermostatSetPoint.ts
│   │   │   └── ValveHeatingTemperatureInterface.ts
│   │   ├── HeatingSystem.ts
│   │   ├── HitachiHeatingSystem/
│   │   │   ├── HitachiAirToAirHeatPump.ts
│   │   │   ├── HitachiAirToWaterHeatingZone.ts
│   │   │   ├── HitachiAirToWaterMainComponent.ts
│   │   │   └── HitachiDHW.ts
│   │   ├── HumiditySensor/
│   │   │   └── WaterDetectionSensor.ts
│   │   ├── HumiditySensor.ts
│   │   ├── Light.ts
│   │   ├── LightSensor.ts
│   │   ├── OccupancySensor.ts
│   │   ├── OnOff.ts
│   │   ├── Pergola/
│   │   │   ├── BioclimaticPergola.ts
│   │   │   └── PergolaHorizontalAwningUno.ts
│   │   ├── Pergola.ts
│   │   ├── RainSensor.ts
│   │   ├── RemoteController.ts
│   │   ├── RollerShutter/
│   │   │   ├── PositionableRollerShutterUno.ts
│   │   │   └── PositionableRollerShutterWithLowSpeedManagement.ts
│   │   ├── RollerShutter.ts
│   │   ├── Screen.ts
│   │   ├── Shutter.ts
│   │   ├── Siren.ts
│   │   ├── SmokeSensor.ts
│   │   ├── SwingingShutter.ts
│   │   ├── TemperatureSensor.ts
│   │   ├── VenetianBlind.ts
│   │   ├── VentilationSystem/
│   │   │   └── DimplexVentilationInletOutlet.ts
│   │   ├── VentilationSystem.ts
│   │   ├── WaterHeatingSystem/
│   │   │   ├── AtlanticPassAPCDHW.ts
│   │   │   ├── DomesticHotWaterProduction/
│   │   │   │   └── AtlanticDomesticHotWaterProductionV2_SPLIT_IOComponent.ts
│   │   │   ├── DomesticHotWaterProduction.ts
│   │   │   └── DomesticHotWaterTank.ts
│   │   ├── WaterHeatingSystem.ts
│   │   ├── WaterSensor.ts
│   │   ├── Window.ts
│   │   └── WindowHandle.ts
│   └── settings.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
contact_links:
  - name: Device integration and bug
    url: https://dev.duboc.pro/homebridge-tahoma
    about: 
      Start here for unsupported device or any command,state unexpected behaviour
  - name: Gestion d'un équipement et anomalies de fonctionnement
    url: https://dev.duboc.pro/homebridge-tahoma
    about: 
      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


================================================
FILE: .github/workflows/new-release.yml
================================================
name: Create Release
on:
  push:
    tags:  
      - v*

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Create GitHub release
        uses: Roang-zero1/github-create-release-action@master
        with:
          version_regex: ^v[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

================================================
FILE: .github/workflows/stale-issue.yml
================================================
name: Mark stale issues and pull requests

on:
  schedule:
    - cron: "30 1 * * *"

jobs:
  stale:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/stale@v3
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          stale-issue-message: >
            There hasn't been any activity on this issue recently.

            Please make sure to update to the latest Homebridge TaHoma version to see
            if that solves the issue.

            This issue will be closed in case of inactivity.
          days-before-stale: 90
          days-before-close: 120
          stale-issue-label: "inactive"
          exempt-issue-labels: "blocked,waiting"


================================================
FILE: .gitignore
================================================
# Ignore compiled code
dist
old

# ------------- Defaults ------------- #

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.pnp.*
.DS_Store


================================================
FILE: .npmignore
================================================
# Ignore source code
src

# ------------- Defaults ------------- #

# eslint
.eslintrc

# typescript
tsconfig.json

# vscode
.vscode

# nodemon
nodemon.json

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.pnp.*

================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": [
    "dbaeumer.vscode-eslint"
  ]
}

================================================
FILE: .vscode/settings.json
================================================
{
  "files.eol": "\n",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "editor.rulers": [ 140 ],
  "eslint.enable": true
}


================================================
FILE: CHANGELOG.md
================================================
## 2.2.54 (2022-12-20)

### Bug Fixes

* Fix nodejs compatibility to node v12.4.0

================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

================================================
FILE: README.md
================================================
# Overkiz (Somfy) - Homebridge-TaHoma

Homebridge plugin supporting Overkiz based platforms :

| Service code				| Vendor					| Product compatibility												|
|---------------------------|---------------------------|-------------------------------------------------------------------|
| `local`					| Somfy local API			| TaHoma, TaHoma Switch ([configure local API](#configure-local-api))												|
| `somfy_europe`			| Somfy Europe			 	| TaHoma, TaHoma Switch, Connexoon IO, Kit de connectivité Orange	|
| `somfy_australia`			| Somfy Australia	 		| Connexoon RTS and other products in Australia						|
| `somfy_north_america`		| Somfy North America 		| TaHoma and other product in North America							|
| `cozytouch`				| Atlantic, Thermor, Sauter | Cozytouch															|
| `flexom`					| Bouygues 					| Flexom															|
| `hi_kumo`					| Hitachi 					| Hi hi_kumo														|
| `rexel`					| Rexel					 	| Energeasy connectivité											|


# Installation

1. Install homebridge using: `npm install -g homebridge`
2. Install this plugin using: `npm install -g homebridge-tahoma`
3. Update your configuration file. See bellow for a sample.

# Configuration

Minimal configuration sample:
```
{
	"bridge": {
		...
	},

	"description": "...",

	"accessories": [],

	"platforms":[
		{
			"platform": "Tahoma",
			"name": "My TaHoma Box",

			"service": "somfy_europe",
			"user": "user@me.com",
			"password": "MyPassw0rd",
		}
	]
}
```

Configuration parameters:

| Parameter					| Type			| Default			| Note											|
|---------------------------|---------------|-------------------|-----------------------------------------------|
| `service`					| String		| 'somfy_europe'	| optional, service name  (see above)			|
| `user`					| String		| null				| mandatory, your service account username (or [gateway PIN / IP address](#configure-local-api))	|
| `password`				| String		| null				| mandatory, your service account password (or [API token](#configure-local-api))	|
| `pollingPeriod`			| Integer		| 30				| optional, bridge polling period in seconds	|
| `refreshPeriod`			| Integer		| 30				| optional, device states refresh period in minutes	|
| `exclude`					| String[]		| []				| optional, protocol, ui, widget or device name to exclude	|
| `exposeScenarios`			| Boolean		| false				| optional, expose scenarios as HomeKit switches. Could also specify a list of string corresponding to scenarios names to expose	|
| `devicesConfig`			| Object[]		| []				| optional list of device specific configuration (see below)	|

### Configure Local API
Local API service is available on TaHoma and TaHoma switch gateways.

**WARNING: Switching to local API will break your HomeKit configuration (automations) as local API device identifiers actually differs.**

To use Local API you will have to:
1. Activate `developer mode` ([www.somfy.com](https://www.somfy.com) > My Account > Activate developer mode) 
2. Generate API credentials at [https://dev.duboc.pro/homebridge-tahoma](https://dev.duboc.pro/homebridge-tahoma)
3. Configure the plugin service to Local API: `"service":"local"`

When 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)

For more information, browse [https://developer.somfy.com/developer-mode](https://developer.somfy.com/developer-mode)

# Specific device configuration

This option allows you to apply a specific configuation to device or group of device.
One configuration is composed of a `key` attribute containing device name, widget, uiClass, protocol or unique identifier and as many parameter depending of device type.

```
{
	"key": "Bedroom door",
	"param1": "value1"
	...
}
```

| Alarm parameters	| Type		| Default		| Note					|
|-------------------|-----------|---------------|-----------------------|
| `stayZones`		| String	| 'A'			| optional, active zones (A,B,C) in 'Stay' mode	|
| `nightZones`		| String	| 'B'			| optional, active zones (A,B,C) in 'Night' mode	|
| `occupancySensor`	| Boolean	| false			| optional, add an occupancy widget linked to the alarm	|

| WindowCovering parameters	| Type		| Default		| Note			|
|---------------------------|-----------|---------------|---------------|
| `defaultPosition`			| Integer	| 0				| optional, final position for UpDown rollershutter after any command	|
| `reverse`					| Boolean	| false			| optional, reverse up/down in case of bad mounting	|
| `lowSpeed`				| Boolean / String	| false			| 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.	|
| `blindMode`				| String	| null			| 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. |
| `blindsOnRollerShutter`	| Boolean	| false				| optional, when blinds are installed on roller shutter motors allow slats to stay horizontal (opened) at intermediate position |
| `movementDuration`		| Integer	| 0				| optional, duration of a full shutter movement from 'open' to 'close' in seconds. Will be used to approximate shutter intermediate position. (0 = disable feature) |

| GarageDoorOpener parameters	| Type			| Default		| Note				|
|-------------------------------|---------------|---------------|-------------------|
| `cyclic`						| Boolean		| false			| optional, restore closed state after `cycleDuration` seconds for stateless devices with cyclic behaviour	|
| `cycleDuration`				| Integer		| false			| optional, cycle duration (in seconds) for cyclic mode (default: 5 sec)				|
| `reverse`						| Boolean		| false			| optional, reverse up/down in case of bad mounting	|
| `pedestrianDuration`			| Integer		| 0				| optional, duration for pedestrian position for RTS gates	|

| HeatingSystem parameters		| Type			| Default		| Note				|
|-------------------------------|---------------|---------------|-------------------|
| `derogationDuration`			| Integer		| 1				| optional, duration (in hours) for derogation orders	|
| `comfort`						| Integer		| 19			| optional, comfort temperature used as display for heaters controled by pilot wire	|
| `eco`							| Integer		| 17			| optional, comfort temperature used as display for heaters controled by pilot wire		|


Full configuration example:
```
{
	"bridge": {
		...
	},

	"description": "...",

	"accessories": [],

	"platforms":[
		{
			"platform": "Tahoma",
			"name": "My Tahoma Bridge",

			"user": "user@me.com",
			"password": "MyPassw0rd",
			"service": "somfy_europe",
			"exclude": ["hue", "rts", "Main door", "Main door", "PositionableHorizontalAwning"],

			"devicesConfig": [
				{
					"key": "Alarm",
					"stayZones": "A,C"
				},
				{
					"key": "Bedroom blind",
					"blindMode": true
				},
				{
					"key": "GarageDoor",
					"reverse": true
				},
				{
					"key": "UpDownRollerShutter",
					"defaultPosition": 50
				}
			]
		}
	]
}
```

# Contribute

You are welcome to contribute to this plugin development by opening an issue in case of unexpected behaviour or unsupported device.

I 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.


================================================
FILE: config.schema.json
================================================
{
  "pluginAlias": "Tahoma",
  "pluginType": "platform",
  "singular": false,
  "schema": {
    "type": "object",
    "properties": {
      "name": {
        "title": "Name",
        "type": "string",
        "required": true,
        "default": "TaHoma"
      },
      "service": {
        "title": "Service",
        "type": "string",
        "default": "somfy_europe",
        "oneOf": [
          {
            "title": "Local API (TaHoma / Switch)",
            "enum": [
              "local"
            ]
          },
          {
            "title": "Somfy Europe (TaHoma / Switch / Connexoon IO)",
            "enum": [
              "somfy_europe"
            ]
          },
          {
            "title": "Somfy Australia (Connexoon RTS)",
            "enum": [
              "somfy_australia"
            ]
          },
          {
            "title": "Somfy North America",
            "enum": [
              "somfy_north_america"
            ]
          },
          {
            "title": "Cozytouch (Atlantic / Thermor / Sauter)",
            "enum": [
              "cozytouch"
            ]
          },
          {
            "title": "Energeasy Connect (Rexel)",
            "enum": [
              "rexel"
            ]
          },
          {
            "title": "Hi Kumo (Hitachi)",
            "enum": [
              "hi_kumo"
            ]
          },
          {
            "title": "Flexom (Bouygues)",
            "enum": [
              "flexom"
            ]
          },
          {
            "title": "Flexom (Bouygues)",
            "enum": [
              "flexom"
            ]
          }
        ],
        "required": true
      },
      "user": {
        "title": "User",
        "type": "string",
        "required": true,
        "description": "Your username for selected service (email, gateway Pin or IP)"
      },
      "password": {
        "title": "Password",
        "type": "string",
        "required": true,
        "description": "Your password/token for selected service"
      },
      "pollingPeriod": {
        "title": "Polling period",
        "type": "number",
        "minimum": 10,
        "placeholder": 60,
        "description": "Period (in seconds) for fetching device changes made from other controller (with TaHoma app for eg.)"
      },
      "refreshPeriod": {
        "title": "Refresh period",
        "type": "number",
        "minimum": 10,
        "placeholder": 30,
        "description": "Period (in minutes) for refreshing device changes made locally (with remote control for eg.)"
      },
      "exposeScenarios": {
        "title": "Expose scenarios",
        "type": "boolean",
        "description": "Expose scenarios as HomeKit switch to trigger them"
      },
      "exclude": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "devicesConfig": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "key": {
              "title": "Device name",
              "type": "string",
              "required": true,
              "description": "Device name, widget or uiClass type"
            },
            "blindMode": {
              "title": "Blind mode",
              "type": "boolean",
              "description": "Manage blind orientation with main slider"
            },
            "blindsOnRollerShutter": {
              "title": "Blinds on roller shutter",
              "type": "boolean",
              "description": "Manage blinds installed on roller shutter motors"
            },
            "reverse": {
              "title": "Reverse",
              "type": "boolean",
              "description": "Reverse behaviour for open/close commands"
            },
            "lowSpeed": {
              "title": "Low speed mode",
              "type": "boolean",
              "description": "Low speed for supported roller shutter"
            },
            "defaultPosition": {
              "title": "Default position",
              "type": "number",
              "minimum": 0,
              "maximum": 100,
              "description": "Restore specific default position for stateless covering"
            },
            "movementDuration": {
              "title": "Movement Duration",
              "type": "integer",
              "minimum": 0,
              "description": "Duration from 'opened' to 'closed' position to estimate intermediate positions"
            },
            "cyclic": {
              "title": "Cyclic",
              "type": "boolean",
              "description": "Emulate cyclic door"
            },
            "cycleDuration": {
              "title": "Cycle Duration",
              "type": "integer",
              "description": "Cycle duration if cyclic mode enabled"
            },
            "occupancySensor": {
              "title": "Occupancy sensor",
              "type": "boolean",
              "description": "Expose an occupancy sensor, active when alarm trigered"
            },
            "stayZones": {
              "title": "Stay Zones",
              "type": "string",
              "description": "Zones to activate in Presence mode"
            },
            "nightZones": {
              "title": "Night Zones",
              "type": "string",
              "description": "Zones to activate in Night mode"
            }
          }
        }
      }
    }
  },
  "layout": [
    "name",
    "service",
    "user",
    "password",
    {
      "type": "fieldset",
      "title": "What",
      "description": "Select what kind of ressources to expose.",
      "expandable": true,
      "expanded": false,
      "items": [
        "exposeScenarios",
        {
          "title": "Exclude devices or scenarios",
          "description": "Exclude devices or scenarios from being exposed",
          "key": "exclude",
          "type": "array",
          "items": {
            "type": "string",
            "description": "Device or scenarios name, widget, uiClass or protocol."
          }
        }
      ]
    },
    {
      "type": "fieldset",
      "title": "Device specific config",
      "description": "Apply specific config for some devices or kind of devices.",
      "expandable": true,
      "expanded": false,
      "items": [
        {
          "key": "devicesConfig",
          "type": "array",
          "items": [
            {
              "type": "div",
              "items": [
                "devicesConfig[].key",
                {
                  "title": "Window Covering",
                  "type": "section",
                  "expandable": true,
                  "expanded": false,
                  "items": [
                    "devicesConfig[].reverse",
                    "devicesConfig[].defaultPosition",
                    "devicesConfig[].blindMode",
                    "devicesConfig[].lowSpeed",
                    "devicesConfig[].blindsOnRollerShutter",
                    "devicesConfig[].movementDuration"
                  ]
                },
                {
                  "title": "Garage Door",
                  "type": "section",
                  "expandable": true,
                  "expanded": false,
                  "items": [
                    "devicesConfig[].reverse",
                    "devicesConfig[].cyclic",
                    "devicesConfig[].cycleDuration"
                  ]
                },
                {
                  "title": "Alarm",
                  "type": "section",
                  "expandable": true,
                  "expanded": false,
                  "items": [
                    "devicesConfig[].occupancySensor",
                    "devicesConfig[].stayZones",
                    "devicesConfig[].nightZones"
                  ]
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "type": "fieldset",
      "title": "Advanced Settings",
      "description": "Don't change these, unless you understand what you're doing.",
      "expandable": true,
      "expanded": false,
      "items": [
        "pollingPeriod",
        "refreshPeriod"
      ]
    }
  ]
}

================================================
FILE: eslint.config.mjs
================================================
import tsParser from "@typescript-eslint/parser";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
    baseDirectory: __dirname,
    recommendedConfig: js.configs.recommended,
    allConfig: js.configs.all
});

export default [{
    ignores: ["**/dist"],
}, ...compat.extends(
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
), {
    languageOptions: {
        parser: tsParser,
        ecmaVersion: 2018,
        sourceType: "module",
    },

    rules: {
        quotes: ["warn", "single"],

        indent: ["warn", 4, {
            SwitchCase: 1,
        }],

        semi: ["warn", "always"],
        "comma-dangle": ["warn", "always-multiline"],
        "dot-notation": "off",
        eqeqeq: "warn",
        curly: ["warn", "all"],
        "brace-style": ["warn"],
        "prefer-arrow-callback": ["warn"],
        "max-len": ["warn", 140],
        "no-console": ["warn"],
        "no-non-null-assertion": ["off"],
        "comma-spacing": ["error"],

        "no-multi-spaces": ["warn", {
            ignoreEOLComments: true,
        }],

        "lines-between-class-members": ["warn", "always", {
            exceptAfterSingleLine: true,
        }],

        "@typescript-eslint/explicit-function-return-type": "off",
        "@typescript-eslint/no-non-null-assertion": "off",
        "@typescript-eslint/explicit-module-boundary-types": "off",
        "@typescript-eslint/no-explicit-any": "off",
    },
}];


================================================
FILE: nodemon.json
================================================
{
  "watch": [
    "src"
  ],
  "ext": "ts",
  "ignore": [],
  "exec": "tsc && homebridge -I -D",
  "signal": "SIGTERM",
  "env": {
    "NODE_OPTIONS": "--trace-warnings"
  }
}

================================================
FILE: package.json
================================================
{
  "name": "homebridge-tahoma",
  "displayName": "Homebridge TaHoma",
  "version": "2.2.61",
  "description": "Sample Platform plugin for TaHoma and Cozytouch services (Somfy,Atlantic,Thermor,Sauter): https://github.com/dubocr/homebridge-tahoma",
  "author": "Romain DUBOC <dubocr@gmail.com>",
  "license": "Apache-2.0",
  "repository": {
    "type": "git",
    "url": "https://github.com/dubocr/homebridge-tahoma.git"
  },
  "bugs": {
    "url": "https://github.com/dubocr/homebridge-tahoma/issues"
  },
  "engines": {
    "node": ">=12.4.0",
    "homebridge": ">=1.3.0"
  },
  "main": "dist/index.js",
  "scripts": {
    "lint": "eslint src/**.ts",
    "watch": "npm run build && npm link && nodemon",
    "clean": "rimraf ./dist",
    "build": "rimraf ./dist && tsc",
    "prepublishOnly": "npm run lint && npm run build && npm version patch --m 'Release %s'",
    "postpublish": "npm run clean"
  },
  "keywords": [
    "homebridge-plugin",
    "tahoma",
    "cozytouch",
    "somfy",
    "connexoon"
  ],
  "homepage": "https://github.com/dubocr/homebridge-tahoma#readme",
  "dependencies": {
    "moment": "^2.30.1",
    "overkiz-client": "^1.0.20"
  },
  "devDependencies": {
    "@types/node": "^22.8.7",
    "@typescript-eslint/eslint-plugin": "^8.13.0",
    "@typescript-eslint/parser": "^8.13.0",
    "eslint": "^9.14.0",
    "homebridge": "^1.8.5",
    "nodemon": "^3.1.7",
    "rimraf": "^6.0.1",
    "ts-node": "^10.9.2",
    "typescript": "^5.6.3"
  },
  "funding": {
    "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=L4X489MG7FUCN"
  }
}


================================================
FILE: platform.schema.json
================================================
{
  "plugin_alias": "Tahoma",
  "schema": {
    "type": "object",
    "properties": {
      "platform": {
        "title": "Platform",
        "type": "string",
        "const": "Tahoma",
        "readOnly": true
      },
      "name": {
        "title": "Name",
        "type": "string",
        "required": true,
        "default": "TaHoma",
        "description": "The name of this platform in HomeKit"
      },
      "service": {
        "title": "Service",
        "type": "string",
        "default": "somfy_europe",
        "oneOf": [
          {
            "title": "Local API (TaHoma / Switch)",
            "enum": [
              "local"
            ]
          },
          {
            "title": "Somfy Europe (TaHoma / Switch / Connexoon IO)",
            "enum": [
              "somfy_europe"
            ]
          },
          {
            "title": "Somfy Australia (Connexoon RTS)",
            "enum": [
              "somfy_australia"
            ]
          },
          {
            "title": "Somfy North America",
            "enum": [
              "somfy_north_america"
            ]
          },
          {
            "title": "Cozytouch (Atlantic / Thermor / Sauter)",
            "enum": [
              "cozytouch"
            ]
          },
          {
            "title": "Energeasy Connect (Rexel)",
            "enum": [
              "rexel"
            ]
          },
          {
            "title": "Hi Kumo (Hitachi)",
            "enum": [
              "hi_kumo"
            ]
          },
          {
            "title": "Flexom (Bouygues)",
            "enum": [
              "flexom"
            ]
          }
        ],
        "required": true,
        "description": "Service name"
      },
      "user": {
        "title": "Username",
        "type": "string",
        "description": "Your username for selected service (email, gateway Pin or IP)"
      },
      "password": {
        "title": "Password",
        "type": "string",
        "options": {
          "hidden": true
        },
        "description": "Your password/token for selected service"
      },
      "exposeScenarios": {
        "title": "Expose scenarios",
        "type": "boolean",
        "description": "Expose scenarios as HomeKit switch to trigger them"
      },
      "exclude": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "description": "List of device or scenario to exclude (should be a name, widget, uiClass or protocol)"
      }
    }
  }
}

================================================
FILE: src/CustomCharacteristics.ts
================================================
import { HAP, Characteristic, Perms, Formats, WithUUID } from 'homebridge';

export let CurrentShowerCharacteristic: WithUUID<{ new(): Characteristic }>;
export let TargetShowerCharacteristic: WithUUID<{ new(): Characteristic }>;
export let MyPositionCharacteristic: WithUUID<{ new(): Characteristic }>;
export let ProgCharacteristic: WithUUID<{ new(): Characteristic }>;
export let EcoCharacteristic: WithUUID<{ new(): Characteristic }>;
export let TotalConsumptionCharacteristic: WithUUID<{ new(): Characteristic }>;
export let CurrentConsumptionCharacteristic: WithUUID<{ new(): Characteristic }>;

export class CustomCharacteristics {
    constructor(hap: HAP) {
        CurrentShowerCharacteristic = class extends hap.Characteristic {

            public static readonly UUID: string = '10000001-0000-1000-8000-0026BB765291';

            constructor() {
                super('Current Shower', CurrentShowerCharacteristic.UUID, {
                    format: Formats.INT,
                    minValue: 0,
                    maxValue: 8,
                    minStep: 1,
                    perms: [Perms.NOTIFY, Perms.PAIRED_READ],
                });
                this.value = this.getDefaultValue();
            }
        };

        TargetShowerCharacteristic = class extends hap.Characteristic {

            public static readonly UUID: string = '10000002-0000-1000-8000-0026BB765291';

            constructor() {
                super('Target Shower', TargetShowerCharacteristic.UUID, {
                    format: Formats.INT,
                    minValue: 0,
                    maxValue: 8,
                    minStep: 1,
                    perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
                });
                this.value = this.getDefaultValue();
            }
        };

        MyPositionCharacteristic = class extends hap.Characteristic {

            public static readonly UUID: string = '10000003-0000-1000-8000-0026BB765291';

            constructor() {
                super('My', MyPositionCharacteristic.UUID, {
                    format: Formats.BOOL,
                    perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
                });
                this.value = this.getDefaultValue();
            }
        };

        ProgCharacteristic = class extends hap.Characteristic {

            public static readonly UUID: string = '10000004-0000-1000-8000-0026BB765291';

            constructor() {
                super('Prog', ProgCharacteristic.UUID, {
                    format: Formats.BOOL,
                    perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
                });
                this.value = this.getDefaultValue();
            }
        };

        EcoCharacteristic = class extends hap.Characteristic {

            public static readonly UUID: string = '10000005-0000-1000-8000-0026BB765291';

            constructor() {
                super('Eco', EcoCharacteristic.UUID, {
                    format: Formats.BOOL,
                    perms: [Perms.NOTIFY, Perms.PAIRED_READ, Perms.PAIRED_WRITE],
                });
                this.value = this.getDefaultValue();
            }
        };

        TotalConsumptionCharacteristic = class extends hap.Characteristic {

            public static readonly UUID: string = 'E863F10C-079E-48FF-8F27-9C2605A29F52';

            constructor() {
                super('Total Consumption', TotalConsumptionCharacteristic.UUID, {
                    format: Formats.FLOAT,
                    unit: 'kWh',
                    minValue: 0,
                    maxValue: 1000000,
                    minStep: 0.1,
                    perms: [Perms.NOTIFY, Perms.PAIRED_READ],
                });
                this.value = this.getDefaultValue();
            }
        };

        CurrentConsumptionCharacteristic = class extends hap.Characteristic {

            public static readonly UUID: string = 'E863F10D-079E-48FF-8F27-9C2605A29F52';

            constructor() {
                super('Current Consumption', CurrentConsumptionCharacteristic.UUID, {
                    format: Formats.FLOAT,
                    unit: 'W',
                    minValue: 0,
                    maxValue: 12000,
                    minStep: 0.1,
                    perms: [Perms.NOTIFY, Perms.PAIRED_READ],
                });
                this.value = this.getDefaultValue();
            }
        };
    }
}


================================================
FILE: src/Mapper.ts
================================================
import { Characteristics, Services } from './Platform';
import { CharacteristicValue, HAPStatus, Logger, PlatformAccessory, Service } from 'homebridge';
import { Device, State, Command, Action, ExecutionState } from 'overkiz-client';
import { Platform } from './Platform';
import { GREY } from './colors';

export default abstract class Mapper {
    protected log: Logger;
    private postponeTimer;
    private debounceTimer;
    protected stateless = false;
    //protected config: Record<string, string | boolean | number> = {};
    private executionId;
    private actionPromise;
    protected expectedStates: Array<string> = [];

    constructor(
        protected readonly platform: Platform,
        protected readonly accessory: PlatformAccessory,
        protected readonly device: Device,
    ) {
        this.log = this.platform.log;
    }

    public build() {
        const config = Object.assign({},
            this.platform.devicesConfig[this.device.definition.uiClass],
            this.platform.devicesConfig[this.device.definition.widgetName],
            this.platform.devicesConfig[this.device.label],
            this.platform.devicesConfig[this.device.uuid],
        );
        this.stateless = this.device.states.length === 0 ||
            (this.expectedStates.length > 0 && !this.expectedStates.some((state) => this.device.hasState(state)));
        this.applyConfig(config);
        if (Object.keys(config).length > 0) {
            delete config.key;
            if (this.platform.config.debug) {
                this.log.info(`${GREY}  Config: `, JSON.stringify(config));
            } else {
                this.log.debug('  Config: ', JSON.stringify(config));
            }
        }

        const services = this.registerServices();

        const info = this.accessory.getService(Services.AccessoryInformation);
        if (info) {
            info.setCharacteristic(Characteristics.Manufacturer, this.device.manufacturer);
            info.setCharacteristic(Characteristics.Model, this.device.model);
            info.setCharacteristic(Characteristics.SerialNumber, this.device.address.substring(0, 64));
            services.push(info);
        }

        this.accessory.services.forEach((service) => {
            if (!services.find((s) => s.UUID === service.UUID && s.subtype === service.subtype)) {
                this.accessory.removeService(service);
            }
        });

        if (!this.stateless) {
            // Init and register states changes
            this.onStatesChanged(this.device.states, true);
            this.device.on('states', states => this.onStatesChanged(states));

            // Init and register sensors states changes
            this.device.sensors.forEach((sensor) => {
                this.onStatesChanged(sensor.states, true);
                sensor.on('states', states => this.onStatesChanged(states));
            });
        }

        // TODO: instanciate mapper for device sensors
        // Configure accessory sensors
        // this.device.sensors.forEach((sensor) => new mapper(platform, accessory, sensor)))
    }

    /**
     * Helper methods
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    protected applyConfig(config) {
        //
    }

    protected registerService(type: any, subtype?: string): Service {
        let service: Service;
        const name = subtype ? this.translate(subtype) : this.device.label;
        if (subtype) {
            service = this.accessory.getServiceById(type, subtype) || this.accessory.addService(type, name, subtype);
        } else {
            service = this.accessory.getService(type) || this.accessory.addService(type);
        }
        service.setCharacteristic(Characteristics.Name, name);
        /*
        service.getCharacteristic(Characteristics.Name)
            .updateValue(name)
            .onSet((value) => {
                this.debug('Will rename ' + name + ' to ' + value);
                this.platform.client.setDeviceName(this.device.deviceURL, value);
            });
        */
        return service;
    }

    private translate(value: string) {
        switch (value) {
            case 'boost': return 'Boost';
            case 'drying': return 'Séchage';
            default: return value.charAt(0).toUpperCase() + value.slice(1);
        }
    }

    protected debounce(task, immediate: Array<CharacteristicValue> = []) {
        return async (value: CharacteristicValue) => {
            if (this.debounceTimer !== null) {
                clearTimeout(this.debounceTimer);
            }
            if (immediate.includes(value)) {
                await task.bind(this, value)();
            } else {
                this.debounceTimer = setTimeout(async () => {
                    this.debounceTimer = null;
                    task.bind(this, value)().catch(() => null);
                }, 500);
            }
        };
    }

    protected postpone(task, ...args) {
        if (this.postponeTimer !== null) {
            clearTimeout(this.postponeTimer);
        }
        this.postponeTimer = setTimeout(() => {
            this.postponeTimer = null;
            task.bind(this, ...args)();
        }, 500);
    }

    protected async executeCommands(commands: Command | Array<Command> | undefined, standalone = false): Promise<Action> {
        if (commands === undefined || (Array.isArray(commands) && commands.length === 0)) {
            this.error('No target command for', this.device.label);
            throw HAPStatus.RESOURCE_DOES_NOT_EXIST;
        } else if (Array.isArray(commands)) {
            for (const c of commands) {
                this.info(c.name + JSON.stringify(c.parameters));
            }
        } else {
            this.info(commands.name + JSON.stringify(commands.parameters));
            commands = [commands];
        }

        const commandName = commands[0].name;
        const localizedName = this.platform.translate(
            commands[0].name + (commands[0].parameters.length > 0 ? '.' + commands[0].parameters[0] : ''),
        );
        /*
        if (!this.isIdle) {
            this.cancelExecution();
        }
        */

        const highPriority = this.device.hasState('io:PriorityLockLevelState') ? true : false;
        const label = this.device.label + ' - ' + localizedName;

        if (this.actionPromise) {
            this.actionPromise.action.addCommands(commands);
        } else {
            this.actionPromise = new Promise((resolve, reject) => {
                setTimeout(async () => {
                    try {
                        this.executionId = await this.platform.executeAction(label, this.actionPromise.action, highPriority, standalone);
                        resolve(this.actionPromise.action);
                    } catch (error: any) {
                        this.error(commandName + ' ' + error.message);
                        reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
                    }
                    this.actionPromise = null;
                }, 100);

            });
            this.actionPromise.action = new Action(this.device.deviceURL, commands);
            this.actionPromise.action.on('update', (state, event) => {
                if (state === ExecutionState.FAILED) {
                    this.error(commandName, event.failureType);
                } else if (state === ExecutionState.COMPLETED) {
                    this.info(commandName, state);
                } else {
                    this.debug(commandName, state);
                }
            });
        }
        return this.actionPromise;
    }

    private async delay(duration) {
        return new Promise(resolve => setTimeout(resolve, duration));
    }

    protected async requestStatesUpdate(defer?: number) {
        if (defer) {
            await this.delay(defer * 1000);
        }
        await this.platform.client.refreshDeviceStates(this.device.deviceURL);
    }

    /**
     * Logging methods
     */

    protected debug(...args) {
        if (this.platform.config.debug) {
            this.platform.log.info(`${GREY}[${this.device.label}]`, ...args);
        } else {
            this.platform.log.debug(`[${this.device.label}]`, ...args);
        }
    }

    protected info(...args) {
        this.platform.log.info(`[${this.device.label}]`, ...args);
    }

    protected warn(...args) {
        this.platform.log.warn(`[${this.device.label}]`, ...args);
    }

    protected error(...args) {
        this.platform.log.error(`[${this.device.label}]`, ...args);
    }

    protected registerServices(): Array<Service> {
        if (typeof this.registerMainService === 'function') {
            try {
                return [this.registerMainService()];
            } catch (error: any) {
                this.log.warn(error.message);
            }
        } else {
            this.log.warn(this.device.definition.widgetName + ' not supported.');
        }
        return [];
    }

    protected onStatesChanged(states: Array<State>, init = false) {
        states.forEach((state: State) => {
            if (!init) {
                this.debug(state.name + ' => ' + state.value);
            }
            if (typeof this.onStateChanged === 'function') {
                this.onStateChanged(state.name, state.value);
            }
        });
    }

    // OLD
    get isIdle() {
        return !this.platform.client.hasExecution(this.executionId);
    }

    async cancelExecution() {
        await this.platform.client.cancelExecution(this.executionId);
    }

    /**
     * Abstract methods to be implemented
     */

    /**
     * Build the main device service
     * @return the main service
     */
    protected abstract registerMainService(): Service;

    /**
     * Triggered when device state change
     * @param name State name
     * @param value State value
     */
    protected abstract onStateChanged(name: string, value);
}


================================================
FILE: src/Platform.ts
================================================
import { API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service } from 'homebridge';

import { PLATFORM_NAME, PLUGIN_NAME } from './settings';
import { Client, Execution, Action } from 'overkiz-client';
import Mapper from './Mapper';
import SceneMapper from './SceneMapper';
import { CustomCharacteristics } from './CustomCharacteristics';
import { BLUE, GREY, RESET } from './colors';


export let Services: typeof Service;
export let Characteristics: typeof Characteristic;

const DEFAULT_RETRY_DELAY = 60;

/**
 * HomebridgePlatform
 * This class is the main constructor for your plugin, this is where you should
 * parse the user config and discover/register accessories with Homebridge.
 */
export class Platform implements DynamicPlatformPlugin {
    // this is used to track restored cached accessories
    private readonly accessories: PlatformAccessory[] = [];
    public readonly client: Client;

    private readonly exclude: Array<string>;
    private readonly exposeScenarios: boolean | Array<string>;
    public readonly devicesConfig: Array<unknown> = [];

    private translations;
    private executionPromise;
    private retryDelay = DEFAULT_RETRY_DELAY;

    constructor(public readonly log: Logger, public readonly config: PlatformConfig, public readonly api: API) {
        Services = this.api.hap.Service;
        Characteristics = this.api.hap.Characteristic;
        new CustomCharacteristics(this.api.hap);
        this.log.debug('Finished initializing platform:', this.config.name);

        process.on('unhandledRejection', (error: any) => this.log.error(error));
        process.on('uncaughtException', (error: any) => this.log.error(error));

        this.exclude = config.exclude || [];
        this.exclude.push('Pod', 'ConfigurationComponent', 'NetworkComponent', 'ProtocolGateway', 'ConsumptionSensor',
            'OnOffHeatingSystem', 'Wifi', 'RemoteController',
            // AtlanticElectricalTowelDryer bad sensors
            'io:LightIOSystemDeviceSensor', 'io:RelativeHumidityIOSystemDeviceSensor', 'WeatherForecastSensor',
        );
        this.exposeScenarios = config.exposeScenarios;
        config.devicesConfig?.forEach(x => this.devicesConfig[x.key] = x);

        const logger = Object.assign({}, log, {
            debug: (...args) => {
                if (config['debug']) {
                    log.info('\x1b[90m', ...args)
                } else {
                    log.debug(args.shift(), ...args)
                }
            },
        });
        this.client = new Client(logger, config);

        // When this event is fired it means Homebridge has restored all cached accessories from disk.
        // Dynamic Platform plugins should only register new accessories after this event was fired,
        // in order to ensure they weren't added to homebridge already. This event can also be used
        // to start discovery of new accessories.
        this.api.on('didFinishLaunching', () => {
            log.debug('Executed didFinishLaunching callback');
            // run the method to discover / register your devices as accessories
            this.discoverDevices();
            if (this.config['service'] !== 'local') {
                this.loadLocation();
            }
        });
    }

    /**
     * This function is invoked when homebridge restores cached accessories from disk at startup.
     * It should be used to setup event handlers for characteristics and update respective values.
     */
    async loadLocation() {
        let countryCode = 'en';
        const location = await this.client.getSetupLocation().catch((error) => this.log.warn('Fail to load lang file:', error));
        if (location?.countryCode) {
            countryCode = location.countryCode.toLowerCase().trim();
        }
        this.translations = await import(`./lang/${countryCode}.json`)
            .catch(() => import('./lang/en.json'))
            .then((c) => c.default);

    }

    /**
     * This function is invoked when homebridge restores cached accessories from disk at startup.
     * It should be used to setup event handlers for characteristics and update respective values.
     */
    async configureAccessory(accessory: PlatformAccessory) {
        if (!this.accessories.map((a) => a.UUID).includes(accessory.UUID)) {
            this.accessories.push(accessory);
        }
    }

    /**
     * This is an example method showing how to register discovered accessories.
     * Accessories must only be registered once, previously created accessories
     * must not be registered again to prevent "duplicate UUID" errors.
     */
    async discoverDevices() {
        try {
            const uuids = Array<string>();
            const devices = await this.client.getDevices();
            this.log.debug(devices.length + ' devices discovered');

            // loop over the discovered devices and register each one if it has not already been registered
            for (const device of devices) {
                if (
                    this.exclude.includes(device.definition.uiClass) ||
                    this.exclude.includes(device.definition.widgetName) ||
                    this.exclude.includes(device.controllableName) ||
                    this.exclude.includes(device.label) ||
                    this.exclude.includes(device.protocol)
                ) {
                    continue;
                }

                // see if an accessory with the same uuid has already been registered and restored from
                // the cached devices we stored in the `configureAccessory` method above
                let accessory = this.accessories.find(accessory => accessory.UUID === device.uuid);

                if (accessory) {
                    // the accessory already exists
                    //this.log.info('Updating accessory:', accessory.displayName);
                    /*
                    const newaccessory = new this.api.platformAccessory(device.label, device.uuid);
                    newaccessory.context.device = device;
                    await this.configureAccessory(newaccessory);
                    const services = newaccessory.services.map((service) => service.UUID);
                    accessory.services
                        .filter((service) => !services.includes(service.UUID))
                        .forEach((services) => accessory?.removeService(services));
                    this.api.updatePlatformAccessories([accessory]);
                    */
                } else {
                    // the accessory does not yet exist, so we need to create it
                    this.log.info('Create accessory:', device.label);
                    accessory = new this.api.platformAccessory(device.label, device.uuid);
                    //accessory.context.device = device;
                    await this.configureAccessory(accessory);
                    this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
                }

                this.log.info(`Configure device ${BLUE}${accessory.displayName}${RESET}`);
                this.log.info(`${GREY}  ${device.definition.uiClass} > ${device.definition.widgetName}`);

                const mapper = await import(`./mappers/${device.definition.uiClass}/${device.definition.widgetName}/${device.uniqueName}`)
                    .catch(() => import(`./mappers/${device.definition.uiClass}/${device.definition.widgetName}`))
                    .catch(() => import(`./mappers/${device.definition.uiClass}`))
                    .then((c) => c.default)
                    .catch(() => Mapper);
                new mapper(this, accessory, device).build();

                uuids.push(device.uuid);
            }


            if (this.exposeScenarios) {
                const actionGroups = await this.client.getActionGroups();

                for (const actionGroup of actionGroups) {
                    if (this.exclude.includes(actionGroup.label) || actionGroup.label.startsWith('internal:') || actionGroup.label === '') {
                        continue;
                    }

                    let accessory = this.accessories.find(accessory => accessory.UUID === actionGroup.oid);

                    if (!accessory) {
                        // the accessory does not yet exist, so we need to create it
                        this.log.info('Create accessory', actionGroup.label);
                        accessory = new this.api.platformAccessory(actionGroup.label, actionGroup.oid);
                        await this.configureAccessory(accessory);
                        this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
                    }

                    this.log.info('Map scene', accessory.displayName);

                    new SceneMapper(this, accessory, actionGroup);
                    uuids.push(actionGroup.oid);
                }
            }

            const deleted = this.accessories.filter((accessory) => !uuids.includes(accessory.UUID));
            this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, deleted);
            this.retryDelay = DEFAULT_RETRY_DELAY;
        } catch (error: any) {
            this.log.error(error);
            this.log.error('Retry in ' + this.retryDelay + ' sec...');
            setTimeout(this.discoverDevices.bind(this), this.retryDelay * 1000);
            this.retryDelay *= 2;
        }
    }

    /*
        action: The action to execute
    */
    public executeAction(label: string, action: Action, highPriority = false, standalone = false) {
        if (standalone) {
            // Run action in standalone execution
            return this.client.execute(highPriority ? 'apply/highPriority' : 'apply', new Execution(label + ' - HomeKit', action));
        } else {
            if (this.executionPromise) {
                this.executionPromise.execution.addAction(action);
                this.executionPromise.execution.label = 'Execute scene (' +
                    this.executionPromise.execution.actions.length + ' devices) - HomeKit';
            } else {
                this.executionPromise = new Promise((resolve, reject) => {
                    setTimeout(() => {
                        this.client.execute(highPriority ? 'apply/highPriority' : 'apply', this.executionPromise.execution)
                            .then(resolve)
                            .catch(reject);
                        this.executionPromise = null;
                    }, 100);
                });
                this.executionPromise.execution = new Execution(label + ' - HomeKit', action);
            }
            return this.executionPromise;
        }
    }

    /**
     * Translate
     * @param path 
     * @returns string
     */
    public translate(label: string): string | null {
        const path = label.split('.');
        let translation = this.translations;
        for (const key of path) {
            if (typeof translation === 'object' && key in translation) {
                translation = translation[key];
            } else if (typeof translation === 'string') {
                if (translation.includes(':param')) {
                    translation = translation.replace(':param', key);
                }
                return translation;
            }
        }
        return label;
    }
}


================================================
FILE: src/SceneMapper.ts
================================================
import { Characteristics, Services } from './Platform';
import { Characteristic, Logger, PlatformAccessory, Service } from 'homebridge';
import { ExecutionState, ActionGroup, Execution } from 'overkiz-client';
import { Platform } from './Platform';

export default class Mapper {
    protected log: Logger;
    protected services: Array<Service> = [];
    protected on: Characteristic | undefined;
    private lastExecId;

    constructor(
        protected readonly platform: Platform,
        protected readonly accessory: PlatformAccessory,
        protected readonly action: ActionGroup,
    ) {
        this.log = platform.log;

        const service = this.accessory.getService(Services.Switch) || this.accessory.addService(Services.Switch);
        this.on = service.getCharacteristic(Characteristics.On);

        this.on.onSet(this.setOn.bind(this));
        this.on.updateValue(0);
    }

    private get isInProgress() {
        return this.platform.client.hasExecution(this.lastExecId);
    }

    protected async setOn(value) {
        if (value) {
            const execution = new Execution('');
            this.lastExecId = await this.platform.client.execute(this.action.oid, execution);
            execution.on('update', (state, event) => {
                switch (state) {
                    case ExecutionState.COMPLETED:
                    case ExecutionState.FAILED:
                        this.log.info('[Scene] ' + this.action.label + ' ' + (state === ExecutionState.FAILED ? event.failureType : state));
                        this.on?.updateValue(0);
                        break;
                }
            });
        } else if (this.isInProgress) {
            await this.platform.client.cancelExecution(this.lastExecId);
        }
    }
}

================================================
FILE: src/colors.ts
================================================
export const RESET = '\x1b[0m';
export const BRIGHT = '\x1b[1m';
export const DIM = '\x1b[2m';
export const UNDERSCORE = '\x1b[4m';
export const BLINK = '\x1b[5m';
export const REVERSE = '\x1b[7m';
export const HIDDEN = '\x1b[8m';

export const BLACK = '\x1b[30m';
export const RED = '\x1b[31m';
export const GREEN = '\x1b[32m';
export const YELLOW = '\x1b[33m';
export const BLUE = '\x1b[34m';
export const MAGENTA = '\x1b[35m';
export const CYAN = '\x1b[36m';
export const LIGHT_GREY = '\x1b[37m';
export const GREY = '\x1b[90m';
export const WHITE = '\x1b[97m';

================================================
FILE: src/index.ts
================================================
import { API } from 'homebridge';

import { PLATFORM_NAME } from './settings';
import { Platform } from './Platform';

/**
 * This method registers the platform with Homebridge
 */
export = (api: API) => {
    api.registerPlatform(PLATFORM_NAME, Platform);
}

================================================
FILE: src/lang/en.json
================================================
{
    "others": ":param other(s)",
    "setClosure": "Close :param%",
    "setHeatingLevel": {
        "comfort": "Comfort mode",
        "eco": "Eco mode",
        "frostprotection": "Frost protection mode",
        "off": "Stop"
    },
    "open": "Open",
    "close": "Close",
    "setPedestrianPosition": "Pedestrian position",
    "partialPosition": "Partial position"
}

================================================
FILE: src/lang/fr.json
================================================
{
    "others": ":param autre(s)",
    "setClosure": "Fermeture :param%",
    "setHeatingLevel": {
        "comfort": "Mode confort",
        "eco": "Mode eco",
        "frostprotection": "Mode hors gel",
        "off": "Arrêt"
    },
    "open": "Ouverture",
    "close": "Fermeture",
    "setPedestrianPosition": "Ouverture piéton",
    "partialPosition": "Ouverture partielle"
}

================================================
FILE: src/mappers/AdjustableSlatsRollerShutter.ts
================================================
import { Command } from 'overkiz-client';
import VenetianBlind from './VenetianBlind';

export default class AdjustableSlatsRollerShutter extends VenetianBlind {

    protected getTargetCommands(value) {
        if(this.blindMode) {
            if(value === 100) {
                return new Command('setClosure', 0);
            } else {
                return new Command('setClosureOrOrientation', [100, this.reversedValue(value)]);
            }
        } else {
            return new Command('setClosureOrOrientation', [
                this.reversedValue(value),
                this.angleToOrientation(this.targetAngle?.value),
            ]);
        }
    }

    protected getTargetAngleCommands(value) {
        return new Command('setClosureOrOrientation', [
            this.reversedValue(this.targetPosition?.value),
            this.angleToOrientation(value),
        ]);
    }

    protected onStateChanged(name, value) {
        super.onStateChanged(name, value);

        switch(name) {
            case 'core:ClosureOrRockerPositionState':
                this.currentPosition?.updateValue(this.reversedValue(value));
                if(!this.device.hasState('core:TargetClosureState')) {
                    this.targetPosition?.updateValue(this.reversedValue(value));
                }
                break;
            default: break;
        }
    }
}

================================================
FILE: src/mappers/AirSensor/CO2Sensor.ts
================================================
import { Characteristics } from '../../Platform';
import { Characteristic } from 'homebridge';
import AirSensor from '../AirSensor';

export default class RelativeHumiditySensor extends AirSensor {
    protected co2: Characteristic | undefined;

    protected registerMainService() {
        const service = super.registerMainService();
        service.addOptionalCharacteristic(Characteristics.CarbonDioxideLevel);
        this.co2 = service.getCharacteristic(Characteristics.CarbonDioxideLevel);
        return service;
    }

    

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:CO2ConcentrationState':
                this.co2?.updateValue(value);
                this.quality?.updateValue(this.co2ToQuality(value));
                break;
        }
    }

    private co2ToQuality(value) {
        if (value < 350) {
            return Characteristics.AirQuality.EXCELLENT;
        } else if (value < 1000) {
            return Characteristics.AirQuality.GOOD;
        } else if (value < 2000) {
            return Characteristics.AirQuality.FAIR;
        } else if (value < 5000) {
            return Characteristics.AirQuality.INFERIOR;
        } else {
            return Characteristics.AirQuality.POOR;
        }
    }
}

================================================
FILE: src/mappers/AirSensor/RelativeHumiditySensor.ts
================================================
import HumiditySensor from '../HumiditySensor';

export default class RelativeHumiditySensor extends HumiditySensor {

}

================================================
FILE: src/mappers/AirSensor.ts
================================================

import { Characteristics, Services } from '../Platform';
import { Characteristic, Service } from 'homebridge';
import Mapper from '../Mapper';

export default class AirSensor extends Mapper {
    protected quality: Characteristic | undefined;

    protected registerMainService(): Service {
        const service = this.registerService(Services.AirQualitySensor);
        this.quality = service.getCharacteristic(Characteristics.AirQuality);
        return service;
    }
    
    protected onStateChanged(name: string, value) {
        switch(name) {
            default: this.quality?.updateValue(value);
        }
    }
}

================================================
FILE: src/mappers/Alarm/MyFoxAlarmController.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import Alarm from '../Alarm';

export default class MyFoxAlarmController extends Alarm {
    protected getTargetCommands(value): Command | Array<Command> {
        switch(value) {
            default:
            case Characteristics.SecuritySystemTargetState.STAY_ARM:
                return [];
            case Characteristics.SecuritySystemTargetState.NIGHT_ARM:
                return new Command('partial');
            case Characteristics.SecuritySystemTargetState.AWAY_ARM:
                return new Command('arm');
            case Characteristics.SecuritySystemTargetState.DISARM:
                return new Command('disarm');
        }
    }

    protected onStateChanged(name: string, value) {
        switch(name) {
            case 'myfox:AlarmStatusState':
                switch(value) {
                    default:
                    case 'disarmed': 
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.DISARMED);
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.DISARM);
                        break;
                    case 'armed': 
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.AWAY_ARM);
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.AWAY_ARM);
                        break;
                    case 'partial': 
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.NIGHT_ARM);
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.NIGHT_ARM);
                        break;
                }
                break;
        }
    }
}

================================================
FILE: src/mappers/Alarm/TSKAlarmController.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import Alarm from '../Alarm';

export default class TSKAlarmController extends Alarm {
    protected getTargetCommands(value): Command | Array<Command> {
        switch(value) {
            default:
            case Characteristics.SecuritySystemTargetState.STAY_ARM:
                return new Command('alarmPartial1');
            case Characteristics.SecuritySystemTargetState.NIGHT_ARM:
                return new Command('alarmPartial2');
            case Characteristics.SecuritySystemTargetState.AWAY_ARM:
                return new Command('alarmOn');
            case Characteristics.SecuritySystemTargetState.DISARM:
                return new Command('alarmOff');
        }
    }

    protected onStateChanged(name: string, value) {
        switch(name) {
            case 'internal:CurrentAlarmModeState':
                switch(value) {
                    default:
                    case 'off': 
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.DISARMED);
                        break;
                    case 'partial1':
                    case 'zone1': 
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.STAY_ARM);
                        break;
                    case 'total': 
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.AWAY_ARM);
                        break;
                    case 'partial2':
                    case 'zone2': 
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.NIGHT_ARM);
                        break;
                }
                break;
            
            case 'internal:TargetAlarmModeState':
                switch(value) {
                    default:
                    case 'off': 
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.DISARM);
                        break;
                    case 'partial1':
                    case 'zone1': 
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.STAY_ARM);
                        break;
                    case 'total': 
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.AWAY_ARM);
                        break;
                    case 'partial2':
                    case 'zone2': 
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.NIGHT_ARM);
                        break;
                }
                break;
        }
    }
}

================================================
FILE: src/mappers/Alarm.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic, CharacteristicSetCallback } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import Mapper from '../Mapper';

export default class Alarm extends Mapper {
    protected currentState: Characteristic | undefined;
    protected targetState: Characteristic | undefined;

    protected stayZones: unknown | undefined;
    protected nightZones: unknown | undefined;
    protected occupancySensor: unknown | undefined;

    protected applyConfig(config) {
        this.stayZones = config.stayZones || 'A';
        this.nightZones = config.nightZones || 'B';
        this.occupancySensor = config.occupancySensor || false;
    }


    protected registerMainService() {
        const service = this.registerService(Services.SecuritySystem);
        this.currentState = service.getCharacteristic(Characteristics.SecuritySystemCurrentState);
        this.targetState = service.getCharacteristic(Characteristics.SecuritySystemTargetState);

        this.targetState.onSet(this.setTargetState.bind(this));
        return service;
    }

    protected getTargetCommands(value): Command | Array<Command> {
        switch (value) {
            default:
            case Characteristics.SecuritySystemTargetState.STAY_ARM:
                return new Command('alarmZoneOn', [this.stayZones]);
            case Characteristics.SecuritySystemTargetState.NIGHT_ARM:
                return new Command('alarmZoneOn', [this.nightZones]);
            case Characteristics.SecuritySystemTargetState.AWAY_ARM:
                return new Command('alarmOn');
            case Characteristics.SecuritySystemTargetState.DISARM:
                return new Command('alarmOff');
        }
    }

    async setTargetState(value) {
        const action = await this.executeCommands(this.getTargetCommands(value));
        action.on('update', (state, data) => {
            switch (state) {
                case ExecutionState.COMPLETED:
                    if (this.stateless) {
                        this.currentState?.updateValue(value);
                    }
                    break;
                case ExecutionState.FAILED:
                    if (this.currentState &&
                        this.currentState.value !== Characteristics.SecuritySystemCurrentState.ALARM_TRIGGERED) {
                        this.targetState?.updateValue(this.currentState.value);
                    }
                    break;
            }
        });
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:ActiveZonesState':
                switch (value) {
                    default:
                    case '':
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.DISARMED);
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.DISARM);
                        break;
                    case this.stayZones:
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.STAY_ARM);
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.STAY_ARM);
                        break;
                    case 'A,B,C':
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.AWAY_ARM);
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.AWAY_ARM);
                        break;
                    case this.nightZones:
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.NIGHT_ARM);
                        this.targetState?.updateValue(Characteristics.SecuritySystemTargetState.NIGHT_ARM);
                        break;
                    case 'triggered':
                        this.currentState?.updateValue(Characteristics.SecuritySystemCurrentState.ALARM_TRIGGERED);
                        break;
                }
                break;
        }
    }
}

================================================
FILE: src/mappers/Awning/PositionableHorizontalAwningUno.ts
================================================
import Awning from '../Awning';
export default class PositionableHorizontalAwningUno extends Awning {
    protected onStateChanged(name: string, value) {
        switch(name) {
            case 'core:TargetClosureState':
                if(this.isIdle) {
                    this.targetPosition?.updateValue(this.reversedValue(value));
                }
                this.currentPosition?.updateValue(this.reversedValue(value));
                break;
        }
    }
}

================================================
FILE: src/mappers/Awning.ts
================================================
import RollerShutter from './RollerShutter';
import { Command } from 'overkiz-client';

export default class Awning extends RollerShutter {
    /**
	* Triggered when Homekit try to modify the Characteristic.TargetPosition
	* HomeKit '0' (Close) => 0% Deployment
	* HomeKit '100' (Open) => 100% Deployment
	**/
    protected getTargetCommands(value) {
        if(this.stateless) {
            if(value === 100) {
                return new Command(this.reverse ? 'undeploy' : 'deploy');
            } else if(value === 0) {
                return new Command(this.reverse ? 'deploy' : 'undeploy');
            } else {
                if(this.movementDuration > 0) {
                    const delta = value - Number(this.currentPosition!.value);
                    if(this.reverse) {
                        return new Command(delta > 0 ? 'undeploy' : 'deploy');
                    } else {
                        return new Command(delta > 0 ? 'deploy' : 'undeploy');
                    }
                } else {
                    return new Command('my');
                }
            }
        } else {
            return new Command('setDeployment', this.reversedValue(value));
        }
    }

    protected reversedValue(value) {
        return this.reverse ? (100-value) : value;
    }

    protected onStateChanged(name: string, value) {
        switch(name) {
            case 'core:DeploymentState':
                this.currentPosition?.updateValue(this.reversedValue(value));
                if(!this.device.hasState('core:TargetClosureState')) {
                    this.targetPosition?.updateValue(this.reversedValue(value));
                }
                break;
            case 'core:ClosureState':
                this.currentPosition?.updateValue(this.reversedValue(value));
                if(!this.device.hasState('core:TargetClosureState')) {
                    this.targetPosition?.updateValue(this.reversedValue(value));
                }
                break;
            case 'core:TargetClosureState':
                this.targetPosition?.updateValue(this.reversedValue(value));
                if(!this.device.hasState('core:ClosureState')) {
                    this.currentPosition?.updateValue(this.reversedValue(value));
                }
                break;
        }
    }
}

================================================
FILE: src/mappers/ConsumptionSensor.ts
================================================
import { Service } from 'homebridge';
import Mapper from '../Mapper';

export default class ConsumptionSensor extends Mapper {
    protected registerMainService(): Service {
        throw new Error('ConsumptionSensor not implemented.');
    }

    protected onStateChanged(name: string, value: any) {
        this.info(name + ' => ' + value);
    }
}

================================================
FILE: src/mappers/ContactSensor.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic } from 'homebridge';
import Mapper from '../Mapper';

export default class ContactSensor extends Mapper {
    protected state: Characteristic | undefined;
    protected fault: Characteristic | undefined;
    protected battery: Characteristic | undefined;

    protected registerMainService() {
        const service = this.registerService(Services.ContactSensor);
        this.state = service.getCharacteristic(Characteristics.ContactSensorState);
        if (this.device.hasState('core:SensorDefectState')) {
            this.fault = service.getCharacteristic(Characteristics.StatusFault);
            this.battery = service.getCharacteristic(Characteristics.StatusLowBattery);
        }
        return service;
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:ContactState':
                switch (value) {
                    case 'closed':
                        this.state?.updateValue(Characteristics.ContactSensorState.CONTACT_DETECTED);
                        break;
                    case 'tilt':
                    case 'open':
                        this.state?.updateValue(Characteristics.ContactSensorState.CONTACT_NOT_DETECTED);
                        break;
                }
                break;
            case 'core:SensorDefectState':
                switch (value) {
                    case 'lowBattery':
                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_LOW);
                        break;
                    case 'maintenanceRequired':
                    case 'dead':
                        this.fault?.updateValue(Characteristics.StatusFault.GENERAL_FAULT);
                        break;
                    case 'noDefect':
                        this.fault?.updateValue(Characteristics.StatusFault.NO_FAULT);
                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_NORMAL);
                        break;
                }
                break;
        }
    }
}

================================================
FILE: src/mappers/Curtain.ts
================================================
import RollerShutter from './RollerShutter';

export default class Curtain extends RollerShutter {
}

================================================
FILE: src/mappers/DoorLock.ts
================================================

import { Characteristics, Services } from '../Platform';
import { Characteristic } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import Mapper from '../Mapper';

export default class VentilationSystem extends Mapper {
    protected currentState: Characteristic | undefined;
    protected targetState: Characteristic | undefined;

    protected registerMainService() {
        const service = this.registerService(Services.LockMechanism);
        this.currentState = service.getCharacteristic(Characteristics.LockCurrentState);
        this.targetState = service.getCharacteristic(Characteristics.LockTargetState);

        this.targetState?.onSet(this.setTargetState.bind(this));
        return service;
    }

    protected getTargetStateCommands(value): Command | Array<Command> {
        switch (value) {
            case Characteristics.LockTargetState.SECURED:
                return new Command('setLockedUnlocked', 'locked');
            case Characteristics.LockTargetState.UNSECURED:
            default:
                return new Command('setLockedUnlocked', 'unlocked');
        }
    }

    protected async setTargetState(value) {
        const action = await this.executeCommands(this.getTargetStateCommands(value));
        action.on('update', (state) => {
            switch (state) {
                case ExecutionState.COMPLETED:
                    if (this.stateless) {
                        this.currentState?.updateValue(value);
                    }
                    break;
                case ExecutionState.FAILED:
                    if (this.currentState) {
                        this.targetState?.updateValue(this.currentState.value);
                    }
                    break;
            }
        });
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:LockedUnlockedState':
                switch (value) {
                    case 'locked':
                        this.currentState?.updateValue(Characteristics.LockCurrentState.SECURED);
                        break;
                    default:
                        this.currentState?.updateValue(Characteristics.LockCurrentState.UNSECURED);
                        break;
                }
                if (this.isIdle && this.currentState) {
                    this.targetState?.updateValue(this.currentState.value);
                }
                break;
        }
    }
}

================================================
FILE: src/mappers/ElectricitySensor/CumulativeElectricPowerConsumptionSensor.ts
================================================
import { Services } from '../../Platform';
import { Characteristic } from 'homebridge';
import { CurrentConsumptionCharacteristic, TotalConsumptionCharacteristic } from '../../CustomCharacteristics';
import ElectricitySensor from '../ElectricitySensor';


export default class CumulativeElectricPowerConsumptionSensor extends ElectricitySensor {

    protected consumption: Characteristic | undefined;
    protected power: Characteristic | undefined;

    protected registerMainService() {
        const service = super.registerMainService();
        service.addOptionalCharacteristic(TotalConsumptionCharacteristic);
        this.consumption = service.getCharacteristic(TotalConsumptionCharacteristic);
        service.addOptionalCharacteristic(CurrentConsumptionCharacteristic);
        this.power = service.getCharacteristic(CurrentConsumptionCharacteristic);
        return service;
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:ElectricEnergyConsumptionState':
                this.consumption?.updateValue(value / 1000);
                break;
            case 'core:ElectricPowerConsumptionState':
                this.power?.updateValue(value);
                break;
        }
    }
}

================================================
FILE: src/mappers/ElectricitySensor.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Service } from 'homebridge';
import Mapper from '../Mapper';

export default class ElectricitySensor extends Mapper {
    protected registerMainService(): Service {
        const service = this.registerService(Services.AccessoryMetrics);
        return service;
    }

    protected onStateChanged(name: string, value: any) {
        this.info(name + ' => ' + value);
    }
}

================================================
FILE: src/mappers/EvoHome/DHWSetPoint.ts
================================================
import HeatingSystem from '../HeatingSystem';
import TemperatureSensor from '../TemperatureSensor';

export default class DHWSetPoint extends TemperatureSensor {

}

================================================
FILE: src/mappers/EvoHome/EvoHomeController.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class EvoHomeController extends HeatingSystem {
    protected registerMainService() {
        const service = super.registerMainService();
        this.targetState?.setProps({ validValues: [
            Characteristics.TargetHeatingCoolingState.AUTO,
            Characteristics.TargetHeatingCoolingState.OFF,
        ] });
        return service;
    }

    protected getTargetStateCommands(value): Command | Array<Command> | undefined {
        switch(value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                return new Command('setOperatingMode', 'auto');
            case Characteristics.TargetHeatingCoolingState.OFF:
                return new Command('setOperatingMode', 'off');
        }
    }
}

================================================
FILE: src/mappers/EvoHome/HeatingSetPoint.ts
================================================
import { Characteristics } from '../../Platform';
import HeatingSystem from '../HeatingSystem';

export default class HeatingSetPoint extends HeatingSystem {
    protected registerMainService() {
        const service = super.registerMainService();
        this.targetState?.setProps({ validValues: [
            Characteristics.TargetHeatingCoolingState.AUTO,
        ] });
        this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);
        return service;
    }
}

================================================
FILE: src/mappers/ExteriorHeatingSystem/DimmerExteriorHeating.ts
================================================
import { Characteristic, Service } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import { Characteristics, Services } from '../../Platform';
import ExteriorHeatingSystem from '../ExteriorHeatingSystem';

export default class DimmerExteriorHeating extends ExteriorHeatingSystem {
    protected level: Characteristic | undefined;

    protected registerMainService() {
        const service = this.registerService(Services.Lightbulb);
        this.on = service.getCharacteristic(Characteristics.On);

        this.on.onSet(this.setOn.bind(this));

        this.level = service.getCharacteristic(Characteristics.Brightness);
        this.level.onSet(this.debounce(this.setBrightness, [0, 100]));
        return service;
    }

    protected async setBrightness(value) {
        const action = await this.executeCommands(new Command('setLevel', 100 - value));
        action.on('update', (state, data) => {
            switch (state) {
                case ExecutionState.COMPLETED:
                    break;
                case ExecutionState.FAILED:
                    break;
            }
        });
    }

    protected onStateChanged(name: string, value): boolean {
        value = 100 - value;
        switch (name) {
            case 'core:LevelState':
                this.level?.updateValue(value);
                this.on?.updateValue(value === 0 ? 0 : 1);
                break;
        }
        return false;
    }
}

================================================
FILE: src/mappers/ExteriorHeatingSystem.ts
================================================
import { Command } from 'overkiz-client';
import HeatingSystem from './HeatingSystem';

export default class ExteriorHeatingSystem extends HeatingSystem {
    protected registerMainService() {
        return this.registerSwitchService();
    }

    protected getOnCommands(value): Command | Array<Command> {
        return new Command(value ? 'on' : 'off');
    }
}

================================================
FILE: src/mappers/ExteriorScreen.ts
================================================
import RollerShutter from './RollerShutter';

export default class ExteriorScreen extends RollerShutter {

}

================================================
FILE: src/mappers/ExteriorVenetianBlind.ts
================================================
import VenetianBlind from './VenetianBlind';

export default class ExteriorVenetianBlind extends VenetianBlind {
    
}

================================================
FILE: src/mappers/GarageDoor.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import Mapper from '../Mapper';

export default class GarageDoor extends Mapper {
    protected expectedStates = ['core:OpenClosedPartialState', 'core:OpenClosedUnknownState', 'core:OpenClosedState'];
    protected currentState: Characteristic | undefined;
    protected targetState: Characteristic | undefined;

    protected cyclic;
    protected cycleDuration;

    protected applyConfig(config) {
        this.cyclic = config['cyclic'] || false;
        this.cycleDuration = (config['cycleDuration'] || 5) * 1000;
    }

    protected registerMainService() {
        const service = this.registerService(Services.GarageDoorOpener);
        this.currentState = service.getCharacteristic(Characteristics.CurrentDoorState);
        this.targetState = service.getCharacteristic(Characteristics.TargetDoorState);
        this.targetState.onSet(this.setTargetState.bind(this));

        this.cyclic = this.cyclic || this.device.hasCommand('cycle');
        if (this.stateless) {
            this.currentState.updateValue(Characteristics.CurrentDoorState.CLOSED);
            this.targetState.updateValue(Characteristics.TargetDoorState.CLOSED);
        }
        return service;
    }

    protected getTargetCommands(value) {
        if (this.device.hasCommand('cycle')) {
            return new Command('cycle');
        } else {
            return new Command(value ? 'close' : 'open');
        }
    }

    protected async setTargetState(value) {
        const previousTarget = this.targetState?.value;
        const action = await this.executeCommands(this.getTargetCommands(value));
        action.on('update', (state) => {
            switch (state) {
                case ExecutionState.IN_PROGRESS:
                    if (value === Characteristics.TargetDoorState.OPEN) {
                        this.currentState?.updateValue(Characteristics.CurrentDoorState.OPENING);
                    } else {
                        this.currentState?.updateValue(Characteristics.CurrentDoorState.CLOSING);
                    }
                    break;
                case ExecutionState.COMPLETED:
                    if (this.stateless) {
                        this.onStateChanged(
                            this.expectedStates[0],
                            value === Characteristics.TargetDoorState.CLOSED ? 'closed' : 'open',
                        );
                        if (this.cyclic) {
                            setTimeout(() => {
                                this.onStateChanged(this.expectedStates[0], 'closed');
                            }, this.cycleDuration);
                        }
                    } else if (this.cyclic) {
                        this.requestStatesUpdate(60).catch((e) => this.warn(e));
                    }
                    break;
                case ExecutionState.FAILED:
                    if (previousTarget) {
                        this.targetState?.updateValue(previousTarget);
                    }
                    break;
            }
        });
    }

    protected onStateChanged(name: string, value) {
        let targetState;
        if (this.expectedStates.includes(name)) {
            switch (value) {
                case 'open':
                    this.currentState?.updateValue(Characteristics.CurrentDoorState.OPEN);
                    targetState = Characteristics.TargetDoorState.OPEN;
                    break;
                case 'partial':
                    this.currentState?.updateValue(Characteristics.CurrentDoorState.STOPPED);
                    targetState = Characteristics.TargetDoorState.OPEN;
                    break;
                case 'closed':
                    this.currentState?.updateValue(Characteristics.CurrentDoorState.CLOSED);
                    targetState = Characteristics.TargetDoorState.CLOSED;
                    break;
                case 'unknown':
                    break;
            }
        }

        if (this.targetState && targetState !== undefined) {
            this.targetState.updateValue(targetState);
        }
    }
}

================================================
FILE: src/mappers/Gate.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic, Service } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import GarageDoor from './GarageDoor';

export default class Gate extends GarageDoor {
    protected expectedStates = ['core:OpenClosedPedestrianState'];

    protected currentPedestrian: Characteristic | undefined;
    protected targetPedestrian: Characteristic | undefined;
    protected on: Characteristic | undefined;

    protected pedestrianCommand;
    protected pedestrianDuration;

    protected cancelTimeout;

    protected applyConfig(config) {
        super.applyConfig(config);
        this.pedestrianDuration = (config['pedestrianDuration'] || 0) * 1000;
        this.pedestrianCommand = ['setPedestrianPosition', 'partialPosition', 'my']
            .find((command: string) => this.device.hasCommand(command));
    }

    protected registerServices() {
        const services = super.registerServices();
        if (this.pedestrianCommand || this.pedestrianDuration) {
            const pedestrian = this.registerLockService('pedestrian');
            services.push(pedestrian);
        }
        if (this.stateless) {
            this.currentPedestrian?.updateValue(Characteristics.LockCurrentState.SECURED);
            this.targetPedestrian?.updateValue(Characteristics.LockCurrentState.SECURED);
        }
        return services;
    }

    protected registerSwitchService(subtype?: string): Service {
        const service = this.registerService(Services.Switch, subtype);
        this.on = service.getCharacteristic(Characteristics.On);

        this.on?.onSet(this.setOn.bind(this));
        return service;
    }

    protected registerLockService(subtype?: string): Service {
        const service = this.registerService(Services.LockMechanism, subtype);
        this.currentPedestrian = service.getCharacteristic(Characteristics.LockCurrentState);
        this.targetPedestrian = service.getCharacteristic(Characteristics.LockTargetState);

        this.targetPedestrian?.onSet(this.setLock.bind(this));
        return service;
    }

    protected getLockCommands(value): Command | Array<Command> {
        if (value === Characteristics.LockTargetState.UNSECURED && this.pedestrianCommand) {
            return new Command(this.pedestrianCommand);
        } else {
            return new Command(value === Characteristics.LockTargetState.UNSECURED ? 'open' : 'close');
        }
    }

    protected async setLock(value) {
        if (this.cancelTimeout !== null) {
            clearTimeout(this.cancelTimeout);
        }
        const action = await this.executeCommands(this.getLockCommands(value));
        action.on('update', (state) => {
            switch (state) {
                case ExecutionState.IN_PROGRESS:
                    if (this.stateless && !this.pedestrianCommand && this.pedestrianDuration) {
                        this.info('Will stop movement in ' + this.pedestrianDuration + ' millisec');
                        this.cancelTimeout = setTimeout(() => {
                            this.cancelTimeout = null;
                            if (this.isIdle) {
                                this.executeCommands(new Command('stop'), true);
                            } else {
                                this.cancelExecution().catch(this.error.bind(this));
                            }
                        }, this.pedestrianDuration);
                    }
                    break;
                case ExecutionState.COMPLETED:
                    if (this.stateless) {
                        this.onStateChanged(
                            'core:OpenClosedPedestrianState',
                            value === Characteristics.LockTargetState.SECURED ? 'closed' : 'pedestrian',
                        );
                        if (this.cyclic) {
                            setTimeout(() => {
                                this.onStateChanged('core:OpenClosedPedestrianState', 'closed');
                            }, this.cycleDuration);
                        }
                    }
                    break;
            }
        });
    }

    protected getOnCommands(value): Command | Array<Command> {
        if (value && this.pedestrianCommand) {
            return new Command(this.pedestrianCommand);
        } else {
            return new Command(value ? 'open' : 'close');
        }
    }

    protected async setOn(value) {
        const action = await this.executeCommands(this.getOnCommands(value));
        action.on('update', (state) => {
            switch (state) {
                case ExecutionState.FAILED:
                    this.on?.updateValue(!value);
                    break;
            }
        });
    }

    protected onStateChanged(name: string, value) {
        let targetState;
        let targetPedestrian;
        if (this.expectedStates.includes(name)) {
            switch (value) {
                case 'unknown':
                case 'open':
                    this.on?.updateValue(false);
                    this.currentState?.updateValue(Characteristics.CurrentDoorState.OPEN);
                    targetState = Characteristics.TargetDoorState.OPEN;
                    this.currentPedestrian?.updateValue(Characteristics.LockCurrentState.UNKNOWN);
                    targetPedestrian = Characteristics.LockTargetState.UNSECURED;
                    break;
                case 'pedestrian':
                case 'partial':
                    this.on?.updateValue(true);
                    this.currentState?.updateValue(Characteristics.CurrentDoorState.STOPPED);
                    targetState = Characteristics.TargetDoorState.OPEN;
                    this.currentPedestrian?.updateValue(Characteristics.LockCurrentState.UNSECURED);
                    targetPedestrian = Characteristics.LockTargetState.UNSECURED;
                    break;
                case 'closed':
                    this.on?.updateValue(false);
                    this.currentState?.updateValue(Characteristics.CurrentDoorState.CLOSED);
                    targetState = Characteristics.TargetDoorState.CLOSED;
                    this.currentPedestrian?.updateValue(Characteristics.LockCurrentState.SECURED);
                    targetPedestrian = Characteristics.LockTargetState.SECURED;
                    break;
            }
        }

        if (this.targetState && targetState !== undefined) {
            this.targetState.updateValue(targetState);
        }
        if (this.targetPedestrian && targetPedestrian !== undefined) {
            this.targetPedestrian.updateValue(targetPedestrian);
        }
    }
}

================================================
FILE: src/mappers/Generic/CyclicGeneric.ts
================================================
import GarageDoor from '../GarageDoor';

export default class CyclicGeneric extends GarageDoor {

}

================================================
FILE: src/mappers/Generic/DimmerOnOff.ts
================================================
import Light from '../Light';

export default class DimmerOnOff extends Light {

}

================================================
FILE: src/mappers/Generic/RTSGeneric.ts
================================================
import { Command } from 'overkiz-client';
import RollerShutter from '../RollerShutter';

export default class RTSGeneric extends RollerShutter {
    protected getTargetCommands(value) {
        if(value === 0) {
            return new Command('down');
        } else {
            return new Command('up');
        }
    }
}

================================================
FILE: src/mappers/Generic/RTSGeneric4T.ts
================================================
import GarageDoor from '../GarageDoor';

export default class RTSGeneric extends GarageDoor {

}

================================================
FILE: src/mappers/HeatingSystem/AtlanticElectricalHeater.ts
================================================
import { Characteristics } from '../../Platform';
import { Perms } from 'homebridge';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

const FROSTPROTECTION_TEMP = 7;

export default class AtlanticElectricalHeater extends HeatingSystem {
    protected THERMOSTAT_CHARACTERISTICS = ['eco'];
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.HEAT,
        Characteristics.TargetHeatingCoolingState.COOL,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    protected registerMainService() {
        const service = super.registerMainService();
        this.targetTemperature?.setProps({
            minValue: FROSTPROTECTION_TEMP,
            maxValue: this.comfortTemperature,
            minStep: 1,
            perms: [Perms.PAIRED_READ, Perms.EVENTS, Perms.PAIRED_WRITE],
        });
        return service;
    }

    protected getTargetStateCommands(value): Command | Array<Command> {
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                return new Command('setHeatingLevel', this?.eco?.value ? 'eco' : 'comfort');
            case Characteristics.TargetHeatingCoolingState.HEAT:
                return new Command('setHeatingLevel', 'comfort');
            case Characteristics.TargetHeatingCoolingState.COOL:
                return new Command('setHeatingLevel', 'eco');
            case Characteristics.TargetHeatingCoolingState.OFF:
                return new Command('setHeatingLevel', 'off');
        }
        return [];
    }

    protected async setTargetTemperature(value) {
        if (this.targetState?.value === Characteristics.CurrentHeatingCoolingState.OFF) {
            return;
        }
        const frostEcoLimit = FROSTPROTECTION_TEMP + (this.ecoTemperature - FROSTPROTECTION_TEMP) / 2;
        const ecoComfortLimit = this.ecoTemperature + (this.comfortTemperature - this.ecoTemperature) / 2;
        let newValue = value;
        if (value <= frostEcoLimit) {
            newValue = FROSTPROTECTION_TEMP;
        } else if (value > frostEcoLimit && value <= this.ecoTemperature) {
            newValue = this.ecoTemperature;
        } else if (value > this.ecoTemperature && value <= ecoComfortLimit) {
            newValue = this.comfortTemperature;
        }
        if (newValue !== value) {
            this.targetTemperature?.updateValue(newValue);
        }
        await this.executeCommands(this.getTargetTemperatureCommands(newValue));
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {
        if (value === FROSTPROTECTION_TEMP) {
            this.targetState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
            return new Command('setHeatingLevel', 'frostprotection');
        } else if (value === this.ecoTemperature) {
            this.targetState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
            return new Command('setHeatingLevel', 'eco');
        } else if (value === this.comfortTemperature) {
            this.targetState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
            return new Command('setHeatingLevel', 'comfort');
        }
    }

    protected getProgCommands(): Command | Array<Command> | undefined {
        return new Command('setHeatingLevel', this?.eco?.value ? 'eco' : 'comfort');
    }

    protected onStateChanged(name, value) {
        let targetState;
        switch (name) {
            case 'io:TargetHeatingLevelState':
                //targetState = Characteristics.TargetHeatingCoolingState.AUTO;
                switch (value) {
                    case 'off':
                        targetState = Characteristics.TargetHeatingCoolingState.OFF;
                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                        break;
                    case 'frostprotection':
                        targetState = Characteristics.TargetHeatingCoolingState.HEAT;
                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                        this.currentTemperature?.updateValue(FROSTPROTECTION_TEMP);
                        this.targetTemperature?.updateValue(FROSTPROTECTION_TEMP);
                        break;
                    case 'comfort':
                    case 'comfort-1':
                    case 'comfort-2':
                        targetState = Characteristics.TargetHeatingCoolingState.HEAT;
                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                        this.eco?.updateValue(false);
                        this.currentTemperature?.updateValue(this.comfortTemperature);
                        this.targetTemperature?.updateValue(this.comfortTemperature);
                        break;
                    case 'eco':
                        targetState = Characteristics.TargetHeatingCoolingState.COOL;
                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                        this.eco?.updateValue(true);
                        this.currentTemperature?.updateValue(this.ecoTemperature);
                        this.targetTemperature?.updateValue(this.ecoTemperature);
                        break;
                }
                if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {
                    this.targetState.updateValue(targetState);
                }
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint extends HeatingSystem {
    protected THERMOSTAT_CHARACTERISTICS = ['prog'];
    protected MIN_TEMP = 7;
    protected MAX_TEMP = 28;
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    protected registerMainService() {
        if (this.device.get('io:NativeFunctionalLevelState') === 'Top') {
            this.TARGET_MODES.push(Characteristics.TargetHeatingCoolingState.HEAT);
        }
        return super.registerMainService();
    }

    protected getTargetStateCommands(value): Command | Array<Command> {
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                if (this.device.get('io:NativeFunctionalLevelState') === 'Top') {
                    return new Command('setOperatingMode', 'auto');
                } else {
                    return new Command('setOperatingMode', this.prog?.value ? 'internal' : 'basic');
                }
            case Characteristics.TargetHeatingCoolingState.HEAT:
                if (this.device.get('io:NativeFunctionalLevelState') === 'Top') {
                    return new Command('setOperatingMode', this.prog?.value ? 'internal' : 'basic');
                }
                break;
            case Characteristics.TargetHeatingCoolingState.OFF:
                return new Command('setOperatingMode', 'standby');
        }
        return [];
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {
        if (this.prog?.value) {
            return new Command('setDerogatedTargetTemperature', value);
        } else {
            return new Command('setTargetTemperature', value);
        }
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:TemperatureState':
                this.onTemperatureUpdate(value);
                break;
            case 'io:EffectiveTemperatureSetpointState':
            case 'core:TargetTemperatureState':
            case 'io:TargetHeatingLevelState':
            case 'core:OperatingModeState':
                this.postpone(this.computeStates);
                break;

            default:
                super.onStateChanged(name, value);
                break;
        }
    }

    protected computeStates() {
        let targetState;
        let targetTemperature;
        targetState = Characteristics.TargetHeatingCoolingState.AUTO;
        switch (this.device.get('core:OperatingModeState')) {
            case 'off':
            case 'away':
            case 'frostprotection':
            case 'standby':
                targetState = Characteristics.TargetHeatingCoolingState.OFF;
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                targetTemperature = this.device.get('core:TargetTemperatureState');
                break;
            case 'auto':
                this.prog?.updateValue(false);
                if (this.device.get('io:TargetHeatingLevelState') === 'eco') {
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                } else {
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                }
                targetTemperature = this.device.get('io:EffectiveTemperatureSetpointState');
                break;
            case 'prog':
            case 'program':
            case 'internal':
            case 'comfort':
            case 'eco':
            case 'manual':
            case 'basic':
                if (this.device.get('io:NativeFunctionalLevelState') === 'Top') {
                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;
                }
                this.prog?.updateValue(['prog', 'program', 'internal'].includes(this.device.get('core:OperatingModeState')));
                if (this.device.get('io:TargetHeatingLevelState') === 'eco') {
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                } else {
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                }
                targetTemperature = this.device.get('io:EffectiveTemperatureSetpointState');
                break;
        }

        if (this.targetTemperature !== undefined && targetTemperature !== undefined && targetTemperature !== null) {
            this.targetTemperature.updateValue(targetTemperature);
        }

        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {
            this.targetState.updateValue(targetState);
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/AtlanticElectricalTowelDryer.ts
================================================
import { Characteristics, Services } from '../../Platform';
import { Characteristic } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class AtlanticElectricalTowelDryer extends HeatingSystem {
    protected THERMOSTAT_CHARACTERISTICS = ['prog'];
    protected MIN_TEMP = 7;
    protected MAX_TEMP = 28;
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    protected drying: Characteristic | undefined;

    protected registerServices() {
        const services = super.registerServices();
        if (this.device.hasCommand('setTowelDryerBoostModeDuration')) {
            const boost = this.registerSwitchService('boost');
            services.push(boost);
        }
        if (this.device.hasCommand('setDryingDuration')) {
            const drying = this.registerService(Services.Switch, 'drying');
            this.drying = drying.getCharacteristic(Characteristics.On);

            this.drying?.onSet(this.setDrying.bind(this));
            services.push(drying);
        }
        return services;
    }

    protected getTargetStateCommands(value): Command | Array<Command> {
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                return new Command('setTowelDryerOperatingMode', this.prog?.value ? 'internal' : 'external');
            case Characteristics.TargetHeatingCoolingState.OFF:
                return new Command('setTowelDryerOperatingMode', 'standby');
        }
        return [];
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {
        if (this.prog?.value) {
            return new Command('setDerogatedTargetTemperature', value);
        } else {
            return new Command('setTargetTemperature', value);
        }
    }

    protected getOnCommands(value): Command | Array<Command> {
        const commands = new Array<Command>();
        commands.push(new Command('setTowelDryerTemporaryState', value ? 'boost' : 'permanentHeating'));
        if (value) {
            commands.push(new Command('setTowelDryerBoostModeDuration', 10));
        }
        return commands;
    }

    protected async setDrying(value) {
        const commands = new Array<Command>();
        commands.push(new Command('setTowelDryerTemporaryState', value ? 'drying' : 'permanentHeating'));
        if (value) {
            commands.push(new Command('setDryingDuration', 60));
        }
        const action = await this.executeCommands(commands);
        action.on('update', (state) => {
            switch (state) {
                case ExecutionState.FAILED:
                    this.drying?.updateValue(!value);
                    break;
            }
        });
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:TemperatureState': this.onTemperatureUpdate(value); break;
            case 'io:TowelDryerTemporaryStateState':
                this.on?.updateValue(value === 'boost');
                this.drying?.updateValue(value === 'drying');
                break;
            case 'core:TargetTemperatureState':
            case 'core:DerogatedTargetTemperatureState':
            case 'core:ComfortRoomTemperatureState':
            case 'core:EcoRoomTemperatureState':
            case 'core:OperatingModeState':
            case 'io:TargetHeatingLevelState':
                this.postpone(this.computeStates);
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }

    protected computeStates() {
        let targetTemperature = Number(this.device.get('core:ComfortRoomTemperatureState'));
        switch (this.device.get('io:TargetHeatingLevelState')) {
            case 'off':
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                this.targetTemperature?.updateValue(this.device.get('core:TargetTemperatureState'));
                break;
            case 'eco':
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                targetTemperature = targetTemperature - Number(this.device.get('core:EcoRoomTemperatureState'));
                break;
            case 'comfort':
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                break;
        }

        switch (this.device.get('core:OperatingModeState')) {
            case 'standby':
                this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.OFF);
                break;
            case 'internal':
                this.prog?.updateValue(true);
                this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);
                if (Number(this.device.get('core:DerogatedTargetTemperatureState')) > 0) {
                    this.targetTemperature?.updateValue(this.device.get('core:DerogatedTargetTemperatureState'));
                } else {
                    this.targetTemperature?.updateValue(targetTemperature);
                }
                break;
            case 'external':
                this.prog?.updateValue(false);
                this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);
                this.targetTemperature?.updateValue(targetTemperature);
                break;
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/AtlanticPassAPCBoiler.ts
================================================
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class AtlanticPassAPCBoiler extends HeatingSystem {
    protected registerMainService() {
        return this.registerSwitchService();
    }

    protected getOnCommands(value): Command | Array<Command> {
        return new Command('setPassAPCOperatingMode', value ? 'heating' : 'stop');
    }

    protected onStateChanged(name, value) {
        switch (name) {
            case 'io:PassAPCOperatingModeState':
                switch (value) {
                    case 'stop':
                        this.on?.updateValue(false);
                        break;
                    case 'heating':
                    case 'drying':
                    case 'cooling':
                        this.on?.updateValue(true);
                        break;
                }
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/AtlanticPassAPCHeatPump.ts
================================================
import { Characteristic, Perms } from 'homebridge';
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';
import { TotalConsumptionCharacteristic } from '../../CustomCharacteristics';

export default class AtlanticPassAPCHeatPump extends HeatingSystem {
    protected MIN_TEMP = 0;
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.HEAT,
        Characteristics.TargetHeatingCoolingState.COOL,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];
    protected consumption: Characteristic | undefined;

    protected registerMainService() {
        const service = super.registerMainService();
        this.targetTemperature?.setProps({ perms: [Perms.PAIRED_READ, Perms.EVENTS] });
        return service;
    }

    protected getTargetStateCommands(value): Command | Array<Command> {
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                return new Command('setPassAPCOperatingMode', 'heating');

            case Characteristics.TargetHeatingCoolingState.HEAT:
                return new Command('setPassAPCOperatingMode', 'heating');

            case Characteristics.TargetHeatingCoolingState.COOL:
                return new Command('setPassAPCOperatingMode', 'cooling');

            default:
            case Characteristics.TargetHeatingCoolingState.OFF:
                return new Command('setPassAPCOperatingMode', 'stop');
        }
    }

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    protected getTargetTemperatureCommands(value): Command | Array<Command> {
        return [];
    }

    protected onStateChanged(name, value) {
        switch (name) {
            case 'io:PassAPCOperatingModeState':
                this.postpone(this.computeStates);
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }

    protected computeStates() {
        let targetState;
        switch (this.device.get('io:PassAPCOperatingModeState')) {
            case 'heating':
                targetState = Characteristics.TargetHeatingCoolingState.HEAT;
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                break;
            case 'cooling':
                targetState = Characteristics.TargetHeatingCoolingState.COOL;
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                break;
            case 'stop':
                targetState = Characteristics.TargetHeatingCoolingState.OFF;
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                break;
        }

        // eslint-disable-next-line eqeqeq
        if (this.targetState !== undefined && targetState != null && this.isIdle) {
            this.targetState.updateValue(targetState);
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/AtlanticPassAPCHeatingAndCoolingZone.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class AtlanticPassAPCHeatingAndCoolingZone extends HeatingSystem {
    protected THERMOSTAT_CHARACTERISTICS = ['prog'];
    protected MIN_TEMP = 16;
    protected MAX_TEMP = 30;
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    private refreshStatesTimeout;

    protected applyConfig(config) {
        super.applyConfig(config);
    }

    protected getTargetStateCommands(value): Command | Array<Command> {
        const heatingCooling = this.getHeatingCooling();
        const commands: Array<Command> = [];
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                commands.push(new Command('set' + heatingCooling + 'OnOffState', 'on'));
                commands.push(new Command('setPassAPC' + heatingCooling + 'Mode', this.prog?.value ? 'internalScheduling' : 'manu'));
                break;

            case Characteristics.TargetHeatingCoolingState.OFF:
                commands.push(new Command('set' + heatingCooling + 'OnOffState', 'off'));
                break;
        }

        return commands;
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> {
        const heatingCooling = this.getHeatingCooling();
        if (this.prog?.value) {
            if (this.device.hasCommand('setDerogatedTargetTemperature')) {
                // AtlanticPassAPCHeatPump
                return [
                    new Command('setDerogatedTargetTemperature', value),
                    new Command('setDerogationTime', this.derogationDuration),
                    new Command('setDerogationOnOffState', 'on'),
                ];
            } else {
                const profile = this.getProfile();
                return new Command(`set${profile}${heatingCooling}TargetTemperature`, value);
            }
        } else {
            if (this.device.hasCommand(`set${heatingCooling}TargetTemperature`)) {
                // AtlanticPassAPCZoneControl
                return new Command(`set${heatingCooling}TargetTemperature`, value);
            } else {
                // AtlanticPassAPCHeatPump
                return new Command(`setComfort${heatingCooling}TargetTemperature`, value);
            }
        }
    }

    protected onStateChanged(name, value) {
        switch (name) {
            case 'core:TemperatureState':
                this.onTemperatureUpdate(value);
                break;
            case 'core:TargetTemperatureState':
                if (value >= 16) {
                    this.targetTemperature?.updateValue(value);
                }
                break;
            case 'core:HeatingOnOffState':
            case 'core:CoolingOnOffState':
            case 'io:PassAPCHeatingModeState':
            case 'io:PassAPCCoolingModeState':
            case 'io:PassAPCHeatingProfileState':
            case 'io:PassAPCCoolingProfileState':
                this.postpone(this.computeStates);
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }

    protected computeStates() {
        let targetState;
        let targetTemperature;
        const heatingCooling = this.getHeatingCooling();

        if (this.device.get(`core:${heatingCooling}OnOffState`) === 'off') {
            targetState = Characteristics.TargetHeatingCoolingState.OFF;
            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
        } else {
            targetTemperature = targetTemperature = this.device.get(`core:${heatingCooling}TargetTemperatureState`) ||
                this.device.get('core:TargetTemperatureState');
            const currentTemperature = this.currentTemperature?.value || targetTemperature;
            if (heatingCooling === 'Heating') {
                if (currentTemperature >= (targetTemperature + 0.5)) {
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                } else {
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                }
            } else {
                if (currentTemperature <= (targetTemperature - 0.5)) {
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                } else {
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                }
            }
            targetState = Characteristics.TargetHeatingCoolingState.AUTO;
        }

        if (this.device.get(`io:PassAPC${heatingCooling}ModeState`) === 'internalScheduling') {
            this.prog?.updateValue(true);
        } else {
            this.prog?.updateValue(false);
        }



        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {
            this.targetState.updateValue(targetState);
        }

        if (this.targetTemperature !== undefined && targetTemperature >= 16 && this.isIdle) {
            this.targetTemperature.updateValue(targetTemperature);
        }
    }

    /**
     * Helpers
     */
    private getHeatingCooling() {
        const operatingMode = this.device.parent?.get('io:PassAPCOperatingModeState');
        if (operatingMode === 'cooling') {
            return 'Cooling';
        } else {
            return 'Heating';
        }
    }

    private getProfile() {
        const heatingCooling = this.getHeatingCooling();
        if (this.device.get(`core:Eco${heatingCooling}TargetTemperatureState`) === this.device.get('core:TargetTemperatureState')) {
            return 'Eco';
        } else {
            return 'Comfort';
        }
    }

    private launchRefreshStates() {
        clearTimeout(this.refreshStatesTimeout);
        this.refreshStatesTimeout = setTimeout(() => {
            const commands = [
                new Command('refreshTargetTemperature'),
                new Command('refreshPassAPCHeatingProfile'),
            ];
            this.executeCommands(commands);
        }, 30 * 1000);
    }

    private launchRefreshTemperature() {
        clearTimeout(this.refreshStatesTimeout);
        this.refreshStatesTimeout = setTimeout(() => {
            this.executeCommands(new Command('refreshTargetTemperature'));
        }, 30 * 1000);
    }
}

================================================
FILE: src/mappers/HeatingSystem/AtlanticPassAPCHeatingZone.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class AtlanticPassAPCHeatingZone extends HeatingSystem {
    protected THERMOSTAT_CHARACTERISTICS = ['eco', 'prog'];
    protected MIN_TEMP = 10;
    protected MAX_TEMP = 35;
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    protected getTargetStateCommands(value): Command | Array<Command> {
        const commands: Array<Command> = [];
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                commands.push(new Command('setHeatingOnOffState', 'on'));
                if (this.prog?.value) {
                    commands.push(new Command('setPassAPCHeatingMode', 'internalScheduling'));
                } else {
                    commands.push(new Command('setDerogationOnOffState', 'off'));
                    if (this.eco?.value) {
                        commands.push(new Command('setPassAPCHeatingMode', 'eco'));
                    } else {
                        commands.push(new Command('setPassAPCHeatingMode', 'comfort'));
                    }
                }
                break;

            case Characteristics.TargetHeatingCoolingState.OFF:
                commands.push(new Command('setHeatingOnOffState', 'off'));
                //commands.push(new Command('setHeatingOnOffState', 'on'));
                //commands.push(new Command('setPassAPCHeatingMode', 'absence'));
                break;
        }
        return commands;
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> {
        const duration = this.derogationDuration;
        const commands: Array<Command> = [];
        if (this.prog?.value) {
            commands.push(new Command('setDerogatedTargetTemperature', value));
            commands.push(new Command('setDerogationTime', duration));
            commands.push(new Command('setDerogationOnOffState', 'on'));
        } else {
            if (this.eco?.value) {
                commands.push(new Command('setEcoHeatingTargetTemperature', value));
            } else {
                commands.push(new Command('setComfortHeatingTargetTemperature', value));
            }
        }
        return commands;
    }

    protected onStateChanged(name, value) {
        switch (name) {
            case 'core:TemperatureState': this.onTemperatureUpdate(value); break;
            case 'core:TargetTemperatureState':
            case 'core:HeatingOnOffState':
            case 'io:PassAPCHeatingModeState':
            case 'io:PassAPCHeatingProfileState':
            case 'core:ComfortHeatingTargetTemperatureState':
            case 'core:EcoHeatingTargetTemperatureState':
                this.postpone(this.computeStates);
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }

    protected computeStates() {
        let targetState;
        if (this.device.get('core:HeatingOnOffState') === 'on') {
            targetState = Characteristics.TargetHeatingCoolingState.AUTO;
            switch (this.device.get('io:PassAPCHeatingModeState')) {
                case 'off':
                case 'absence':
                    targetState = Characteristics.TargetHeatingCoolingState.OFF;
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                    this.targetTemperature?.updateValue(this.device.get('core:TargetTemperatureState'));
                    break;
                case 'auto':
                case 'internalScheduling':
                case 'externalScheduling':
                    this.prog?.updateValue(true);
                    if (this.device.get('io:PassAPCHeatingProfileState') === 'comfort') {
                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                    } else {
                        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                    }
                    if (this.device.get('io:PassAPCHeatingProfileState') === 'derogation') {
                        this.targetTemperature?.updateValue(this.device.get('core:DerogatedTargetTemperatureState'));
                    } else if (this.device.get('io:PassAPCHeatingProfileState') === 'comfort') {
                        this.targetTemperature?.updateValue(this.device.get('core:ComfortHeatingTargetTemperatureState'));
                    } else if (this.device.get('io:PassAPCHeatingProfileState') === 'eco') {
                        this.targetTemperature?.updateValue(this.device.get('core:EcoHeatingTargetTemperatureState'));
                    } else {
                        this.targetTemperature?.updateValue(this.device.get('core:TargetTemperatureState'));
                    }
                    break;
                case 'comfort':
                    this.prog?.updateValue(false);
                    this.eco?.updateValue(false);
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                    this.targetTemperature?.updateValue(this.device.get('core:ComfortHeatingTargetTemperatureState'));
                    break;
                case 'eco':
                    this.prog?.updateValue(false);
                    this.eco?.updateValue(true);
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                    this.targetTemperature?.updateValue(this.device.get('core:EcoHeatingTargetTemperatureState'));
                    break;
            }
        } else {
            targetState = Characteristics.TargetHeatingCoolingState.OFF;
            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
            this.targetTemperature?.updateValue(this.device.get('core:TargetTemperatureState'));
        }
        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {
            this.targetState.updateValue(targetState);
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/AtlanticPassAPCZoneControl.ts
================================================
import { Perms } from 'homebridge';
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class AtlanticPassAPCZoneControl extends HeatingSystem {
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.HEAT,
        Characteristics.TargetHeatingCoolingState.COOL,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    protected registerMainService() {
        const service = super.registerMainService();
        this.targetTemperature?.setProps({ perms: [Perms.PAIRED_READ, Perms.EVENTS] });
        return service;
    }

    protected getTargetStateCommands(value): Command | Array<Command> {
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                return [
                    new Command('setPassAPCOperatingMode', 'heating'),
                    new Command('setHeatingCoolingAutoSwitch', 'on'),
                ];

            case Characteristics.TargetHeatingCoolingState.HEAT:
                return [
                    new Command('setPassAPCOperatingMode', 'heating'),
                    new Command('setHeatingCoolingAutoSwitch', 'off'),
                ];

            case Characteristics.TargetHeatingCoolingState.COOL:
                return [
                    new Command('setPassAPCOperatingMode', 'cooling'),
                    new Command('setHeatingCoolingAutoSwitch', 'off'),
                ];

            default:
            case Characteristics.TargetHeatingCoolingState.OFF:
                return [
                    new Command('setPassAPCOperatingMode', 'stop'),
                ];
        }
    }

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    protected getTargetTemperatureCommands(value): Command | Array<Command> {
        return [];
    }

    protected onStateChanged(name, value) {
        switch (name) {
            case 'io:PassAPCOperatingModeState':
            case 'core:HeatingCoolingAutoSwitchState':
                this.postpone(this.computeStates);
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }

    protected computeStates() {
        let targetState;
        switch (this.device.get('io:PassAPCOperatingModeState')) {
            case 'heating':
                if (this.device.get('core:HeatingCoolingAutoSwitchState') === 'on') {
                    targetState = Characteristics.TargetHeatingCoolingState.AUTO;
                } else {
                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;
                }
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                break;
            case 'cooling':
                if (this.device.get('core:HeatingCoolingAutoSwitchState') === 'on') {
                    targetState = Characteristics.TargetHeatingCoolingState.AUTO;
                } else {
                    targetState = Characteristics.TargetHeatingCoolingState.COOL;
                }
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                break;
            case 'stop':
                targetState = Characteristics.TargetHeatingCoolingState.OFF;
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                break;
        }

        // eslint-disable-next-line eqeqeq
        if (this.targetState !== undefined && targetState != null && this.isIdle) {
            this.targetState.updateValue(targetState);
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/ProgrammableAndProtectableThermostatSetPoint.ts
================================================
import ThermostatSetPoint from './ThermostatSetPoint';

export default class ProgrammableAndProtectableThermostatSetPoint extends ThermostatSetPoint {   
    
}

================================================
FILE: src/mappers/HeatingSystem/SomfyHeatingTemperatureInterface.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class SomfyHeatingTemperatureInterface extends HeatingSystem {
    protected THERMOSTAT_CHARACTERISTICS = ['prog', 'eco'];
    protected MIN_TEMP = 0;
    protected MAX_TEMP = 26;
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.HEAT,
        Characteristics.TargetHeatingCoolingState.COOL,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    protected getTargetStateCommands(value): Command | Array<Command> | undefined {
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                return [
                    new Command('setOnOff', 'on'),
                    new Command('setOperatingMode', 'both'),
                ];

            case Characteristics.TargetHeatingCoolingState.HEAT:
                return [
                    new Command('setOnOff', 'on'),
                    new Command('setOperatingMode', 'heating'),
                ];

            case Characteristics.TargetHeatingCoolingState.COOL:
                return [
                    new Command('setOnOff', 'on'),
                    new Command('setOperatingMode', 'cooling'),
                ];

            case Characteristics.TargetHeatingCoolingState.OFF:
                return new Command('setOnOff', 'off');
        }
    }

    protected getProgCommands(): Command | Array<Command> | undefined {
        if (this.prog?.value) {
            return new Command('setActiveMode', 'auto');
        } else {
            if (this.eco?.value) {
                return new Command('setManuAndSetPointModes', 'eco');
            } else {
                return new Command('setManuAndSetPointModes', 'comfort');
            }
        }
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {
        if (this.device.get('ovp:HeatingTemperatureInterfaceSetPointModeState') === 'comfort') {
            return new Command('setComfortTemperature', value);
        } else {
            return new Command('setEcoTemperature', value);
        }
    }

    protected onStateChanged(name, value) {
        switch (name) {
            case 'core:OnOffState':
            case 'ovp:HeatingTemperatureInterfaceOperatingModeState':
                this.postpone(this.computeStates);
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }

    protected computeStates() {
        let targetState;
        if (this.device.get('core:OnOffState') === 'on') {
            switch (this.device.get('ovp:HeatingTemperatureInterfaceOperatingModeState')) {
                case 'both':
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                    targetState = Characteristics.TargetHeatingCoolingState.AUTO;
                    break;
                case 'heating':
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                    targetState = Characteristics.TargetHeatingCoolingState.HEAT;
                    break;
                case 'cooling':
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                    targetState = Characteristics.TargetHeatingCoolingState.COOL;
                    break;
            }
        } else {
            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
            targetState = Characteristics.TargetHeatingCoolingState.OFF;
        }
        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {
            this.targetState.updateValue(targetState);
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/SomfyThermostat.ts
================================================
import { Characteristics } from '../../Platform';
import { Command, ExecutionState } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class SomfyThermostat extends HeatingSystem {
    protected MIN_TEMP = 0;
    protected MAX_TEMP = 26;
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.HEAT,
        Characteristics.TargetHeatingCoolingState.COOL,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];
    private lastRefresh = Date.now();

    protected registerMainService() {
        const service = super.registerMainService();
        this.targetState?.onGet(this.refreshStates.bind(this));
        return service;
    }

    protected async refreshStates() {
        if (this.lastRefresh < Date.now() - (60 * 1000)) {
            this.lastRefresh = Date.now();
            await this.executeCommands(new Command('refreshState'));
        }
        return this.targetState?.value ?? Characteristics.TargetHeatingCoolingState.OFF;
    }

    protected getTargetStateCommands(value): Command | Array<Command> | undefined {
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                return new Command('exitDerogation');

            case Characteristics.TargetHeatingCoolingState.HEAT:
                return new Command('setDerogation', ['atHomeMode', 'further_notice']);

            case Characteristics.TargetHeatingCoolingState.COOL:
                return new Command('setDerogation', ['sleepingMode', 'further_notice']);

            case Characteristics.TargetHeatingCoolingState.OFF:
                return new Command('setDerogation', ['awayMode', 'further_notice']);
        }
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {
        return new Command('setDerogation', [value, 'further_notice']);
    }

    protected onStateChanged(name, value) {
        switch (name) {
            case 'core:TargetTemperatureState':
            case 'core:DerogatedTargetTemperatureState':
            case 'core:DerogationActivationState':
            case 'somfythermostat:DerogationHeatingModeState':
                this.postpone(this.computeStates);
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }

    protected computeStates() {
        let targetState;
        let targetTemperature;

        const derog = this.device.get('core:DerogationActivationState') === 'active';
        const mode = this.device.get(derog ? 'somfythermostat:DerogationHeatingModeState' : 'somfythermostat:HeatingModeState');
        switch (mode) {
            case 'atHomeMode':
            case 'geofencingMode':
            case 'manualMode':
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                targetState = derog ? Characteristics.TargetHeatingCoolingState.HEAT : Characteristics.TargetHeatingCoolingState.AUTO;
                break;
            case 'sleepingMode':
            case 'suddenDropMode':
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                targetState = derog ? Characteristics.TargetHeatingCoolingState.COOL : Characteristics.TargetHeatingCoolingState.AUTO;
                break;
            case 'awayMode':
            case 'freezeMode':
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                targetState = Characteristics.TargetHeatingCoolingState.OFF;
                break;
        }

        switch (mode) {
            case 'atHomeMode':
                targetTemperature = this.device.get('somfythermostat:AtHomeTargetTemperatureState');
                break;
            case 'geofencingMode':
                targetTemperature = this.device.get('somfythermostat:GeofencingModeTargetTemperatureState');
                break;
            case 'manualMode':
                targetTemperature = this.device.get('somfythermostat:ManualModeTargetTemperatureState');
                break;
            case 'sleepingMode':
                targetTemperature = this.device.get('somfythermostat:SleepingModeTargetTemperatureState');
                break;
            case 'suddenDropMode':
                targetTemperature = this.device.get('somfythermostat:SuddenDropModeTargetTemperatureState');
                break;
            case 'awayMode':
                targetTemperature = this.device.get('somfythermostat:AwayModeTargetTemperatureState');
                break;
            case 'freezeMode':
                targetTemperature = this.device.get('somfythermostat:FreezeModeTargetTemperatureState');
                break;
        }
        if (targetTemperature === undefined || targetTemperature === null) {
            targetTemperature = this.device.get(derog ? 'core:DerogatedTargetTemperatureState' : 'core:TargetTemperatureState');
        }

        if (this.targetTemperature !== undefined && targetTemperature !== undefined) {
            this.targetTemperature.updateValue(targetTemperature);
        }

        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {
            this.targetState.updateValue(targetState);
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/ThermostatSetPoint.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class ThermostatSetPoint extends HeatingSystem {
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
    ];

    protected registerMainService() {
        const service = super.registerMainService();
        this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);
        this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
        return service;
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> {
        return new Command('setHeatingTargetTemperature', value);
    }

    protected onStateChanged(name, value) {
        switch (name) {
            case 'zwave:SetPointHeatingValueState':
            case 'core:RoomTemperatureState':
                this.onTemperatureUpdate(value);
                break;
            case 'core:HeatingTargetTemperatureState':
                this.targetTemperature?.updateValue(value);
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem/ValveHeatingTemperatureInterface.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class ValveHeatingTemperatureInterface extends HeatingSystem {
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.HEAT,
        Characteristics.TargetHeatingCoolingState.COOL,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    protected getTargetStateCommands(value): Command | Array<Command> | undefined {
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                return new Command('exitDerogation');

            case Characteristics.TargetHeatingCoolingState.HEAT:
                return new Command('setDerogation', ['comfort', 'further_notice']);

            case Characteristics.TargetHeatingCoolingState.COOL:
                return new Command('setDerogation', ['eco', 'further_notice']);

            case Characteristics.TargetHeatingCoolingState.OFF:
                return new Command('setDerogation', ['away', 'further_notice']);
        }
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {
        return new Command('setDerogation', [value, 'further_notice']);
    }

    protected onStateChanged(name, value) {
        switch (name) {
            case 'core:OperatingModeState':
            case 'io:CurrentHeatingModeState':
                this.postpone(this.computeStates);
                break;
            case 'core:TargetRoomTemperatureState':
                this.targetTemperature?.updateValue(value);
                break;
            default:
                super.onStateChanged(name, value);
                break;
        }
    }

    protected computeStates() {
        let targetState;

        const auto = ['auto', 'prog', 'program'].includes(this.device.get('core:OperatingModeState') || '');

        switch (this.device.get('io:CurrentHeatingModeState')) {
            case 'manual':
            case 'comfort':
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                targetState = auto ? Characteristics.TargetHeatingCoolingState.AUTO : Characteristics.TargetHeatingCoolingState.HEAT;
                break;
            case 'eco':
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                targetState = auto ? Characteristics.TargetHeatingCoolingState.AUTO : Characteristics.TargetHeatingCoolingState.COOL;
                break;
            case 'off':
            case 'awayMode':
            case 'frostprotection':
                this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                targetState = Characteristics.TargetHeatingCoolingState.OFF;
                break;
        }
        if (this.targetState !== undefined && targetState !== undefined && this.isIdle) {
            this.targetState.updateValue(targetState);
        }
    }
}

================================================
FILE: src/mappers/HeatingSystem.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic, Service } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import Mapper from '../Mapper';
import { EcoCharacteristic, ProgCharacteristic, TotalConsumptionCharacteristic } from '../CustomCharacteristics';

export default class HeatingSystem extends Mapper {
    protected THERMOSTAT_CHARACTERISTICS: string[] = [];
    protected MIN_TEMP = 7;
    protected MAX_TEMP = 30;
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    protected currentTemperature: Characteristic | undefined;
    protected targetTemperature: Characteristic | undefined;
    protected currentState: Characteristic | undefined;
    protected targetState: Characteristic | undefined;

    protected on: Characteristic | undefined;

    protected prog: Characteristic | undefined;
    protected eco: Characteristic | undefined;

    protected consumption: Characteristic | undefined;

    protected derogationDuration;
    protected comfortTemperature;
    protected ecoTemperature;

    protected applyConfig(config) {
        this.derogationDuration = config['derogationDuration'] || 1;
        this.comfortTemperature = config['comfort'] || 19;
        this.ecoTemperature = config['eco'] || 17;
    }

    protected registerMainService(): Service {
        const service = this.registerService(Services.Thermostat);
        service.setPrimaryService(true);
        service.addOptionalCharacteristic(ProgCharacteristic);
        service.addOptionalCharacteristic(EcoCharacteristic);
        this.currentTemperature = service.getCharacteristic(Characteristics.CurrentTemperature);
        this.targetTemperature = service.getCharacteristic(Characteristics.TargetTemperature);
        this.currentState = service.getCharacteristic(Characteristics.CurrentHeatingCoolingState);
        this.targetState = service.getCharacteristic(Characteristics.TargetHeatingCoolingState);

        this.currentTemperature.setProps({ minStep: 0.1 });

        this.targetState?.setProps({ validValues: this.TARGET_MODES });
        this.targetTemperature?.setProps({ minValue: this.MIN_TEMP, maxValue: this.MAX_TEMP, minStep: 0.5 });
        const temp = Number(this.targetTemperature.value)
        if (this.targetTemperature && temp < this.targetTemperature.props.minValue!) {
            this.targetTemperature.value = this.targetTemperature.props.minValue!;
        }
        if (this.targetTemperature && temp > this.targetTemperature.props.maxValue!) {
            this.targetTemperature.value = this.targetTemperature.props.maxValue!;
        }

        if (this.THERMOSTAT_CHARACTERISTICS.includes('prog')) {
            this.prog = service.getCharacteristic(ProgCharacteristic);
            this.prog.onSet((value) => {
                this.prog?.updateValue(value);
                this.sendProgCommands();
            });
        }

        if (this.THERMOSTAT_CHARACTERISTICS.includes('eco')) {
            this.eco = service.getCharacteristic(EcoCharacteristic);
            this.eco.onSet((value) => {
                this.eco?.updateValue(value);
                this.sendProgCommands();
            });
        }

        if (this.device.hasSensor('CumulativeElectricPowerConsumptionSensor')) {
            service.addOptionalCharacteristic(TotalConsumptionCharacteristic);
            this.consumption = service.getCharacteristic(TotalConsumptionCharacteristic);
        }

        this.targetState?.onSet(this.setTargetState.bind(this));
        this.targetTemperature?.onSet(this.debounce(this.setTargetTemperature));
        return service;
    }

    protected registerSwitchService(subtype?: string): Service {
        const service = this.registerService(Services.Switch, subtype);
        this.on = service.getCharacteristic(Characteristics.On);

        this.on?.onSet(this.setOn.bind(this));
        return service;
    }

    protected getTargetStateCommands(value): Command | Array<Command> | undefined {
        switch (value) {
            case Characteristics.TargetHeatingCoolingState.AUTO:
                return new Command('auto');
            case Characteristics.TargetHeatingCoolingState.HEAT:
                return new Command('heat');
            case Characteristics.TargetHeatingCoolingState.COOL:
                return new Command('cool');
            case Characteristics.TargetHeatingCoolingState.OFF:
                return new Command('off');
            default:
                return new Command('auto');
        }
    }

    protected async setTargetState(value) {
        if (value === this.targetState?.value) {
            return;
        }
        const action = await this.executeCommands(this.getTargetStateCommands(value));
        action.on('update', (state) => {
            switch (state) {
                case ExecutionState.COMPLETED:
                    if (this.stateless) {
                        this.currentState?.updateValue(value);
                    }
                    break;
                case ExecutionState.FAILED:
                    if (this.currentState) {
                        this.targetState?.updateValue(this.currentState.value);
                    }
                    break;
            }
        });
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> | undefined {
        return new Command('setTargetTemperature', value);
    }

    protected async setTargetTemperature(value) {
        await this.executeCommands(this.getTargetTemperatureCommands(value));
    }

    protected getOnCommands(value): Command | Array<Command> | undefined {
        return new Command('setOn', value);
    }

    protected async setOn(value) {
        const action = await this.executeCommands(this.getOnCommands(value));
        action.on('update', (state) => {
            switch (state) {
                case ExecutionState.FAILED:
                    this.on?.updateValue(!value);
                    break;
            }
        });
    }

    protected getProgCommands(): Command | Array<Command> | undefined {
        return this.getTargetStateCommands(this.targetState?.value);
    }

    protected sendProgCommands() {
        if (this.targetState?.value !== Characteristics.TargetHeatingCoolingState.OFF) {
            this.executeCommands(this.getProgCommands());
        }
    }

    protected onTemperatureUpdate(value) {
        this.currentTemperature?.updateValue(value > 273.15 ? (value - 273.15) : value);
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:TemperatureState': this.onTemperatureUpdate(value); break;
            case 'core:TargetTemperatureState':
                this.targetTemperature?.updateValue(value);
                break;
            case 'core:ElectricEnergyConsumptionState':
                this.consumption?.updateValue(value / 1000);
                break;
        }
    }
}


================================================
FILE: src/mappers/HitachiHeatingSystem/HitachiAirToAirHeatPump.ts
================================================
import { Characteristics } from '../../Platform';
import { Command } from 'overkiz-client';
import HeatingSystem from '../HeatingSystem';

export default class HitachiAirToAirHeatPump extends HeatingSystem {
    protected MIN_TEMP = 16;
    protected MAX_TEMP = 30;
    protected TARGET_MODES = [
        Characteristics.TargetHeatingCoolingState.AUTO,
        Characteristics.TargetHeatingCoolingState.HEAT,
        Characteristics.TargetHeatingCoolingState.COOL,
        Characteristics.TargetHeatingCoolingState.OFF,
    ];

    protected getTargetStateCommands(value): Command | Array<Command> | undefined {
        return this.getCommands(value, this.targetTemperature?.value);
    }

    protected getTargetTemperatureCommands(value): Command | Array<Command> {
        return this.getCommands(this.targetState?.value, value);
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'ovp:ModeChangeState':
            case 'ovp:MainOperationState':
                if (this.device.get('ovp:MainOperationState') === 'Off' || this.device.get('ovp:MainOperationState') === 'off') {
                    this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.OFF);
                    this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.OFF);
                } else {
                    switch (this.device.get('ovp:ModeChangeState')?.toLowerCase()) {
                        case 'auto cooling':
                            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                            this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);
                            break;
                        case 'auto heating':
                            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                            this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.AUTO);
                            break;
                        case 'cooling':
                            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.COOL);
                            this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.COOL);
                            break;
                        case 'heating':
                            this.currentState?.updateValue(Characteristics.CurrentHeatingCoolingState.HEAT);
                            this.targetState?.updateValue(Characteristics.TargetHeatingCoolingState.HEAT);
                            break;
                    }
                }
                break;
            case 'ovp:RoomTemperatureState':
                this.onTemperatureUpdate(value);
                break;
            case 'core:TargetTemperatureState':
                this.targetTemperature?.updateValue(value);
                break;
            /*
            case 'ovp:TemperatureChangeState':
                if(value <= 5 && this.currentTemperature) {
                    this.targetTemperature?.updateValue(this.currentTemperature.value + value);
                } else {
                    this.targetTemperature?.updateValue(value);
                }
                break;
            */
        }
    }

    private getCommands(state, temperature) {
        const currentState = this.currentState ? this.currentState.value : 0;
        const currentTemperature = this.currentTemperature && this.currentTemperature.value !== null ? this.currentTemperature.value : 0;
        let onOff = 'on';
        const fanMode = 'auto';
        const progMode = 'manu';
        let heatMode = 'auto';
        const autoTemp = Math.trunc(Math.max(Math.min(temperature - parseInt(currentTemperature.toString()), 5), -5));

        switch (state) {
            case Characteristics.TargetHeatingCoolingState.OFF:
                onOff = 'off';
                switch (currentState) {
                    case Characteristics.CurrentHeatingCoolingState.HEAT:
                        heatMode = 'heating';
                        break;
                    case Characteristics.CurrentHeatingCoolingState.COOL:
                        heatMode = 'cooling';
                        break;
                    default:
                        temperature = autoTemp;
                        break;
                }
                break;

            case Characteristics.TargetHeatingCoolingState.HEAT:
                heatMode = 'heating';
                break;

            case Characteristics.TargetHeatingCoolingState.COOL:
                heatMode = 'cooling';
                break;

            case Characteristics.TargetHeatingCoolingState.AUTO:
                heatMode = 'auto';
                temperature = autoTemp;
                break;

            default:
                temperature = autoTemp;
                break;
        }

        temperature = Math.round(temperature);
        this.debug('FROM ' + currentState + '/' + currentTemperature + ' TO ' + state + '/' + temperature);

        return new Command('globalControl', [onOff, temperature, fanMode, heatMode, progMode]);
    }
}

================================================
FILE: src/mappers/HitachiHeatingSystem/HitachiAirToWaterHeatingZone.ts
================================================
import HeatingSystem from '../HeatingSystem';

export default class HitachiAirToWaterHeatingZone extends HeatingSystem {

}

================================================
FILE: src/mappers/HitachiHeatingSystem/HitachiAirToWaterMainComponent.ts
================================================
import HeatingSystem from '../HeatingSystem';

export default class HitachiAirToWaterMainComponent extends HeatingSystem {
    protected MIN_TEMP = 0;

}

================================================
FILE: src/mappers/HitachiHeatingSystem/HitachiDHW.ts
================================================
import WaterHeatingSystem from '../WaterHeatingSystem';

export default class HitachiDHW extends WaterHeatingSystem {

}

================================================
FILE: src/mappers/HumiditySensor/WaterDetectionSensor.ts
================================================
import { Characteristics } from '../../Platform';
import ContactSensor from '../ContactSensor';

export default class WaterDetectionSensor extends ContactSensor {
    protected onStateChanged(name: string, value) {
        switch(name) {
            case 'core:WaterDetectionState ':
                this.state?.updateValue(
                    value === 'detected' ? 
                        Characteristics.ContactSensorState.CONTACT_DETECTED :
                        Characteristics.ContactSensorState.CONTACT_NOT_DETECTED
                    );
                break;
        }
    }
}

================================================
FILE: src/mappers/HumiditySensor.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic } from 'homebridge';
import Mapper from '../Mapper';

export default class HumiditySensor extends Mapper {
    protected humidity: Characteristic | undefined;

    protected registerMainService() {
        const service = this.registerService(Services.HumiditySensor);
        this.humidity = service.getCharacteristic(Characteristics.CurrentRelativeHumidity);
        return service;
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:RelativeHumidityState':
                this.humidity?.updateValue(value);
                break;
        }
    }
}

================================================
FILE: src/mappers/Light.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic, CharacteristicSetCallback } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import Mapper from '../Mapper';

export default class Light extends Mapper {
    protected on: Characteristic | undefined;
    protected hue: Characteristic | undefined;
    protected brightness: Characteristic | undefined;
    protected saturation: Characteristic | undefined;

    protected registerMainService() {
        const service = this.registerService(this.device.hasCommand('setIntensity') ? Services.Lightbulb : Services.Switch);
        this.on = service.getCharacteristic(Characteristics.On);

        this.on.onSet(this.setOn.bind(this));

        if (this.device.hasCommand('setIntensity')) {
            this.brightness = service.getCharacteristic(Characteristics.Brightness);
            this.brightness.onSet(this.setBrightness.bind(this));

            if (this.device.hasCommand('setHueAndSaturation')) {
                this.hue = service.getCharacteristic(Characteristics.Hue);
                this.saturation = service.getCharacteristic(Characteristics.Saturation);
                this.saturation.onSet(this.setSaturation.bind(this));
            }
        }
        return service;
    }

    protected getOnOffCommands(value): Command | Array<Command> {
        return new Command(value ? 'on' : 'off');
    }

    protected async setOn(value) {
        const action = await this.executeCommands(this.getOnOffCommands(value));
        action.on('update', (state, data) => {
            switch (state) {
                case ExecutionState.COMPLETED:
                    break;
                case ExecutionState.FAILED:
                    break;
            }
        });
    }

    protected getBrightnessCommands(value): Command | Array<Command> {
        return new Command('setIntensity', value);
    }

    protected async setBrightness(value) {
        const action = await this.executeCommands(this.getBrightnessCommands(value));
        action.on('update', (state, data) => {
            switch (state) {
                case ExecutionState.COMPLETED:
                    break;
                case ExecutionState.FAILED:
                    break;
            }
        });
    }

    protected getSaturationCommands(value): Command | Array<Command> {
        return new Command('setHueAndSaturation', [this.hue?.value, value]);
    }

    protected async setSaturation(value) {
        const action = await this.executeCommands(this.getSaturationCommands(value));
        action.on('update', (state, data) => {
            switch (state) {
                case ExecutionState.COMPLETED:
                    break;
                case ExecutionState.FAILED:
                    break;
            }
        });
    }

    protected onStateChanged(name: string, value): boolean {
        switch (name) {
            case 'core:OnOffState':
                this.on?.updateValue(value === 'on');
                break;
            case 'core:IntensityState':
            case 'core:LightIntensityState':
                this.brightness?.updateValue(value);
                break;
            case 'core:ColorHueState':
                this.hue?.updateValue(value);
                break;
            case 'core:ColorSaturationState':
                this.saturation?.updateValue(value);
                break;
        }
        return false;
    }
}


================================================
FILE: src/mappers/LightSensor.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic } from 'homebridge';
import Mapper from '../Mapper';

export default class LightSensor extends Mapper {
    protected lightLevel: Characteristic | undefined;

    protected registerMainService() {
        const service = this.registerService(Services.LightSensor);
        this.lightLevel = service.getCharacteristic(Characteristics.CurrentAmbientLightLevel);
        return service;
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:LuminanceState':
                this.lightLevel?.updateValue(value);
                break;
        }
    }
}

================================================
FILE: src/mappers/OccupancySensor.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic } from 'homebridge';
import Mapper from '../Mapper';

export default class OccupancySensor extends Mapper {
    protected occupancy: Characteristic | undefined;
    protected fault: Characteristic | undefined;
    protected battery: Characteristic | undefined;

    protected registerMainService() {
        const motion = this.device.definition.widgetName.startsWith('Motion');
        const service = this.registerService(motion ? Services.MotionSensor : Services.OccupancySensor);
        this.occupancy = service.getCharacteristic(motion ? Characteristics.MotionDetected : Characteristics.OccupancyDetected);
        if (this.device.hasState('core:SensorDefectState')) {
            this.fault = service.getCharacteristic(Characteristics.StatusFault);
            this.battery = service.getCharacteristic(Characteristics.StatusLowBattery);
        }
        return service;
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:OccupancyState':
                this.occupancy?.updateValue(value === 'personInside');
                break;
            case 'core:SensorDefectState':
                switch (value) {
                    case 'lowBattery':
                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_LOW);
                        break;
                    case 'maintenanceRequired':
                    case 'dead':
                        this.fault?.updateValue(Characteristics.StatusFault.GENERAL_FAULT);
                        break;
                    case 'noDefect':
                        this.fault?.updateValue(Characteristics.StatusFault.NO_FAULT);
                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_NORMAL);
                        break;
                }
                break;
        }
    }
}

================================================
FILE: src/mappers/OnOff.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic, CharacteristicSetCallback } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import Mapper from '../Mapper';

export default class OnOff extends Mapper {
    protected on: Characteristic | undefined;

    protected registerMainService() {
        const service = this.registerService(Services.Switch);
        this.on = service.getCharacteristic(Characteristics.On);

        this.on.onSet(this.setOn.bind(this));
        return service;
    }

    protected getOnOffCommands(value): Command | Array<Command> {
        return new Command(value ? 'on' : 'off');
    }

    protected async setOn(value) {
        const action = await this.executeCommands(this.getOnOffCommands(value));
        action.on('update', (state, data) => {
            switch (state) {
                case ExecutionState.COMPLETED:
                    break;
                case ExecutionState.FAILED:
                    break;
            }
        });
    }

    protected onStateChanged(name: string, value): boolean {
        switch (name) {
            case 'core:OnOffState':
                this.on?.updateValue(value === 'on');
                break;
        }
        return false;
    }
}

================================================
FILE: src/mappers/Pergola/BioclimaticPergola.ts
================================================
import { Command } from 'overkiz-client';
import Pergola from '../Pergola';

export default class BioclimaticPergola extends Pergola {
    protected getTargetCommands(value) {
        return new Command('setOrientation', this.reversedValue(value));
    }

    protected onStateChanged(name, value) {
        switch(name) {
            case 'core:SlatsOrientationState':
                this.currentPosition?.updateValue(this.reversedValue(value));
                if(this.isIdle) {
                    this.targetPosition?.updateValue(this.reversedValue(value));
                }
                break;
            default: break;
        }
    }
}

================================================
FILE: src/mappers/Pergola/PergolaHorizontalAwningUno.ts
================================================
import Pergola from '../Pergola';

export default class PergolaHorizontalAwningUno extends Pergola {
    protected onStateChanged(name: string, value) {
        // Fix (https://github.com/dubocr/homebridge-tahoma/issues/305)
        value = 100 - value;
        switch (name) {
            case 'core:ClosureState':
                this.currentPosition?.updateValue(this.reversedValue(value));
                if (!this.device.hasState('core:TargetClosureState') && this.isIdle) {
                    this.targetPosition?.updateValue(this.reversedValue(value));
                }
                break;
            case 'core:TargetClosureState':
                this.targetPosition?.updateValue(this.reversedValue(value));
                if (!this.device.hasState('core:ClosureState')) {
                    this.currentPosition?.updateValue(this.reversedValue(value));
                }
                break;
        }
    }
}

================================================
FILE: src/mappers/Pergola.ts
================================================
import RollerShutter from './RollerShutter';

export default class Pergola extends RollerShutter {
}

================================================
FILE: src/mappers/RainSensor.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic } from 'homebridge';
import Mapper from '../Mapper';

export default class RainSensor extends Mapper {
    protected rain: Characteristic | undefined;
    protected fault: Characteristic | undefined;
    protected battery: Characteristic | undefined;

    protected registerMainService() {
        const service = this.registerService(Services.ContactSensor);
        this.rain = service.getCharacteristic(Characteristics.ContactSensorState);
        if (this.device.hasState('core:SensorDefectState')) {
            this.fault = service.getCharacteristic(Characteristics.StatusFault);
            this.battery = service.getCharacteristic(Characteristics.StatusLowBattery);
        }
        return service;
    }

    protected onStateChanged(name: string, value) {
        switch (name) {
            case 'core:RainState':
                this.rain?.updateValue(value === 'detected');
                break;
            case 'core:SensorDefectState':
                switch (value) {
                    case 'lowBattery':
                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_LOW);
                        break;
                    case 'maintenanceRequired':
                    case 'dead':
                        this.fault?.updateValue(Characteristics.StatusFault.GENERAL_FAULT);
                        break;
                    case 'noDefect':
                        this.fault?.updateValue(Characteristics.StatusFault.NO_FAULT);
                        this.battery?.updateValue(Characteristics.StatusLowBattery.BATTERY_LEVEL_NORMAL);
                        break;
                }
                break;
        }
    }
}

================================================
FILE: src/mappers/RemoteController.ts
================================================
import { Characteristic } from 'homebridge';
import Mapper from '../Mapper';
import { Characteristics, Services } from '../Platform';

export default class RemoteController extends Mapper {
    protected event: Characteristic | undefined;

    protected registerMainService() {
        throw new Error('Service RemoteController not implemented');
        const service = this.registerService(Services.StatelessProgrammableSwitch);
        this.event = service.getCharacteristic(Characteristics.ProgrammableSwitchEvent);
        return service;
    }
    
    
    protected onStateChanged(name: string, value) {
        switch(name) {
            default: this.event?.updateValue(value);
        }
    }
}

================================================
FILE: src/mappers/RollerShutter/PositionableRollerShutterUno.ts
================================================
import RollerShutter from '../RollerShutter';
export default class PositionableRollerShutterUno extends RollerShutter {
    protected onStateChanged(name: string, value) {
        switch(name) {
            case 'core:TargetClosureState':
                if(this.isIdle) {
                    this.targetPosition?.updateValue(this.reversedValue(value));
                }
                this.currentPosition?.updateValue(this.reversedValue(value));
                break;
        }
    }
}

================================================
FILE: src/mappers/RollerShutter/PositionableRollerShutterWithLowSpeedManagement.ts
================================================
import moment from 'moment';
import { Command } from 'overkiz-client';
import RollerShutter from '../RollerShutter';
export default class PositionableRollerShutterWithLowSpeedManagement extends RollerShutter {
    protected lowSpeed;

    protected applyConfig(config) {
        this.lowSpeed = config['lowSpeed'] || false;
    }

    protected getTargetCommands(value) {
        if (this.isLowSpeed) {
            return new Command('setClosureAndLinearSpeed', [this.reversedValue(value), 'lowspeed']);
        } else {
            return new Command('setClosure', this.reversedValue(value));
        }
    }

    protected get isLowSpeed() {
        if (this.lowSpeed === true) {
            return true;
        } else if (typeof this.lowSpeed === 'string') {
            const parts = this.lowSpeed.split(new RegExp('[-:]'));
            const now = moment();
            const start = moment();
            const end = moment();
            start.set({ 'hour': parseInt(parts[0]), 'minute': parseInt(parts[1]), 'second': 0, 'millisecond': 0 });
            end.set({ 'hour': parseInt(parts[2]), 'minute': parseInt(parts[3]), 'second': 0, 'millisecond': 0 });
            if (end.isBefore(start)) {
                return now.isAfter(start) || now.isBefore(end);
            } else {
                return now.isBetween(start, end);
            }
        } else {
            return false;
        }
    }
}

================================================
FILE: src/mappers/RollerShutter.ts
================================================
import { Characteristics, Services } from '../Platform';
import { Characteristic, Service } from 'homebridge';
import { Command, ExecutionState } from 'overkiz-client';
import Mapper from '../Mapper';
import { MyPositionCharacteristic } from '../CustomCharacteristics';

export default class RollerShutter extends Mapper {
    protected expectedStates = ['core:ClosureState', 'core:TargetClosureState'];
    protected windowService: Service | undefined;

    protected currentPosition: Characteristic | undefined;
    protected targetPosition: Characteristic | undefined;
    protected positionState: Characteristic | undefined;
    protected obstructionDetected: Characteristic | undefined;
    protected my: Characteristic | undefined;

    protected reverse;
    protected initPosition;
    protected defaultPosition;
    protected blindsOnRollerShutter;
    protected movementDuration;
    protected offsetMovementDuration;

    protected cancelTimeout;

    protected applyConfig(config) {
        this.defaultPosition = config['defaultPosition'] || 0;
        this.initPosition = config['initPosition'] !== undefined ? config['initPosition'] : (config['defaultPosition'] || 50);
        this.reverse = config['reverse'] || false;
        this.movementDuration = config['movementDuration'] || 0;
        this.offsetMovementDuration = config['offsetMovementDuration'] || 0;
        this.blindsOnRollerShutter = config['blindsOnRollerShutter'] || false;
    }

    protected registerMainService() {
        const service = this.registerService(Services.WindowCovering);
        service.addOptionalCharacteristic(MyPositionCharacteristic);
        this.currentPosition = service.getCharacteristic(Characteristics.CurrentPosition);
        this.targetPosition = service.getCharacteristic(Characteristics.TargetPosition);
        this.positionState = service.getCharacteristic(Characteristics.PositionState);
        if (this.stateless) {
            //this.currentPosition.updateValue(this.initPosition);
            //this.targetPosition.updateValue(this.initPosition);
            if (this.device.hasCommand('my')) {
                this.my = service.getCharacteristic(MyPositionCharacteristic);
                this.my.onSet(this.setMyPosition.bind(this));
            }
        } else {
            this.obstructionDetected = service.getCharacteristic(Characteristics.ObstructionDetected);
        }
        if (service.testCharacteristic(Characteristics.On)) {
            this.my = service.getCharacteristic(Characteristics.On);
            service.removeCharacteristic(this.my);
        }
        this.positionState.updateValue(Characteristics.PositionState.STOPPED);
        this.targetPosition.onSet(this.debounce(this.setTargetPosition, [0, 100]));
        return service;
    }

    /**
    * Triggered when Homekit try to modify the Characteristic.TargetPosition
    * HomeKit '0' (Close) => 0% Deployment
    * HomeKit '100' (Open) => 100% Deployment
    **/
    protected getTargetCommands(value): Command | Command[] {
        if (this.stateless) {
            if (value === 100) {
                return new Command(this.reverse ? 'close' : 'open');
            } else if (value === 0) {
                return new Command(this.reverse ? 'open' : 'close');
            } else {
                if (this.movementDuration > 0) {
                    const delta = value - Number(this.currentPosition!.value);
                    if (this.reverse) {
                        return new Command(delta > 0 ? 'close' : 'open');
                    } else {
                        return new Command(delta > 0 ? 'open' : 'close');
                    }
                } else {
                    return new Command('my');
                }
            }
        } else {
            return new Command('setClosure', this.reversedValue(value));
        }
    }

    /**
    * Triggered when Homekit try to modify the Characteristic.TargetPosition
    * HomeKit '0' (Close) => 100% Closure
    * HomeKit '100' (Open) => 0% Closure
    **/
    async setTargetPosition(value) {
        if (this.cancelTimeout !== null) {
            clearTimeout(this.cancelTimeout);
        }
        const standalone = this.stateless && this.movementDuration > 0 && value !== 100 && value !== 0;
        const action = await this.executeCommands(this.getTargetCommands(value), standalone);
        action.on('update', (state, data) => {
            const positionState = (value === 100 || value > (this.currentPosition?.value || 0)) ?
                Characteristics.PositionState.INCREASING :
                Characteristics.PositionState.DECREASING;
            switch (state) {
                case ExecutionState.IN_PROGRESS:
                    if (standalone) {
                        const delta = value - Number(this.currentPosition!.value);
                        const duration = this.offsetMovementDuration * 1000 + Math.round(this.movementDuration * Math.abs(delta) * 1000 / 100);
                        this.info('Will stop movement in ' + duration + ' millisec');
                        this.cancelTimeout = setTimeout(() => {
                            this.cancelTimeout = null;
                            if (this.isIdle) {
                                this.executeCommands(new Command('stop'), true);
                            } else {
                                this.cancelExecution().catch(this.error.bind(this));
                            }
                        }, duration);
                    }
                    this.positionState?.updateValue(positionState);
                    break;
                case ExecutionState.COMPLETED:
                    this.positionState?.updateValue(Characteristics.PositionState.STOPPED);
                    if (this.stateless) {
                        if (this.defaultPosition) {
                            this.currentPosition?.updateValue(this.defaultPosition);
                            this.targetPosition?.updateValue(this.defaultPosition);
                        } else {
                            this.currentPosition?.updateValue(value);
                        }
                    } else {
                        this.obstructionDetected?.updateValue(false);
                    }
                    if (this.blindsOnRollerShutter && value < 98) {
                        this.executeCommands(new Command('setClosure', value 
Download .txt
gitextract_f_m6ihgp/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   └── config.yml
│   └── workflows/
│       ├── new-release.yml
│       └── stale-issue.yml
├── .gitignore
├── .npmignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── config.schema.json
├── eslint.config.mjs
├── nodemon.json
├── package.json
├── platform.schema.json
├── src/
│   ├── CustomCharacteristics.ts
│   ├── Mapper.ts
│   ├── Platform.ts
│   ├── SceneMapper.ts
│   ├── colors.ts
│   ├── index.ts
│   ├── lang/
│   │   ├── en.json
│   │   └── fr.json
│   ├── mappers/
│   │   ├── AdjustableSlatsRollerShutter.ts
│   │   ├── AirSensor/
│   │   │   ├── CO2Sensor.ts
│   │   │   └── RelativeHumiditySensor.ts
│   │   ├── AirSensor.ts
│   │   ├── Alarm/
│   │   │   ├── MyFoxAlarmController.ts
│   │   │   └── TSKAlarmController.ts
│   │   ├── Alarm.ts
│   │   ├── Awning/
│   │   │   └── PositionableHorizontalAwningUno.ts
│   │   ├── Awning.ts
│   │   ├── ConsumptionSensor.ts
│   │   ├── ContactSensor.ts
│   │   ├── Curtain.ts
│   │   ├── DoorLock.ts
│   │   ├── ElectricitySensor/
│   │   │   └── CumulativeElectricPowerConsumptionSensor.ts
│   │   ├── ElectricitySensor.ts
│   │   ├── EvoHome/
│   │   │   ├── DHWSetPoint.ts
│   │   │   ├── EvoHomeController.ts
│   │   │   └── HeatingSetPoint.ts
│   │   ├── ExteriorHeatingSystem/
│   │   │   └── DimmerExteriorHeating.ts
│   │   ├── ExteriorHeatingSystem.ts
│   │   ├── ExteriorScreen.ts
│   │   ├── ExteriorVenetianBlind.ts
│   │   ├── GarageDoor.ts
│   │   ├── Gate.ts
│   │   ├── Generic/
│   │   │   ├── CyclicGeneric.ts
│   │   │   ├── DimmerOnOff.ts
│   │   │   ├── RTSGeneric.ts
│   │   │   └── RTSGeneric4T.ts
│   │   ├── HeatingSystem/
│   │   │   ├── AtlanticElectricalHeater.ts
│   │   │   ├── AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint.ts
│   │   │   ├── AtlanticElectricalTowelDryer.ts
│   │   │   ├── AtlanticPassAPCBoiler.ts
│   │   │   ├── AtlanticPassAPCHeatPump.ts
│   │   │   ├── AtlanticPassAPCHeatingAndCoolingZone.ts
│   │   │   ├── AtlanticPassAPCHeatingZone.ts
│   │   │   ├── AtlanticPassAPCZoneControl.ts
│   │   │   ├── ProgrammableAndProtectableThermostatSetPoint.ts
│   │   │   ├── SomfyHeatingTemperatureInterface.ts
│   │   │   ├── SomfyThermostat.ts
│   │   │   ├── ThermostatSetPoint.ts
│   │   │   └── ValveHeatingTemperatureInterface.ts
│   │   ├── HeatingSystem.ts
│   │   ├── HitachiHeatingSystem/
│   │   │   ├── HitachiAirToAirHeatPump.ts
│   │   │   ├── HitachiAirToWaterHeatingZone.ts
│   │   │   ├── HitachiAirToWaterMainComponent.ts
│   │   │   └── HitachiDHW.ts
│   │   ├── HumiditySensor/
│   │   │   └── WaterDetectionSensor.ts
│   │   ├── HumiditySensor.ts
│   │   ├── Light.ts
│   │   ├── LightSensor.ts
│   │   ├── OccupancySensor.ts
│   │   ├── OnOff.ts
│   │   ├── Pergola/
│   │   │   ├── BioclimaticPergola.ts
│   │   │   └── PergolaHorizontalAwningUno.ts
│   │   ├── Pergola.ts
│   │   ├── RainSensor.ts
│   │   ├── RemoteController.ts
│   │   ├── RollerShutter/
│   │   │   ├── PositionableRollerShutterUno.ts
│   │   │   └── PositionableRollerShutterWithLowSpeedManagement.ts
│   │   ├── RollerShutter.ts
│   │   ├── Screen.ts
│   │   ├── Shutter.ts
│   │   ├── Siren.ts
│   │   ├── SmokeSensor.ts
│   │   ├── SwingingShutter.ts
│   │   ├── TemperatureSensor.ts
│   │   ├── VenetianBlind.ts
│   │   ├── VentilationSystem/
│   │   │   └── DimplexVentilationInletOutlet.ts
│   │   ├── VentilationSystem.ts
│   │   ├── WaterHeatingSystem/
│   │   │   ├── AtlanticPassAPCDHW.ts
│   │   │   ├── DomesticHotWaterProduction/
│   │   │   │   └── AtlanticDomesticHotWaterProductionV2_SPLIT_IOComponent.ts
│   │   │   ├── DomesticHotWaterProduction.ts
│   │   │   └── DomesticHotWaterTank.ts
│   │   ├── WaterHeatingSystem.ts
│   │   ├── WaterSensor.ts
│   │   ├── Window.ts
│   │   └── WindowHandle.ts
│   └── settings.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (352 symbols across 83 files)

FILE: src/CustomCharacteristics.ts
  class CustomCharacteristics (line 11) | class CustomCharacteristics {
    method constructor (line 12) | constructor(hap: HAP) {

FILE: src/Mapper.ts
  method constructor (line 17) | constructor(
  method build (line 25) | public build() {
  method applyConfig (line 81) | protected applyConfig(config) {
  method registerService (line 85) | protected registerService(type: any, subtype?: string): Service {
  method translate (line 105) | private translate(value: string) {
  method debounce (line 113) | protected debounce(task, immediate: Array<CharacteristicValue> = []) {
  method postpone (line 129) | protected postpone(task, ...args) {
  method executeCommands (line 139) | protected async executeCommands(commands: Command | Array<Command> | und...
  method delay (line 195) | private async delay(duration) {
  method requestStatesUpdate (line 199) | protected async requestStatesUpdate(defer?: number) {
  method debug (line 210) | protected debug(...args) {
  method info (line 218) | protected info(...args) {
  method warn (line 222) | protected warn(...args) {
  method error (line 226) | protected error(...args) {
  method registerServices (line 230) | protected registerServices(): Array<Service> {
  method onStatesChanged (line 243) | protected onStatesChanged(states: Array<State>, init = false) {
  method isIdle (line 255) | get isIdle() {
  method cancelExecution (line 259) | async cancelExecution() {

FILE: src/Platform.ts
  constant DEFAULT_RETRY_DELAY (line 14) | const DEFAULT_RETRY_DELAY = 60;
  class Platform (line 21) | class Platform implements DynamicPlatformPlugin {
    method constructor (line 34) | constructor(public readonly log: Logger, public readonly config: Platf...
    method loadLocation (line 81) | async loadLocation() {
    method configureAccessory (line 97) | async configureAccessory(accessory: PlatformAccessory) {
    method discoverDevices (line 108) | async discoverDevices() {
    method executeAction (line 205) | public executeAction(label: string, action: Action, highPriority = fal...
    method translate (line 234) | public translate(label: string): string | null {

FILE: src/SceneMapper.ts
  class Mapper (line 6) | class Mapper {
    method constructor (line 12) | constructor(
    method isInProgress (line 26) | private get isInProgress() {
    method setOn (line 30) | protected async setOn(value) {

FILE: src/colors.ts
  constant RESET (line 1) | const RESET = '\x1b[0m';
  constant BRIGHT (line 2) | const BRIGHT = '\x1b[1m';
  constant DIM (line 3) | const DIM = '\x1b[2m';
  constant UNDERSCORE (line 4) | const UNDERSCORE = '\x1b[4m';
  constant BLINK (line 5) | const BLINK = '\x1b[5m';
  constant REVERSE (line 6) | const REVERSE = '\x1b[7m';
  constant HIDDEN (line 7) | const HIDDEN = '\x1b[8m';
  constant BLACK (line 9) | const BLACK = '\x1b[30m';
  constant RED (line 10) | const RED = '\x1b[31m';
  constant GREEN (line 11) | const GREEN = '\x1b[32m';
  constant YELLOW (line 12) | const YELLOW = '\x1b[33m';
  constant BLUE (line 13) | const BLUE = '\x1b[34m';
  constant MAGENTA (line 14) | const MAGENTA = '\x1b[35m';
  constant CYAN (line 15) | const CYAN = '\x1b[36m';
  constant LIGHT_GREY (line 16) | const LIGHT_GREY = '\x1b[37m';
  constant GREY (line 17) | const GREY = '\x1b[90m';
  constant WHITE (line 18) | const WHITE = '\x1b[97m';

FILE: src/mappers/AdjustableSlatsRollerShutter.ts
  class AdjustableSlatsRollerShutter (line 4) | class AdjustableSlatsRollerShutter extends VenetianBlind {
    method getTargetCommands (line 6) | protected getTargetCommands(value) {
    method getTargetAngleCommands (line 21) | protected getTargetAngleCommands(value) {
    method onStateChanged (line 28) | protected onStateChanged(name, value) {

FILE: src/mappers/AirSensor.ts
  class AirSensor (line 6) | class AirSensor extends Mapper {
    method registerMainService (line 9) | protected registerMainService(): Service {
    method onStateChanged (line 15) | protected onStateChanged(name: string, value) {

FILE: src/mappers/AirSensor/CO2Sensor.ts
  class RelativeHumiditySensor (line 5) | class RelativeHumiditySensor extends AirSensor {
    method registerMainService (line 8) | protected registerMainService() {
    method onStateChanged (line 17) | protected onStateChanged(name: string, value) {
    method co2ToQuality (line 26) | private co2ToQuality(value) {

FILE: src/mappers/AirSensor/RelativeHumiditySensor.ts
  class RelativeHumiditySensor (line 3) | class RelativeHumiditySensor extends HumiditySensor {

FILE: src/mappers/Alarm.ts
  class Alarm (line 6) | class Alarm extends Mapper {
    method applyConfig (line 14) | protected applyConfig(config) {
    method registerMainService (line 21) | protected registerMainService() {
    method getTargetCommands (line 30) | protected getTargetCommands(value): Command | Array<Command> {
    method setTargetState (line 44) | async setTargetState(value) {
    method onStateChanged (line 63) | protected onStateChanged(name: string, value) {

FILE: src/mappers/Alarm/MyFoxAlarmController.ts
  class MyFoxAlarmController (line 5) | class MyFoxAlarmController extends Alarm {
    method getTargetCommands (line 6) | protected getTargetCommands(value): Command | Array<Command> {
    method onStateChanged (line 20) | protected onStateChanged(name: string, value) {

FILE: src/mappers/Alarm/TSKAlarmController.ts
  class TSKAlarmController (line 5) | class TSKAlarmController extends Alarm {
    method getTargetCommands (line 6) | protected getTargetCommands(value): Command | Array<Command> {
    method onStateChanged (line 20) | protected onStateChanged(name: string, value) {

FILE: src/mappers/Awning.ts
  class Awning (line 4) | class Awning extends RollerShutter {
    method getTargetCommands (line 10) | protected getTargetCommands(value) {
    method reversedValue (line 33) | protected reversedValue(value) {
    method onStateChanged (line 37) | protected onStateChanged(name: string, value) {

FILE: src/mappers/Awning/PositionableHorizontalAwningUno.ts
  class PositionableHorizontalAwningUno (line 2) | class PositionableHorizontalAwningUno extends Awning {
    method onStateChanged (line 3) | protected onStateChanged(name: string, value) {

FILE: src/mappers/ConsumptionSensor.ts
  class ConsumptionSensor (line 4) | class ConsumptionSensor extends Mapper {
    method registerMainService (line 5) | protected registerMainService(): Service {
    method onStateChanged (line 9) | protected onStateChanged(name: string, value: any) {

FILE: src/mappers/ContactSensor.ts
  class ContactSensor (line 5) | class ContactSensor extends Mapper {
    method registerMainService (line 10) | protected registerMainService() {
    method onStateChanged (line 20) | protected onStateChanged(name: string, value) {

FILE: src/mappers/Curtain.ts
  class Curtain (line 3) | class Curtain extends RollerShutter {

FILE: src/mappers/DoorLock.ts
  class VentilationSystem (line 7) | class VentilationSystem extends Mapper {
    method registerMainService (line 11) | protected registerMainService() {
    method getTargetStateCommands (line 20) | protected getTargetStateCommands(value): Command | Array<Command> {
    method setTargetState (line 30) | protected async setTargetState(value) {
    method onStateChanged (line 48) | protected onStateChanged(name: string, value) {

FILE: src/mappers/ElectricitySensor.ts
  class ElectricitySensor (line 5) | class ElectricitySensor extends Mapper {
    method registerMainService (line 6) | protected registerMainService(): Service {
    method onStateChanged (line 11) | protected onStateChanged(name: string, value: any) {

FILE: src/mappers/ElectricitySensor/CumulativeElectricPowerConsumptionSensor.ts
  class CumulativeElectricPowerConsumptionSensor (line 7) | class CumulativeElectricPowerConsumptionSensor extends ElectricitySensor {
    method registerMainService (line 12) | protected registerMainService() {
    method onStateChanged (line 21) | protected onStateChanged(name: string, value) {

FILE: src/mappers/EvoHome/DHWSetPoint.ts
  class DHWSetPoint (line 4) | class DHWSetPoint extends TemperatureSensor {

FILE: src/mappers/EvoHome/EvoHomeController.ts
  class EvoHomeController (line 5) | class EvoHomeController extends HeatingSystem {
    method registerMainService (line 6) | protected registerMainService() {
    method getTargetStateCommands (line 15) | protected getTargetStateCommands(value): Command | Array<Command> | un...

FILE: src/mappers/EvoHome/HeatingSetPoint.ts
  class HeatingSetPoint (line 4) | class HeatingSetPoint extends HeatingSystem {
    method registerMainService (line 5) | protected registerMainService() {

FILE: src/mappers/ExteriorHeatingSystem.ts
  class ExteriorHeatingSystem (line 4) | class ExteriorHeatingSystem extends HeatingSystem {
    method registerMainService (line 5) | protected registerMainService() {
    method getOnCommands (line 9) | protected getOnCommands(value): Command | Array<Command> {

FILE: src/mappers/ExteriorHeatingSystem/DimmerExteriorHeating.ts
  class DimmerExteriorHeating (line 6) | class DimmerExteriorHeating extends ExteriorHeatingSystem {
    method registerMainService (line 9) | protected registerMainService() {
    method setBrightness (line 20) | protected async setBrightness(value) {
    method onStateChanged (line 32) | protected onStateChanged(name: string, value): boolean {

FILE: src/mappers/ExteriorScreen.ts
  class ExteriorScreen (line 3) | class ExteriorScreen extends RollerShutter {

FILE: src/mappers/ExteriorVenetianBlind.ts
  class ExteriorVenetianBlind (line 3) | class ExteriorVenetianBlind extends VenetianBlind {

FILE: src/mappers/GarageDoor.ts
  class GarageDoor (line 6) | class GarageDoor extends Mapper {
    method applyConfig (line 14) | protected applyConfig(config) {
    method registerMainService (line 19) | protected registerMainService() {
    method getTargetCommands (line 33) | protected getTargetCommands(value) {
    method setTargetState (line 41) | protected async setTargetState(value) {
    method onStateChanged (line 77) | protected onStateChanged(name: string, value) {

FILE: src/mappers/Gate.ts
  class Gate (line 6) | class Gate extends GarageDoor {
    method applyConfig (line 18) | protected applyConfig(config) {
    method registerServices (line 25) | protected registerServices() {
    method registerSwitchService (line 38) | protected registerSwitchService(subtype?: string): Service {
    method registerLockService (line 46) | protected registerLockService(subtype?: string): Service {
    method getLockCommands (line 55) | protected getLockCommands(value): Command | Array<Command> {
    method setLock (line 63) | protected async setLock(value) {
    method getOnCommands (line 100) | protected getOnCommands(value): Command | Array<Command> {
    method setOn (line 108) | protected async setOn(value) {
    method onStateChanged (line 119) | protected onStateChanged(name: string, value) {

FILE: src/mappers/Generic/CyclicGeneric.ts
  class CyclicGeneric (line 3) | class CyclicGeneric extends GarageDoor {

FILE: src/mappers/Generic/DimmerOnOff.ts
  class DimmerOnOff (line 3) | class DimmerOnOff extends Light {

FILE: src/mappers/Generic/RTSGeneric.ts
  class RTSGeneric (line 4) | class RTSGeneric extends RollerShutter {
    method getTargetCommands (line 5) | protected getTargetCommands(value) {

FILE: src/mappers/Generic/RTSGeneric4T.ts
  class RTSGeneric (line 3) | class RTSGeneric extends GarageDoor {

FILE: src/mappers/HeatingSystem.ts
  class HeatingSystem (line 7) | class HeatingSystem extends Mapper {
    method applyConfig (line 32) | protected applyConfig(config) {
    method registerMainService (line 38) | protected registerMainService(): Service {
    method registerSwitchService (line 86) | protected registerSwitchService(subtype?: string): Service {
    method getTargetStateCommands (line 94) | protected getTargetStateCommands(value): Command | Array<Command> | un...
    method setTargetState (line 109) | protected async setTargetState(value) {
    method getTargetTemperatureCommands (line 130) | protected getTargetTemperatureCommands(value): Command | Array<Command...
    method setTargetTemperature (line 134) | protected async setTargetTemperature(value) {
    method getOnCommands (line 138) | protected getOnCommands(value): Command | Array<Command> | undefined {
    method setOn (line 142) | protected async setOn(value) {
    method getProgCommands (line 153) | protected getProgCommands(): Command | Array<Command> | undefined {
    method sendProgCommands (line 157) | protected sendProgCommands() {
    method onTemperatureUpdate (line 163) | protected onTemperatureUpdate(value) {
    method onStateChanged (line 167) | protected onStateChanged(name: string, value) {

FILE: src/mappers/HeatingSystem/AtlanticElectricalHeater.ts
  constant FROSTPROTECTION_TEMP (line 6) | const FROSTPROTECTION_TEMP = 7;
  class AtlanticElectricalHeater (line 8) | class AtlanticElectricalHeater extends HeatingSystem {
    method registerMainService (line 16) | protected registerMainService() {
    method getTargetStateCommands (line 27) | protected getTargetStateCommands(value): Command | Array<Command> {
    method setTargetTemperature (line 41) | protected async setTargetTemperature(value) {
    method getTargetTemperatureCommands (line 61) | protected getTargetTemperatureCommands(value): Command | Array<Command...
    method getProgCommands (line 74) | protected getProgCommands(): Command | Array<Command> | undefined {
    method onStateChanged (line 78) | protected onStateChanged(name, value) {

FILE: src/mappers/HeatingSystem/AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint.ts
  class AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint (line 5) | class AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint extends ...
    method registerMainService (line 14) | protected registerMainService() {
    method getTargetStateCommands (line 21) | protected getTargetStateCommands(value): Command | Array<Command> {
    method getTargetTemperatureCommands (line 40) | protected getTargetTemperatureCommands(value): Command | Array<Command...
    method onStateChanged (line 48) | protected onStateChanged(name: string, value) {
    method computeStates (line 66) | protected computeStates() {

FILE: src/mappers/HeatingSystem/AtlanticElectricalTowelDryer.ts
  class AtlanticElectricalTowelDryer (line 6) | class AtlanticElectricalTowelDryer extends HeatingSystem {
    method registerServices (line 17) | protected registerServices() {
    method getTargetStateCommands (line 33) | protected getTargetStateCommands(value): Command | Array<Command> {
    method getTargetTemperatureCommands (line 43) | protected getTargetTemperatureCommands(value): Command | Array<Command...
    method getOnCommands (line 51) | protected getOnCommands(value): Command | Array<Command> {
    method setDrying (line 60) | protected async setDrying(value) {
    method onStateChanged (line 76) | protected onStateChanged(name: string, value) {
    method computeStates (line 97) | protected computeStates() {

FILE: src/mappers/HeatingSystem/AtlanticPassAPCBoiler.ts
  class AtlanticPassAPCBoiler (line 4) | class AtlanticPassAPCBoiler extends HeatingSystem {
    method registerMainService (line 5) | protected registerMainService() {
    method getOnCommands (line 9) | protected getOnCommands(value): Command | Array<Command> {
    method onStateChanged (line 13) | protected onStateChanged(name, value) {

FILE: src/mappers/HeatingSystem/AtlanticPassAPCHeatPump.ts
  class AtlanticPassAPCHeatPump (line 7) | class AtlanticPassAPCHeatPump extends HeatingSystem {
    method registerMainService (line 17) | protected registerMainService() {
    method getTargetStateCommands (line 23) | protected getTargetStateCommands(value): Command | Array<Command> {
    method getTargetTemperatureCommands (line 41) | protected getTargetTemperatureCommands(value): Command | Array<Command> {
    method onStateChanged (line 45) | protected onStateChanged(name, value) {
    method computeStates (line 56) | protected computeStates() {

FILE: src/mappers/HeatingSystem/AtlanticPassAPCHeatingAndCoolingZone.ts
  class AtlanticPassAPCHeatingAndCoolingZone (line 5) | class AtlanticPassAPCHeatingAndCoolingZone extends HeatingSystem {
    method applyConfig (line 16) | protected applyConfig(config) {
    method getTargetStateCommands (line 20) | protected getTargetStateCommands(value): Command | Array<Command> {
    method getTargetTemperatureCommands (line 37) | protected getTargetTemperatureCommands(value): Command | Array<Command> {
    method onStateChanged (line 62) | protected onStateChanged(name, value) {
    method computeStates (line 86) | protected computeStates() {
    method getHeatingCooling (line 134) | private getHeatingCooling() {
    method getProfile (line 143) | private getProfile() {
    method launchRefreshStates (line 152) | private launchRefreshStates() {
    method launchRefreshTemperature (line 163) | private launchRefreshTemperature() {

FILE: src/mappers/HeatingSystem/AtlanticPassAPCHeatingZone.ts
  class AtlanticPassAPCHeatingZone (line 5) | class AtlanticPassAPCHeatingZone extends HeatingSystem {
    method getTargetStateCommands (line 14) | protected getTargetStateCommands(value): Command | Array<Command> {
    method getTargetTemperatureCommands (line 40) | protected getTargetTemperatureCommands(value): Command | Array<Command> {
    method onStateChanged (line 57) | protected onStateChanged(name, value) {
    method computeStates (line 74) | protected computeStates() {

FILE: src/mappers/HeatingSystem/AtlanticPassAPCZoneControl.ts
  class AtlanticPassAPCZoneControl (line 6) | class AtlanticPassAPCZoneControl extends HeatingSystem {
    method registerMainService (line 14) | protected registerMainService() {
    method getTargetStateCommands (line 20) | protected getTargetStateCommands(value): Command | Array<Command> {
    method getTargetTemperatureCommands (line 49) | protected getTargetTemperatureCommands(value): Command | Array<Command> {
    method onStateChanged (line 53) | protected onStateChanged(name, value) {
    method computeStates (line 65) | protected computeStates() {

FILE: src/mappers/HeatingSystem/ProgrammableAndProtectableThermostatSetPoint.ts
  class ProgrammableAndProtectableThermostatSetPoint (line 3) | class ProgrammableAndProtectableThermostatSetPoint extends ThermostatSet...

FILE: src/mappers/HeatingSystem/SomfyHeatingTemperatureInterface.ts
  class SomfyHeatingTemperatureInterface (line 5) | class SomfyHeatingTemperatureInterface extends HeatingSystem {
    method getTargetStateCommands (line 16) | protected getTargetStateCommands(value): Command | Array<Command> | un...
    method getProgCommands (line 41) | protected getProgCommands(): Command | Array<Command> | undefined {
    method getTargetTemperatureCommands (line 53) | protected getTargetTemperatureCommands(value): Command | Array<Command...
    method onStateChanged (line 61) | protected onStateChanged(name, value) {
    method computeStates (line 73) | protected computeStates() {

FILE: src/mappers/HeatingSystem/SomfyThermostat.ts
  class SomfyThermostat (line 5) | class SomfyThermostat extends HeatingSystem {
    method registerMainService (line 16) | protected registerMainService() {
    method refreshStates (line 22) | protected async refreshStates() {
    method getTargetStateCommands (line 30) | protected getTargetStateCommands(value): Command | Array<Command> | un...
    method getTargetTemperatureCommands (line 46) | protected getTargetTemperatureCommands(value): Command | Array<Command...
    method onStateChanged (line 50) | protected onStateChanged(name, value) {
    method computeStates (line 64) | protected computeStates() {

FILE: src/mappers/HeatingSystem/ThermostatSetPoint.ts
  class ThermostatSetPoint (line 5) | class ThermostatSetPoint extends HeatingSystem {
    method registerMainService (line 10) | protected registerMainService() {
    method getTargetTemperatureCommands (line 17) | protected getTargetTemperatureCommands(value): Command | Array<Command> {
    method onStateChanged (line 21) | protected onStateChanged(name, value) {

FILE: src/mappers/HeatingSystem/ValveHeatingTemperatureInterface.ts
  class ValveHeatingTemperatureInterface (line 5) | class ValveHeatingTemperatureInterface extends HeatingSystem {
    method getTargetStateCommands (line 13) | protected getTargetStateCommands(value): Command | Array<Command> | un...
    method getTargetTemperatureCommands (line 29) | protected getTargetTemperatureCommands(value): Command | Array<Command...
    method onStateChanged (line 33) | protected onStateChanged(name, value) {
    method computeStates (line 48) | protected computeStates() {

FILE: src/mappers/HitachiHeatingSystem/HitachiAirToAirHeatPump.ts
  class HitachiAirToAirHeatPump (line 5) | class HitachiAirToAirHeatPump extends HeatingSystem {
    method getTargetStateCommands (line 15) | protected getTargetStateCommands(value): Command | Array<Command> | un...
    method getTargetTemperatureCommands (line 19) | protected getTargetTemperatureCommands(value): Command | Array<Command> {
    method onStateChanged (line 23) | protected onStateChanged(name: string, value) {
    method getCommands (line 69) | private getCommands(state, temperature) {

FILE: src/mappers/HitachiHeatingSystem/HitachiAirToWaterHeatingZone.ts
  class HitachiAirToWaterHeatingZone (line 3) | class HitachiAirToWaterHeatingZone extends HeatingSystem {

FILE: src/mappers/HitachiHeatingSystem/HitachiAirToWaterMainComponent.ts
  class HitachiAirToWaterMainComponent (line 3) | class HitachiAirToWaterMainComponent extends HeatingSystem {

FILE: src/mappers/HitachiHeatingSystem/HitachiDHW.ts
  class HitachiDHW (line 3) | class HitachiDHW extends WaterHeatingSystem {

FILE: src/mappers/HumiditySensor.ts
  class HumiditySensor (line 5) | class HumiditySensor extends Mapper {
    method registerMainService (line 8) | protected registerMainService() {
    method onStateChanged (line 14) | protected onStateChanged(name: string, value) {

FILE: src/mappers/HumiditySensor/WaterDetectionSensor.ts
  class WaterDetectionSensor (line 4) | class WaterDetectionSensor extends ContactSensor {
    method onStateChanged (line 5) | protected onStateChanged(name: string, value) {

FILE: src/mappers/Light.ts
  class Light (line 6) | class Light extends Mapper {
    method registerMainService (line 12) | protected registerMainService() {
    method getOnOffCommands (line 31) | protected getOnOffCommands(value): Command | Array<Command> {
    method setOn (line 35) | protected async setOn(value) {
    method getBrightnessCommands (line 47) | protected getBrightnessCommands(value): Command | Array<Command> {
    method setBrightness (line 51) | protected async setBrightness(value) {
    method getSaturationCommands (line 63) | protected getSaturationCommands(value): Command | Array<Command> {
    method setSaturation (line 67) | protected async setSaturation(value) {
    method onStateChanged (line 79) | protected onStateChanged(name: string, value): boolean {

FILE: src/mappers/LightSensor.ts
  class LightSensor (line 5) | class LightSensor extends Mapper {
    method registerMainService (line 8) | protected registerMainService() {
    method onStateChanged (line 14) | protected onStateChanged(name: string, value) {

FILE: src/mappers/OccupancySensor.ts
  class OccupancySensor (line 5) | class OccupancySensor extends Mapper {
    method registerMainService (line 10) | protected registerMainService() {
    method onStateChanged (line 21) | protected onStateChanged(name: string, value) {

FILE: src/mappers/OnOff.ts
  class OnOff (line 6) | class OnOff extends Mapper {
    method registerMainService (line 9) | protected registerMainService() {
    method getOnOffCommands (line 17) | protected getOnOffCommands(value): Command | Array<Command> {
    method setOn (line 21) | protected async setOn(value) {
    method onStateChanged (line 33) | protected onStateChanged(name: string, value): boolean {

FILE: src/mappers/Pergola.ts
  class Pergola (line 3) | class Pergola extends RollerShutter {

FILE: src/mappers/Pergola/BioclimaticPergola.ts
  class BioclimaticPergola (line 4) | class BioclimaticPergola extends Pergola {
    method getTargetCommands (line 5) | protected getTargetCommands(value) {
    method onStateChanged (line 9) | protected onStateChanged(name, value) {

FILE: src/mappers/Pergola/PergolaHorizontalAwningUno.ts
  class PergolaHorizontalAwningUno (line 3) | class PergolaHorizontalAwningUno extends Pergola {
    method onStateChanged (line 4) | protected onStateChanged(name: string, value) {

FILE: src/mappers/RainSensor.ts
  class RainSensor (line 5) | class RainSensor extends Mapper {
    method registerMainService (line 10) | protected registerMainService() {
    method onStateChanged (line 20) | protected onStateChanged(name: string, value) {

FILE: src/mappers/RemoteController.ts
  class RemoteController (line 5) | class RemoteController extends Mapper {
    method registerMainService (line 8) | protected registerMainService() {
    method onStateChanged (line 16) | protected onStateChanged(name: string, value) {

FILE: src/mappers/RollerShutter.ts
  class RollerShutter (line 7) | class RollerShutter extends Mapper {
    method applyConfig (line 26) | protected applyConfig(config) {
    method registerMainService (line 35) | protected registerMainService() {
    method getTargetCommands (line 65) | protected getTargetCommands(value): Command | Command[] {
    method setTargetPosition (line 93) | async setTargetPosition(value) {
    method setMyPosition (line 158) | async setMyPosition(value) {
    method reversedValue (line 188) | protected reversedValue(value) {
    method onStateChanged (line 192) | protected onStateChanged(name: string, value) {

FILE: src/mappers/RollerShutter/PositionableRollerShutterUno.ts
  class PositionableRollerShutterUno (line 2) | class PositionableRollerShutterUno extends RollerShutter {
    method onStateChanged (line 3) | protected onStateChanged(name: string, value) {

FILE: src/mappers/RollerShutter/PositionableRollerShutterWithLowSpeedManagement.ts
  class PositionableRollerShutterWithLowSpeedManagement (line 4) | class PositionableRollerShutterWithLowSpeedManagement extends RollerShut...
    method applyConfig (line 7) | protected applyConfig(config) {
    method getTargetCommands (line 11) | protected getTargetCommands(value) {
    method isLowSpeed (line 19) | protected get isLowSpeed() {

FILE: src/mappers/Screen.ts
  class Screen (line 3) | class Screen extends RollerShutter {

FILE: src/mappers/Shutter.ts
  class Shutter (line 3) | class Shutter extends RollerShutter {

FILE: src/mappers/Siren.ts
  class Siren (line 6) | class Siren extends Mapper {
    method registerMainService (line 10) | protected registerMainService() {
    method getMuteCommands (line 22) | protected getMuteCommands(value): Command | Array<Command> {
    method setMute (line 26) | protected async setMute(value) {
    method getVolumeCommands (line 38) | protected getVolumeCommands(value): Command | Array<Command> {
    method setVolume (line 42) | protected async setVolume(value) {
    method onStateChanged (line 54) | protected onStateChanged(name: string, value) {

FILE: src/mappers/SmokeSensor.ts
  class SmokeSensor (line 5) | class SmokeSensor extends Mapper {
    method registerMainService (line 11) | protected registerMainService() {
    method onStateChanged (line 29) | protected onStateChanged(name: string, value) {

FILE: src/mappers/SwingingShutter.ts
  class SwingingShutter (line 3) | class SwingingShutter extends RollerShutter {

FILE: src/mappers/TemperatureSensor.ts
  class TemperatureSensor (line 5) | class TemperatureSensor extends Mapper {
    method registerMainService (line 8) | protected registerMainService() {
    method onStateChanged (line 14) | protected onStateChanged(name: string, value) {

FILE: src/mappers/VenetianBlind.ts
  class VenetianBlind (line 6) | class VenetianBlind extends RollerShutter {
    method applyConfig (line 12) | protected applyConfig(config) {
    method registerMainService (line 17) | protected registerMainService() {
    method orientationToAngle (line 36) | protected orientationToAngle(value) {
    method angleToOrientation (line 40) | protected angleToOrientation(value) {
    method getTargetCommands (line 44) | protected getTargetCommands(value): Command | Command[] {
    method getTargetAngleCommands (line 85) | protected getTargetAngleCommands(value) {
    method setTargetAnglePosition (line 104) | async setTargetAnglePosition(value) {
    method onStateChanged (line 117) | protected onStateChanged(name, value) {

FILE: src/mappers/VentilationSystem.ts
  class VentilationSystem (line 6) | class VentilationSystem extends Mapper {
    method registerMainService (line 11) | protected registerMainService() {
    method getTargetStateCommands (line 21) | protected getTargetStateCommands(value): Command | Array<Command> {
    method setTargetState (line 31) | protected async setTargetState(value) {
    method onStateChanged (line 49) | protected onStateChanged(name: string, value) {

FILE: src/mappers/VentilationSystem/DimplexVentilationInletOutlet.ts
  class DimplexVentilationInletOutlet (line 6) | class DimplexVentilationInletOutlet extends VentilationSystem {
    method getTargetStateCommands (line 7) | protected getTargetStateCommands(value): Command | Array<Command> {

FILE: src/mappers/WaterHeatingSystem.ts
  class WaterHeatingSystem (line 5) | class WaterHeatingSystem extends HeatingSystem {
    method registerMainService (line 13) | protected registerMainService(): Service {

FILE: src/mappers/WaterHeatingSystem/AtlanticPassAPCDHW.ts
  class AtlanticPassAPCDHW (line 5) | class AtlanticPassAPCDHW extends WaterHeatingSystem {
    method registerServices (line 8) | protected registerServices() {
    method getTargetStateCommands (line 17) | protected getTargetStateCommands(value): Command | Array<Command> {
    method getTargetTemperatureCommands (line 40) | protected getTargetTemperatureCommands(value): Command | Array<Command> {
    method getOnCommands (line 48) | protected getOnCommands(value): Command | Array<Command> {
    method onStateChanged (line 52) | protected onStateChanged(name: string, value) {
    method computeStates (line 74) | protected computeStates() {

FILE: src/mappers/WaterHeatingSystem/DomesticHotWaterProduction.ts
  class DomesticHotWaterProduction (line 7) | class DomesticHotWaterProduction extends WaterHeatingSystem {
    method registerMainService (line 17) | protected registerMainService() {
    method registerServices (line 33) | protected registerServices() {
    method getTargetStateCommands (line 40) | protected getTargetStateCommands(value): Command | Array<Command> | un...
    method getTargetTemperatureCommands (line 98) | protected getTargetTemperatureCommands(value): Command | Array<Command> {
    method getOnCommands (line 102) | protected getOnCommands(value): Command | Array<Command> {
    method setTargetShower (line 106) | async setTargetShower(value) {
    method onStateChanged (line 120) | protected onStateChanged(name: string, value) {
    method computeStates (line 156) | protected computeStates(protcol) {

FILE: src/mappers/WaterHeatingSystem/DomesticHotWaterProduction/AtlanticDomesticHotWaterProductionV2_SPLIT_IOComponent.ts
  class AtlanticDomesticHotWaterProductionV2_SPLIT_IOComponent (line 6) | class AtlanticDomesticHotWaterProductionV2_SPLIT_IOComponent extends Dom...
    method registerMainService (line 14) | protected registerMainService(): Service {
    method getTargetTemperatureCommands (line 25) | protected getTargetTemperatureCommands(value): Command | Array<Command> {
    method getTargetStateCommands (line 30) | protected getTargetStateCommands(value): Command | Array<Command> | un...
    method getOnCommands (line 53) | protected getOnCommands(value): Command | Array<Command> {
    method onStateChanged (line 57) | protected onStateChanged(name: string, value) {
    method computeStates (line 72) | protected computeStates() {

FILE: src/mappers/WaterHeatingSystem/DomesticHotWaterTank.ts
  class DomesticHotWaterTank (line 5) | class DomesticHotWaterTank extends WaterHeatingSystem {
    method registerMainService (line 6) | protected registerMainService(): Service {
    method getOnCommands (line 10) | protected getOnCommands(value): Command | Array<Command> {
    method onStateChanged (line 14) | protected onStateChanged(name: string, value) {

FILE: src/mappers/WaterSensor.ts
  class WaterSensor (line 5) | class WaterSensor extends Mapper {
    method registerMainService (line 8) | protected registerMainService() {
    method onStateChanged (line 14) | protected onStateChanged(name: string, value) {

FILE: src/mappers/Window.ts
  class Window (line 4) | class Window extends RollerShutter {
    method registerMainService (line 5) | protected registerMainService() {

FILE: src/mappers/WindowHandle.ts
  class WindowHandle (line 4) | class WindowHandle extends ContactSensor {
    method onStateChanged (line 5) | protected onStateChanged(name: string, value) {

FILE: src/settings.ts
  constant PLATFORM_NAME (line 5) | const PLATFORM_NAME = 'Tahoma';
  constant PLUGIN_NAME (line 10) | const PLUGIN_NAME = 'homebridge-tahoma';
Condensed preview — 102 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (245K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 476,
    "preview": "blank_issues_enabled: true\ncontact_links:\n  - name: Device integration and bug\n    url: https://dev.duboc.pro/homebridge"
  },
  {
    "path": ".github/workflows/new-release.yml",
    "chars": 356,
    "preview": "name: Create Release\non:\n  push:\n    tags:  \n      - v*\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - "
  },
  {
    "path": ".github/workflows/stale-issue.yml",
    "chars": 675,
    "preview": "name: Mark stale issues and pull requests\n\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  stale:\n    runs-on: ubuntu-"
  },
  {
    "path": ".gitignore",
    "chars": 1876,
    "preview": "# Ignore compiled code\ndist\nold\n\n# ------------- Defaults ------------- #\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.l"
  },
  {
    "path": ".npmignore",
    "chars": 1948,
    "preview": "# Ignore source code\nsrc\n\n# ------------- Defaults ------------- #\n\n# eslint\n.eslintrc\n\n# typescript\ntsconfig.json\n\n# vs"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 59,
    "preview": "{\n  \"recommendations\": [\n    \"dbaeumer.vscode-eslint\"\n  ]\n}"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 153,
    "preview": "{\n  \"files.eol\": \"\\n\",\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\"\n  },\n  \"editor.rulers\": [ "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 81,
    "preview": "## 2.2.54 (2022-12-20)\n\n### Bug Fixes\n\n* Fix nodejs compatibility to node v12.4.0"
  },
  {
    "path": "LICENSE",
    "chars": 10172,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 7813,
    "preview": "# Overkiz (Somfy) - Homebridge-TaHoma\n\nHomebridge plugin supporting Overkiz based platforms :\n\n| Service code\t\t\t\t| Vendo"
  },
  {
    "path": "config.schema.json",
    "chars": 8202,
    "preview": "{\n  \"pluginAlias\": \"Tahoma\",\n  \"pluginType\": \"platform\",\n  \"singular\": false,\n  \"schema\": {\n    \"type\": \"object\",\n    \"p"
  },
  {
    "path": "eslint.config.mjs",
    "chars": 1722,
    "preview": "import tsParser from \"@typescript-eslint/parser\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\""
  },
  {
    "path": "nodemon.json",
    "chars": 176,
    "preview": "{\n  \"watch\": [\n    \"src\"\n  ],\n  \"ext\": \"ts\",\n  \"ignore\": [],\n  \"exec\": \"tsc && homebridge -I -D\",\n  \"signal\": \"SIGTERM\","
  },
  {
    "path": "package.json",
    "chars": 1586,
    "preview": "{\n  \"name\": \"homebridge-tahoma\",\n  \"displayName\": \"Homebridge TaHoma\",\n  \"version\": \"2.2.61\",\n  \"description\": \"Sample P"
  },
  {
    "path": "platform.schema.json",
    "chars": 2528,
    "preview": "{\n  \"plugin_alias\": \"Tahoma\",\n  \"schema\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"platform\": {\n        \"title"
  },
  {
    "path": "src/CustomCharacteristics.ts",
    "chars": 4472,
    "preview": "import { HAP, Characteristic, Perms, Formats, WithUUID } from 'homebridge';\n\nexport let CurrentShowerCharacteristic: Wit"
  },
  {
    "path": "src/Mapper.ts",
    "chars": 9954,
    "preview": "import { Characteristics, Services } from './Platform';\nimport { CharacteristicValue, HAPStatus, Logger, PlatformAccesso"
  },
  {
    "path": "src/Platform.ts",
    "chars": 11415,
    "preview": "import { API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service } from 'homebrid"
  },
  {
    "path": "src/SceneMapper.ts",
    "chars": 1777,
    "preview": "import { Characteristics, Services } from './Platform';\nimport { Characteristic, Logger, PlatformAccessory, Service } fr"
  },
  {
    "path": "src/colors.ts",
    "chars": 564,
    "preview": "export const RESET = '\\x1b[0m';\nexport const BRIGHT = '\\x1b[1m';\nexport const DIM = '\\x1b[2m';\nexport const UNDERSCORE ="
  },
  {
    "path": "src/index.ts",
    "chars": 258,
    "preview": "import { API } from 'homebridge';\n\nimport { PLATFORM_NAME } from './settings';\nimport { Platform } from './Platform';\n\n/"
  },
  {
    "path": "src/lang/en.json",
    "chars": 375,
    "preview": "{\n    \"others\": \":param other(s)\",\n    \"setClosure\": \"Close :param%\",\n    \"setHeatingLevel\": {\n        \"comfort\": \"Comfo"
  },
  {
    "path": "src/lang/fr.json",
    "chars": 381,
    "preview": "{\n    \"others\": \":param autre(s)\",\n    \"setClosure\": \"Fermeture :param%\",\n    \"setHeatingLevel\": {\n        \"comfort\": \"M"
  },
  {
    "path": "src/mappers/AdjustableSlatsRollerShutter.ts",
    "chars": 1375,
    "preview": "import { Command } from 'overkiz-client';\nimport VenetianBlind from './VenetianBlind';\n\nexport default class AdjustableS"
  },
  {
    "path": "src/mappers/AirSensor/CO2Sensor.ts",
    "chars": 1282,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Characteristic } from 'homebridge';\nimport AirSensor from '.."
  },
  {
    "path": "src/mappers/AirSensor/RelativeHumiditySensor.ts",
    "chars": 120,
    "preview": "import HumiditySensor from '../HumiditySensor';\n\nexport default class RelativeHumiditySensor extends HumiditySensor {\n\n}"
  },
  {
    "path": "src/mappers/AirSensor.ts",
    "chars": 625,
    "preview": "\nimport { Characteristics, Services } from '../Platform';\nimport { Characteristic, Service } from 'homebridge';\nimport M"
  },
  {
    "path": "src/mappers/Alarm/MyFoxAlarmController.ts",
    "chars": 1836,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport Alarm from '../Alarm'"
  },
  {
    "path": "src/mappers/Alarm/TSKAlarmController.ts",
    "chars": 2730,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport Alarm from '../Alarm'"
  },
  {
    "path": "src/mappers/Alarm.ts",
    "chars": 4087,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, CharacteristicSetCallback } from 'home"
  },
  {
    "path": "src/mappers/Awning/PositionableHorizontalAwningUno.ts",
    "chars": 472,
    "preview": "import Awning from '../Awning';\nexport default class PositionableHorizontalAwningUno extends Awning {\n    protected onSt"
  },
  {
    "path": "src/mappers/Awning.ts",
    "chars": 2323,
    "preview": "import RollerShutter from './RollerShutter';\nimport { Command } from 'overkiz-client';\n\nexport default class Awning exte"
  },
  {
    "path": "src/mappers/ConsumptionSensor.ts",
    "chars": 350,
    "preview": "import { Service } from 'homebridge';\nimport Mapper from '../Mapper';\n\nexport default class ConsumptionSensor extends Ma"
  },
  {
    "path": "src/mappers/ContactSensor.ts",
    "chars": 2132,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from"
  },
  {
    "path": "src/mappers/Curtain.ts",
    "chars": 100,
    "preview": "import RollerShutter from './RollerShutter';\n\nexport default class Curtain extends RollerShutter {\n}"
  },
  {
    "path": "src/mappers/DoorLock.ts",
    "chars": 2471,
    "preview": "\nimport { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command,"
  },
  {
    "path": "src/mappers/ElectricitySensor/CumulativeElectricPowerConsumptionSensor.ts",
    "chars": 1253,
    "preview": "import { Services } from '../../Platform';\nimport { Characteristic } from 'homebridge';\nimport { CurrentConsumptionChara"
  },
  {
    "path": "src/mappers/ElectricitySensor.ts",
    "chars": 441,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Service } from 'homebridge';\nimport Mapper from '../Ma"
  },
  {
    "path": "src/mappers/EvoHome/DHWSetPoint.ts",
    "chars": 164,
    "preview": "import HeatingSystem from '../HeatingSystem';\nimport TemperatureSensor from '../TemperatureSensor';\n\nexport default clas"
  },
  {
    "path": "src/mappers/EvoHome/EvoHomeController.ts",
    "chars": 890,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '."
  },
  {
    "path": "src/mappers/EvoHome/HeatingSetPoint.ts",
    "chars": 493,
    "preview": "import { Characteristics } from '../../Platform';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class He"
  },
  {
    "path": "src/mappers/ExteriorHeatingSystem/DimmerExteriorHeating.ts",
    "chars": 1460,
    "preview": "import { Characteristic, Service } from 'homebridge';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport {"
  },
  {
    "path": "src/mappers/ExteriorHeatingSystem.ts",
    "chars": 365,
    "preview": "import { Command } from 'overkiz-client';\nimport HeatingSystem from './HeatingSystem';\n\nexport default class ExteriorHea"
  },
  {
    "path": "src/mappers/ExteriorScreen.ts",
    "chars": 108,
    "preview": "import RollerShutter from './RollerShutter';\n\nexport default class ExteriorScreen extends RollerShutter {\n\n}"
  },
  {
    "path": "src/mappers/ExteriorVenetianBlind.ts",
    "chars": 119,
    "preview": "import VenetianBlind from './VenetianBlind';\n\nexport default class ExteriorVenetianBlind extends VenetianBlind {\n    \n}"
  },
  {
    "path": "src/mappers/GarageDoor.ts",
    "chars": 4237,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, "
  },
  {
    "path": "src/mappers/Gate.ts",
    "chars": 6701,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, Service } from 'homebridge';\nimport { "
  },
  {
    "path": "src/mappers/Generic/CyclicGeneric.ts",
    "chars": 99,
    "preview": "import GarageDoor from '../GarageDoor';\n\nexport default class CyclicGeneric extends GarageDoor {\n\n}"
  },
  {
    "path": "src/mappers/Generic/DimmerOnOff.ts",
    "chars": 82,
    "preview": "import Light from '../Light';\n\nexport default class DimmerOnOff extends Light {\n\n}"
  },
  {
    "path": "src/mappers/Generic/RTSGeneric.ts",
    "chars": 324,
    "preview": "import { Command } from 'overkiz-client';\nimport RollerShutter from '../RollerShutter';\n\nexport default class RTSGeneric"
  },
  {
    "path": "src/mappers/Generic/RTSGeneric4T.ts",
    "chars": 96,
    "preview": "import GarageDoor from '../GarageDoor';\n\nexport default class RTSGeneric extends GarageDoor {\n\n}"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticElectricalHeater.ts",
    "chars": 5710,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Perms } from 'homebridge';\nimport { Command } from 'overkiz-c"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint.ts",
    "chars": 4963,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '."
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticElectricalTowelDryer.ts",
    "chars": 5571,
    "preview": "import { Characteristics, Services } from '../../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Comman"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCBoiler.ts",
    "chars": 1009,
    "preview": "import { Command } from 'overkiz-client';\nimport HeatingSystem from '../HeatingSystem';\n\nexport default class AtlanticPa"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCHeatPump.ts",
    "chars": 3063,
    "preview": "import { Characteristic, Perms } from 'homebridge';\nimport { Characteristics } from '../../Platform';\nimport { Command }"
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCHeatingAndCoolingZone.ts",
    "chars": 6552,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '."
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCHeatingZone.ts",
    "chars": 6249,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '."
  },
  {
    "path": "src/mappers/HeatingSystem/AtlanticPassAPCZoneControl.ts",
    "chars": 3750,
    "preview": "import { Perms } from 'homebridge';\nimport { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-c"
  },
  {
    "path": "src/mappers/HeatingSystem/ProgrammableAndProtectableThermostatSetPoint.ts",
    "chars": 160,
    "preview": "import ThermostatSetPoint from './ThermostatSetPoint';\n\nexport default class ProgrammableAndProtectableThermostatSetPoin"
  },
  {
    "path": "src/mappers/HeatingSystem/SomfyHeatingTemperatureInterface.ts",
    "chars": 3926,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '."
  },
  {
    "path": "src/mappers/HeatingSystem/SomfyThermostat.ts",
    "chars": 5392,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command, ExecutionState } from 'overkiz-client';\nimport Heati"
  },
  {
    "path": "src/mappers/HeatingSystem/ThermostatSetPoint.ts",
    "chars": 1235,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '."
  },
  {
    "path": "src/mappers/HeatingSystem/ValveHeatingTemperatureInterface.ts",
    "chars": 3092,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '."
  },
  {
    "path": "src/mappers/HeatingSystem.ts",
    "chars": 7058,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, Service } from 'homebridge';\nimport { "
  },
  {
    "path": "src/mappers/HitachiHeatingSystem/HitachiAirToAirHeatPump.ts",
    "chars": 5190,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport HeatingSystem from '."
  },
  {
    "path": "src/mappers/HitachiHeatingSystem/HitachiAirToWaterHeatingZone.ts",
    "chars": 123,
    "preview": "import HeatingSystem from '../HeatingSystem';\n\nexport default class HitachiAirToWaterHeatingZone extends HeatingSystem {"
  },
  {
    "path": "src/mappers/HitachiHeatingSystem/HitachiAirToWaterMainComponent.ts",
    "chars": 153,
    "preview": "import HeatingSystem from '../HeatingSystem';\n\nexport default class HitachiAirToWaterMainComponent extends HeatingSystem"
  },
  {
    "path": "src/mappers/HitachiHeatingSystem/HitachiDHW.ts",
    "chars": 120,
    "preview": "import WaterHeatingSystem from '../WaterHeatingSystem';\n\nexport default class HitachiDHW extends WaterHeatingSystem {\n\n}"
  },
  {
    "path": "src/mappers/HumiditySensor/WaterDetectionSensor.ts",
    "chars": 590,
    "preview": "import { Characteristics } from '../../Platform';\nimport ContactSensor from '../ContactSensor';\n\nexport default class Wa"
  },
  {
    "path": "src/mappers/HumiditySensor.ts",
    "chars": 687,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from"
  },
  {
    "path": "src/mappers/Light.ts",
    "chars": 3474,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, CharacteristicSetCallback } from 'home"
  },
  {
    "path": "src/mappers/LightSensor.ts",
    "chars": 681,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from"
  },
  {
    "path": "src/mappers/OccupancySensor.ts",
    "chars": 1948,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from"
  },
  {
    "path": "src/mappers/OnOff.ts",
    "chars": 1285,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, CharacteristicSetCallback } from 'home"
  },
  {
    "path": "src/mappers/Pergola/BioclimaticPergola.ts",
    "chars": 649,
    "preview": "import { Command } from 'overkiz-client';\nimport Pergola from '../Pergola';\n\nexport default class BioclimaticPergola ext"
  },
  {
    "path": "src/mappers/Pergola/PergolaHorizontalAwningUno.ts",
    "chars": 930,
    "preview": "import Pergola from '../Pergola';\n\nexport default class PergolaHorizontalAwningUno extends Pergola {\n    protected onSta"
  },
  {
    "path": "src/mappers/Pergola.ts",
    "chars": 100,
    "preview": "import RollerShutter from './RollerShutter';\n\nexport default class Pergola extends RollerShutter {\n}"
  },
  {
    "path": "src/mappers/RainSensor.ts",
    "chars": 1764,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from"
  },
  {
    "path": "src/mappers/RemoteController.ts",
    "chars": 705,
    "preview": "import { Characteristic } from 'homebridge';\nimport Mapper from '../Mapper';\nimport { Characteristics, Services } from '"
  },
  {
    "path": "src/mappers/RollerShutter/PositionableRollerShutterUno.ts",
    "chars": 490,
    "preview": "import RollerShutter from '../RollerShutter';\nexport default class PositionableRollerShutterUno extends RollerShutter {\n"
  },
  {
    "path": "src/mappers/RollerShutter/PositionableRollerShutterWithLowSpeedManagement.ts",
    "chars": 1412,
    "preview": "import moment from 'moment';\nimport { Command } from 'overkiz-client';\nimport RollerShutter from '../RollerShutter';\nexp"
  },
  {
    "path": "src/mappers/RollerShutter.ts",
    "chars": 9635,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, Service } from 'homebridge';\nimport { "
  },
  {
    "path": "src/mappers/Screen.ts",
    "chars": 103,
    "preview": "import RollerShutter from './RollerShutter';\n\nexport default class Screen extends RollerShutter {\n   \n}"
  },
  {
    "path": "src/mappers/Shutter.ts",
    "chars": 101,
    "preview": "import RollerShutter from './RollerShutter';\n\nexport default class Shutter extends RollerShutter {\n\n}"
  },
  {
    "path": "src/mappers/Siren.ts",
    "chars": 1980,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic, CharacteristicSetCallback } from 'home"
  },
  {
    "path": "src/mappers/SmokeSensor.ts",
    "chars": 3317,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from"
  },
  {
    "path": "src/mappers/SwingingShutter.ts",
    "chars": 111,
    "preview": "import RollerShutter from './RollerShutter';\n\nexport default class SwingingShutter extends RollerShutter {\n  \n}"
  },
  {
    "path": "src/mappers/TemperatureSensor.ts",
    "chars": 725,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport Mapper from"
  },
  {
    "path": "src/mappers/VenetianBlind.ts",
    "chars": 6003,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, "
  },
  {
    "path": "src/mappers/VentilationSystem/DimplexVentilationInletOutlet.ts",
    "chars": 574,
    "preview": "\nimport { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport VentilationSystem fr"
  },
  {
    "path": "src/mappers/VentilationSystem.ts",
    "chars": 2455,
    "preview": "import { Characteristics, Services } from '../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, "
  },
  {
    "path": "src/mappers/WaterHeatingSystem/AtlanticPassAPCDHW.ts",
    "chars": 5365,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Command } from 'overkiz-client';\nimport WaterHeatingSystem fr"
  },
  {
    "path": "src/mappers/WaterHeatingSystem/DomesticHotWaterProduction/AtlanticDomesticHotWaterProductionV2_SPLIT_IOComponent.ts",
    "chars": 4572,
    "preview": "import { Service } from 'homebridge';\nimport { Command } from 'overkiz-client';\nimport { Characteristics } from '../../."
  },
  {
    "path": "src/mappers/WaterHeatingSystem/DomesticHotWaterProduction.ts",
    "chars": 8483,
    "preview": "import { Characteristics } from '../../Platform';\nimport { Characteristic } from 'homebridge';\nimport { Command, Executi"
  },
  {
    "path": "src/mappers/WaterHeatingSystem/DomesticHotWaterTank.ts",
    "chars": 664,
    "preview": "import { Service } from 'homebridge';\nimport { Command } from 'overkiz-client';\nimport WaterHeatingSystem from '../Water"
  },
  {
    "path": "src/mappers/WaterHeatingSystem.ts",
    "chars": 633,
    "preview": "import { Characteristics } from '../Platform';\nimport { Service } from 'homebridge';\nimport HeatingSystem from './Heatin"
  },
  {
    "path": "src/mappers/WaterSensor.ts",
    "chars": 802,
    "preview": "import { Characteristic } from 'homebridge';\nimport { Characteristics, Services } from '../Platform';\nimport Mapper from"
  },
  {
    "path": "src/mappers/Window.ts",
    "chars": 1006,
    "preview": "import RollerShutter from './RollerShutter';\nimport { Characteristics, Services } from '../Platform';\n\nexport default cl"
  },
  {
    "path": "src/mappers/WindowHandle.ts",
    "chars": 1196,
    "preview": "import { Characteristics } from '../Platform';\nimport ContactSensor from './ContactSensor';\n\nexport default class Window"
  },
  {
    "path": "src/settings.ts",
    "chars": 284,
    "preview": "\n/**\n * This is the name of the platform that users will use to register the plugin in the Homebridge config.json\n */\nex"
  },
  {
    "path": "tsconfig.json",
    "chars": 498,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2018\", // ~node10\n    \"module\": \"commonjs\",\n    \"lib\": [\n      \"es2015\",\n     "
  }
]

About this extraction

This page contains the full source code of the dubocr/homebridge-tahoma GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 102 files (224.9 KB), approximately 48.5k tokens, and a symbol index with 352 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!