develop_1.7.0 e7d4a3e87926 cached
104 files
366.1 KB
91.1k tokens
554 symbols
1 requests
Download .txt
Showing preview only (395K chars total). Download the full file or copy to clipboard to get everything.
Repository: 0x5e/homebridge-tuya-platform
Branch: develop_1.7.0
Commit: e7d4a3e87926
Files: 104
Total size: 366.1 KB

Directory structure:
gitextract_mundw2wv/

├── .eslintrc
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── login_issue.yml
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .npmignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── ADVANCED_OPTIONS.md
├── CHANGELOG.md
├── LICENSE
├── README.md
├── SUPPORTED_DEVICES.md
├── config.schema.json
├── jest.config.js
├── nodemon.json
├── package.json
├── src/
│   ├── accessory/
│   │   ├── AccessoryFactory.ts
│   │   ├── AirConditionerAccessory.ts
│   │   ├── AirPurifierAccessory.ts
│   │   ├── AirQualitySensorAccessory.ts
│   │   ├── BaseAccessory.ts
│   │   ├── CameraAccessory.ts
│   │   ├── CarbonDioxideSensorAccessory.ts
│   │   ├── CarbonMonoxideSensorAccessory.ts
│   │   ├── CatToiletAccessory.ts
│   │   ├── ContactSensorAccessory.ts
│   │   ├── DehumidifierAccessory.ts
│   │   ├── DiffuserAccessory.ts
│   │   ├── DimmerAccessory.ts
│   │   ├── DoorbellAccessory.ts
│   │   ├── ExtractionHoodAccessory.ts
│   │   ├── FanAccessory.ts
│   │   ├── GarageDoorAccessory.ts
│   │   ├── HeaterAccessory.ts
│   │   ├── HumanPresenceSensorAccessory.ts
│   │   ├── HumidifierAccessory.ts
│   │   ├── IRAirConditionerAccessory.ts
│   │   ├── IRControlHubAccessory.ts
│   │   ├── IRGenericAccessory.ts
│   │   ├── LeakSensorAccessory.ts
│   │   ├── LightAccessory.ts
│   │   ├── LightSensorAccessory.ts
│   │   ├── LockAccessory.ts
│   │   ├── MotionSensorAccessory.ts
│   │   ├── OutletAccessory.ts
│   │   ├── PetFeederAccessory.ts
│   │   ├── SaunaAccessory.ts
│   │   ├── SceneAccessory.ts
│   │   ├── SceneSwitchAccessory.ts
│   │   ├── SecuritySystemAccessory.ts
│   │   ├── SmokeSensorAccessory.ts
│   │   ├── SwitchAccessory.ts
│   │   ├── TemperatureHumiditySensorAccessory.ts
│   │   ├── ThermostatAccessory.ts
│   │   ├── ValveAccessory.ts
│   │   ├── VibrationSensorAccessory.ts
│   │   ├── WeatherStationAccessory.ts
│   │   ├── WhiteNoiseLightAccessory.ts
│   │   ├── WindowAccessory.ts
│   │   ├── WindowCoveringAccessory.ts
│   │   ├── WirelessSwitchAccessory.ts
│   │   └── characteristic/
│   │       ├── Active.ts
│   │       ├── AirQuality.ts
│   │       ├── CurrentRelativeHumidity.ts
│   │       ├── CurrentTemperature.ts
│   │       ├── EnergyUsage.ts
│   │       ├── Light.ts
│   │       ├── LockPhysicalControls.ts
│   │       ├── MotionDetected.ts
│   │       ├── Name.ts
│   │       ├── OccupancyDetected.ts
│   │       ├── On.ts
│   │       ├── ProgrammableSwitchEvent.ts
│   │       ├── RelativeHumidityDehumidifierThreshold.ts
│   │       ├── RotationSpeed.ts
│   │       ├── SecuritySystemState.ts
│   │       ├── SwingMode.ts
│   │       └── TemperatureDisplayUnits.ts
│   ├── config.ts
│   ├── core/
│   │   ├── TuyaOpenAPI.ts
│   │   └── TuyaOpenMQ.ts
│   ├── device/
│   │   ├── TuyaCustomDeviceManager.ts
│   │   ├── TuyaDevice.ts
│   │   ├── TuyaDeviceManager.ts
│   │   └── TuyaHomeDeviceManager.ts
│   ├── index.ts
│   ├── platform.ts
│   ├── settings.ts
│   └── util/
│       ├── FfmpegStreamingProcess.ts
│       ├── Logger.ts
│       ├── TuyaRecordingDelegate.ts
│       ├── TuyaStreamDelegate.ts
│       ├── color.ts
│       └── util.ts
├── test/
│   ├── FanAccessory.test.ts
│   ├── Light.test.ts
│   ├── custom.test.ts
│   ├── home.test.ts
│   └── util.ts
└── tsconfig.json

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

================================================
FILE: .eslintrc
================================================
{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended" // uses the recommended rules from the @typescript-eslint/eslint-plugin
  ],
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "ignorePatterns": [
    "dist"
  ],
  "rules": {
    "quotes": ["warn", "single"],
    "indent": ["warn", 2, { "SwitchCase": 1 }],
    "semi": ["off"],
    "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"], // use the provided Homebridge log method instead
    "no-non-null-assertion": ["off"],
    "comma-spacing": ["error"],
    "no-multi-spaces": ["warn", { "ignoreEOLComments": true }],
    "no-trailing-spaces": ["warn"],
    "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/semi": ["warn"],
    "@typescript-eslint/member-delimiter-style": ["warn"]
  }
}


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: ['0x5e']
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Create a report to help us improve
labels: ['bug']
body:
- type: checkboxes
  id: prerequisite
  attributes:
    label: Prerequisite
    description: Have you read the [Readme - FAQ](https://github.com/0x5e/homebridge-tuya-platform#faq) and [Readme - Troubleshooting](https://github.com/0x5e/homebridge-tuya-platform#troubleshooting) section?
    options:
      - label: Yes, I've read the readme completely.
        required: true
- type: checkboxes
  id: cache
  attributes:
    label: Cache
    description: Have you tried clean homebridge accessory cache and restart the service?
    options:
      - label: Yes, I've cleaned accessory cache and the issue still exists.
        required: true
- type: input
  id: version
  attributes:
    label: Version
    description: The version of this plugin you are using.
    placeholder: 1.7.0-beta.xx
  validations:
    required: true
- type: textarea
  id: devide-info
  attributes:
    label: Device Infomation JSON File
    description: If it's related to a device, please paste `TuyaDeviceInfo.{uid}.json` content here.
    render: json
- type: dropdown
  id: control-mode
  attributes:
    label: Device Control Mode
    description: If it's related to a device, please select the device control mode.
    options:
      - Standard Instruction
      - DP Instruction
- type: textarea
  id: logs
  attributes:
    label: Logs
    description: Please paste homebridge logs with debug mode on.
    render: shell
- type: textarea
  id: infos
  attributes:
    label: Other Infomations
    description: Also tell us, what did you expect to happen.
  validations:
    required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Homebridge Discord Community
    url: https://discord.gg/homebridge-432663330281226270
    about: 'Ask your questions in the #tuya channel'


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Suggest an idea for this project
labels: ['enhancement']
body:
  - type: textarea
    id: devide-info
    attributes:
      label: Device Infomation JSON File
      description: If it's related to a device, please paste `TuyaDeviceInfo.{uid}.json` content here.
      render: json
  - type: textarea
    id: infos
    attributes:
      label: Detail Informations
    validations:
      required: true


================================================
FILE: .github/ISSUE_TEMPLATE/login_issue.yml
================================================
name: Login issue
description: Failed to login Tuya Cloud
labels: ['login issue']
body:
  - type: checkboxes
    id: prerequisite
    attributes:
      label: Prerequisite
      description: Have you read the [Readme - FAQ](https://github.com/0x5e/homebridge-tuya-platform#faq) and [Readme - Troubleshooting](https://github.com/0x5e/homebridge-tuya-platform#troubleshooting) section?
      options:
        - label: 'Yes'
          required: true
  - type: checkboxes
    id: accounts
    attributes:
      label: Accounts
      description: Do you know Tuya IoT Platform and Tuya App are using different account?
      options:
        - label: 'Yes'
          required: true
  - type: input
    id: country-code
    attributes:
      label: Country Code
      description: The country code of your app account.
      placeholder: ex. 1
    validations:
      required: true
  - type: dropdown
    id: region-code
    attributes:
      label: Region Code
      description: The region code from app network diagnosis result.
      options:
        - AY (China)
        - AZ (West US)
        - EU (Central Europe)
        - IN (India)
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Logs
      description: Please post homebridge logs. Logs with debug mode on will be better.
      render: shell
    validations:
      required: true
  - type: textarea
    id: infos
    attributes:
      label: Other Infomations
      description: Any information might relate to this issue.
  

================================================
FILE: .github/workflows/build.yml
================================================
name: Build and Lint

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        node-version: [18.x, 20.x, 22.x]

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install dependencies
        run: npm ci

      - name: Lint the project
        run: npm run lint

      - name: Build the project
        run: npm run build
        env:
          CI: true


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

# ------------- 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

# Hide vscode stuff
.vscode/launch.json

# yarn v2

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

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

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

# gitHub actions
.github

# 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

# 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: ADVANCED_OPTIONS.md
================================================
# Advanced Options

**During the beta version, the options are unstable, may get changed during updates.**

The main function of `deviceOverrides` is to convert "non-standard schema" to "standard schema", making the device compatible with this plugin.

Before configuring, you may need to:
- Have basic programming skills in JavaScript (Only used in `onGet`/`onSet` handlers).
- Understand the concept of device schema (also known as Data Type): [Tuya IoT Development Platform > Cloud Development > Standard Instruction Set > Data Type](https://developer.tuya.com/en/docs/iot/datatypedescription?id=K9i5ql2jo7j1k)
- Read the documentation of your device product in [SUPPORTED_DEVICES.md](./SUPPORTED_DEVICES.md).
- Obtain device info json from `/path/to/persist/TuyaDeviceList.xxx.json` (the full path can be found from logs).
- Locate any "incorrect schema" in your device info json, and convert it to the "correct schema".


### Configuration

`options.deviceOverrides` is an **optional** array of device overriding config objects, which is used for converting "non-standard schema" to "standard schema", making the device compatible with this plugin. The structure of each element in the array is described as follows:

- `id` - **required**: Device ID, Product ID, Scene ID, or `global`.
- `category` - **optional**: Device category code. See [SUPPORTED_DEVICES.md](./SUPPORTED_DEVICES.md). Also you can use `hidden` to hide the device, product, or scene. **⚠️Overriding this property may lead to unexpected behaviors and exceptions, so please remove the accessory cache after making changes.**
- `unbridged` - **optional**: Unbridge accessories. Defaults to `false`.
- `adaptiveLighting` - **optional**: Adaptive Lighting. Defaults to `false`. Not all light device support this feature, please use it on demand.
- `schema` - **optional**: An array of schema overriding config objects, used for describing datapoint (DP). When your device has non-standard DP, you need to transform them manually with configuration. Each element in the schema array is described as follows:
  - `code` - **required**: DP code.
  - `newCode` - **optional**: New DP code.
  - `type` - **optional**: New DP type. One of `Boolean`, `Integer`, `Enum`, `String`, `Json`, or `Raw`.
  - `property` - **optional**: New DP property object. For `Integer` type, the object should contain `min`, `max`, `scale`, and `step`. For `Enum` type, the object should contain `range`. For more information, see `TuyaDeviceSchemaProperty` in [TuyaDevice.ts](./src/device/TuyaDevice.ts).
  - `onGet` - **optional**: A one-line JavaScript code to convert the old value to the new value. The function is called with two arguments: `device` and `value`.
  - `onSet` - **optional**: A one-line JavaScript code to convert the new value to the old value. The function is called with two arguments: `device` and `value`. Returning `undefined` means to skip sending the command.
  - `hidden` - **optional**: Hide the schema. Defaults to `false`.


## Examples

### Change category code

```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "category": "xxx"
    }]
  }
}
```

### Hide device / scene

Just the same way as changing category code.

```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id_or_scene_id}",
      "category": "hidden"
    }]
  }
}
```

### Hide DP

An example of hide camera's floodlight (`floodlight_switch`):
```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "schema": [{
        "code": "floodlight_switch",
        "hidden": true
      }]
    }]
  }
}
```

### Enable Adaptive Lighting

```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "adaptiveLighting": true
    }]
  }
}
```


### Offline as off

If you want to display off status when device is offline:
```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "schema": [{
        "code": "{dp_code}",
        "onGet": "(device.online && value)"
      }]
    }]
  }
}
```


### Change DP code

```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "schema": [{
          "code": "{old_dp_code}",
          "newCode": "{new_dp_code}"
      }]
    }]
  }
}
```


### Convert from enum DP to boolean DP

An example of convert `open`/`close` into `true`/`false`:
```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "schema": [{
        "code": "{dp_code}",
        "type": "Boolean",
        "onGet": "(value === 'open') ? true : false;",
        "onSet": "(value === true) ? 'open' : 'close';"
      }]
    }]
  }
}
```


### Adjust integer DP ranges

Some odd thermostat stores double of the real value to keep the decimal part (0.5°C).

We need override both range and value in order to make it working. (Only override value is not enough, range is required too.)

Here's an example of the invalid schema:
```js
{
  code: 'temp_set',
  mode: 'rw',
  type: 'Integer',
  property: { unit: '℃', min: 10, max: 70, scale: 1, step: 5 }
}
```

The value `41` actually represents for `20.5°C`, the range `10~70` actually represents for `5.0°C~35.0°C`.

To fix this, first we need set scale to `1`, and convert `41` to `205` when getting, convert `205` to `41` when getting, which means `value x 5` when getting, and `value / 5` when setting.

Here's the example config:
```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "schema": [{
        "code": "temp_set",
        "onGet": "(value * 5);",
        "onSet": "(value / 5);",
        "property": {
          "min": 50,
          "max": 350,
          "scale": 1,
          "step": 5
        }
      }]
    }]
  }
}
```

After transform value using `onGet` and `onSet`, and new range in `property`, it should be working now.


### Reverse curtain motor's on/off state

Most curtain motor have "reverse mode" setting in the Tuya App, if you don't have this, you can reverse `percent_control`/`position` and `percent_state` in the plugin config:

```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "schema": [{
        "code": "percent_control",
        "onGet": "(100 - value)",
        "onSet": "(100 - value)"
      }, {
        "code": "percent_state",
        "onGet": "(100 - value)",
        "onSet": "(100 - value)"
      }]
    }]
  }
}
```


### Skip send on/off command when touching brightness/speed slider

Some products (dimmer, fan) having issue when sending brightness/speed command with on/off command together. Here's an example of skip on/off command.

```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "schema": [{
        "code": "switch_led",
        "onSet": "(value === device.status.find(status => status.code === 'switch_led').value) ? undefined : value"
      }]
    }]
  }
}
```


### Convert Fahrenheit to Celsius

F = 1.8 * C + 32

C = (F - 32) / 1.8

```js
{
  "options": {
    // ...
    "deviceOverrides": [{
      "id": "{device_id}",
      "schema": [{
        "code": "temp_current",
        "onGet": "Math.round((value - 32) / 1.8);",
        "onSet": "Math.round(1.8 * value + 32);"
      }]
    }]
  }
}
```


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## [1.7.0] - (unreleased)

### Added
- Add scene support. (#118)
- Add Wireless Switch support (`wxkg`).
- Add Solar Light support (`tyndj`).
- Add Dehumidifier support (`cs`).
- Add Scene Switch support (`wxkg`).
- Add device overriding config support. "Non-standard DP" devices have possibility to be supported now. (#119)
- Add Camera support (`sp`). Thanks @ErrorErrorError for the contribution
- Add Air Conditioner support (`kt`). (#160)
- Add Air Conditioner Controller support (`ktkzq`). (#160)
- Add Diffuser support (`xxj`). (#175)
- Add Temperature Control Socket support (`wkcz`).
- Add Environmental Detector support (`hjjcy`).
- Add Water Valve Controller support (`sfkzq`).
- Add IR Remote Control support (`infrared_tv`, `infrared_stb`, `infrared_box`, `infrared_ac`, `infrared_fan`, `infrared_light`, `infrared_amplifier`, `infrared_projector`, `infrared_waterheater`, `infrared_airpurifier`). (#191)
- Add IR AC Controller support (`hwktwkq`).
- Add Fingerbot support (`szjqr`).
- Add Smart Lock support (`ms`, `jtmspro`). (#120) Thanks @pfgimutao for the contribution
- Add Alarm Host support (`mal`). (#246) Thanks @bFollon for the contribution
- Add Vibration Sensor support (`zd`). (#262)
- Add adaptive lighting support. (#272)
- Add Wireless Doorbell support (`wxml`). (#277)
- Add IR Remote Control support (`wsdykq`).
- Add Layout to display schema in sections. (#283) Thanks @donavanbecker for the contribution
- Add option to make accessory and unbridged accessory (#285) Thanks @donavanbecker for the contribution
- Add inching button for switches.
- Add support to 2ch windows covering. (#339) Thanks @CryptoIR for the contribution
- Add retry when network error happened.
- Add Pet Feeder support (`cwwsq`). (#483) Thanks @aselekoglu for the contribution


### Fixed
- Fix `RotationSpeed` missing one level. (#170)
- Fix `bright_value` not sent for the `C/CW` lights who doesn't have `work_mode`. (#171)
- Fix crash when camera sends an invalid status message.
- Fix incorrect Door and Window Controller state. (#178)
- Fix Thermostat cold mode not working (#242).
- Order temp before get the min and max for IRAirConditionerAccessory. (#433) Thanks @tuliocll for the contribution
- Fix energy usage not updated after homebridge restart. (#268)


### Changed
- Support Ceiling Fan icon customize and Floor Fan `lock`, `swing` feature. (#131)
- Adjust humidity range of dehumidifier and humidifier.
- Print scene id in logs.
- Update support for RGB Power Switch (`dj`).
- Support showing device online status via `StatusActive`. (#172)
- Update unit and range of `RotationSpeed`, need clean accessory cache to take effect. (#174, #273)
- Support Diffuser RGB light. (#184)
- Support Fan light temperature and color. (#184)
- Support Humidifier light. (#184)
- Expose energy usage for outlets/switches. (#190) Thanks @lstrojny for the contribution
- Strict config validate for `deviceOverrides`. (#278)
- Support AirPurifier air quality.
- Throw `HapStatusError` when device is offline.


## [1.6.0] - (2022.12.3)

This version has been completely rewritten in TypeScript, brings a lot of bug fix and new device support.

### New Accessories
- Add CO Detector support (`cobj`).
- Add CO2 Detector support (`co2bj`).
- Add Water Detector support (`sj`).
- Add Temperature and Humidity Sensor support (`wsdcg`, `wnykq`). Thanks @bimusiek for the contribution
- Add Light Sensor support (`ldcg`).
- Add Motion Sensor support (`pir`).
- Add PM2.5 Detector support (`pm25`).
- Add Door and Window Controller support (`mc`).
- Add Curtain Switch support (`clkg`). (#8)
- Add Human Presence Sensor support (`hps`). (#17)
- Add Thermostat support (`wk`). (#19) Thanks @burcadoruciprian for the contribution
- Add Spotlight support (`sxd`). (#21)
- Add Irrigator support (`ggq`). (#28)
- Add Scene Light Socket support (`qjdcz`). (#33)
- Add Ceiling Fan Light support (`fsd`). (#37)
- Add Thermostat Valve support (`wkf`). (#50)
- Add Motion Sensor Light support (`gyd`). (#65)
- Add Multiple Dimmer and Dimmer Switch support (`tgq`, `tgkg`). (#82)
- Add Humidifier support (`jsq`). (#89) Thanks @akaminsky-net for the contribution


### Added
- Add config validation during plugin initialization.
- Add instruction message for handling API errors.
- Add debounce in `BaseAccessory.sendCommands()` for better API request peformance.
- Persist `TuyaDeviceList.{uid}.json` for debugging. (#41)
- Add `homeWhitelist` option for whitelisting homes. (#84) Thanks @JulianLepinski for the contribution


### Fixed
- Fix 1004 signature error when url query has more than 2 elements.
- Fix 1010 token expired error when refresh access_token.
- Fix 1106 permission error when polling device info list.
- Fix 1100, 2017 errors when login. (via config validation)
- Fix Lightbulb `RGBW` and `RGBCW` work mode not switched properly (#12 #56 #59)
- Fix Lightbulb color temperature not working. (#13)
- Fix Thermostat temperature units handling. (#20)
- Fix Thermostat mode handling. (#26)
- Fix Curtain Switch with no position feature. (#27)
- Fallback when receiving MQTT message with wrong order. (#35)
- Fix wrong temperature on sensor. (#38)
- Fix fan speed issue. (#46 #51)
- Workaround for Thermostat with wrong schema property (#74)
- Fix Contact Sensor not working (#75)
- Fix iOS 16 default accessory name issue. (#85)


### Changed
- Rewritten in TypeScript, brings benefits of type checking, smart code hints, etc.
- Reimplement accessory logics. More friendly for accessory developers.
- Update device info list polling logic. Less API errors.
- Now `Manufactor`, `Serial Number` and `Model` will be correctly displayed in HomeKit.
- All devices will be shown in HomeKit by default (Including unsupported device).
- Updated unit test.
- Updated documentations. Thanks @prabch for the contribution


### Removed
- Remove `debug` option. Silence logs for users. For debugging, please refer to [troubleshooting](https://github.com/0x5e/homebridge-tuya-platform#troubleshooting).
- Remove `lang` option.
- Remove `username` and `password` options for `Custom` project. User will be created and authorized automatically. (#11)


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014-2021 Tuya Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
# @0x5e/homebridge-tuya-platform

[![npm](https://badgen.net/npm/v/@0x5e/homebridge-tuya-platform)](https://npmjs.com/package/@0x5e/homebridge-tuya-platform)
[![npm](https://badgen.net/npm/dt/@0x5e/homebridge-tuya-platform)](https://npmjs.com/package/@0x5e/homebridge-tuya-platform)
[![mit-license](https://badgen.net/npm/license/@0x5e/homebridge-tuya-platform)](https://github.com/0x5e/homebridge-tuya-platform/blob/main/LICENSE)
[![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
[![Build and Lint](https://github.com/0x5e/homebridge-tuya-platform/actions/workflows/build.yml/badge.svg)](https://github.com/0x5e/homebridge-tuya-platform/actions/workflows/build.yml)
[![join-discord](https://badgen.net/badge/icon/discord?icon=discord&label=homebridge/tuya)](https://discord.gg/homebridge-432663330281226270)


Fork version of the official Tuya Homebridge plugin, with a focus on fixing bugs and adding new device support.



⚠️**Update on 2024.1.14:** Thanks for the attention on this project. There's more and more "problem device", which has wrong definition by manufacture (reversed 0%-100% state, wrong range, wrong unit, ...). Support them one by one really cost a lot. I'm not going to support them in the future, please try solve them by yourself. PRs are still welcome, and bugs will be focused. Thanks again :)




## Features

- Optimized and improved code for better readability and maintainability.
- Enhanced stability.
- Reduced duplicate code.
- Fewer API errors.
- Lower development costs for new accessory categories.
- Supports Tuya Scenes (Tap-to-Run).
- Includes the ability to override device configurations, which enables support for "non-standard" DPs.
- Supports over 60+ device categories, including most light, switch, sensor, camera, lock, IR remote control, etc.


## Supported Tuya Devices
See [SUPPORTED_DEVICES.md](./SUPPORTED_DEVICES.md)


## Changelogs
See [CHANGELOG.md](./CHANGELOG.md)


## Installation
Before using this plugin, please make sure to uninstall `homebridge-tuya-platform` first as these two plugins cannot run simultaneously. However, the configuration files are compatible, so there's no need to delete them.

#### For Homebridge Web UI Users
Go to plugin page, search for `@0x5e/homebridge-tuya-platform` and install it.


#### For Homebridge Command Line Users

Run the following command in the terminal:
```
npm install @0x5e/homebridge-tuya-platform
```


## Configuration

There are two types of projects: `Custom` and `Smart Home`.
The difference between them is:
- The `Custom` project pulls devices from the project's assets.
- The `Smart Home` project pulls devices from the user's home in the Tuya app.

If you are a personal user and are unsure which one to choose, please use the `Smart Home` project.

Before you can configure, you must go to the [Tuya IoT Platform](https://iot.tuya.com):
- Create a cloud development project, and select the data center where your app account is located. See [Mappings Between OEM App Accounts and Data Centers](https://developer.tuya.com/en/docs/iot/oem-app-data-center-distributed?id=Kafi0ku9l07qb)
- Go to the `Project Page` > `Devices Panel` > `Link Tuya App Account`, and link your app account.
- Go to the `Project Page` > `Service API` > `Go to Authorize`, and subscribe to the following APIs (it is free for trial):
    - Authorization Token Management
    - Device Status Notification
    - IoT Core
    - IoT Video Live Stream (for cameras)
    - Industry Project Client Service (for the `Custom` project)
    - IR Control Hub Open Service (for IR devices)
    - Smart Home Scene Linkage (for scenes)
    - Smart Lock Open Service (for Lock devices)
- **⚠️Remember to extend the API trial period every 6 months here [Tuya IoT Platform > Cloud > Cloud Services > IoT Core](https://iot.tuya.com/cloud/products/detail?abilityId=1442730014117204014&id=p1668587814138nv4h3n&abilityAuth=0&tab=1) (the first-time subscription only gives you 1 month).**

#### For "Custom" Project

- `platform` - **required** : Must be 'TuyaPlatform'.
- `options.projectType` - **required** : Must be '1'
- `options.endpoint` - **required** : The endpoint URL taken from the [API Reference > Endpoints](https://developer.tuya.com/en/docs/iot/api-request?id=Ka4a8uuo1j4t4#title-1-Endpoints) table.
- `options.accessId` - **required** : The Access ID obtained from [Tuya IoT Platform > Cloud Develop](https://iot.tuya.com/cloud).
- `options.accessKey` - **required** : The Access Secret obtained from [Tuya IoT Platform > Cloud Develop](https://iot.tuya.com/cloud).
- `options.debug` - **optional**: Includes debugging output in the Homebridge log. (Default: `false`)
- `options.debugLevel` - **optional**: An optional list of strings seperated with comma `,`. `api` represents for HTTP API log, `mqtt` represents for MQTT log, and device ID represents for device log. If blank, all logs are outputed.

#### For "Smart Home" Project

- `platform` - **required** : Must be 'TuyaPlatform'.
- `options.projectType` - **required** : Must be '2'.
- `options.accessId` - **required** : The Access ID obtained from [Tuya IoT Platform > Cloud Develop](https://iot.tuya.com/cloud).
- `options.accessKey` - **required** : The Access Secret obtained from [Tuya IoT Platform > Cloud Develop](https://iot.tuya.com/cloud).
- `options.countryCode` - **required** : The numeric country code of your developer account's region. You can find the country codes list [here](https://developer.tuya.com/en/docs/iot/oem-app-data-center-distributed?id=Kafi0ku9l07qb).
- `options.username` - **required** : The mobile app (Tuya or SmartLife) account's username. Don't use the Tuya IoT Platform developer username.
- `options.password` - **required** : The mobile app (Tuya or SmartLife) account's password. MD5 salted password is also available for increased security. Don't use the Tuya IoT Platform developer password.
- `options.appSchema` - **required** : The app schema: 'tuyaSmart' for the Tuya Smart App, or 'smartlife' for the Smart Life App.
- `options.endpoint` - **optional** : The endpoint URL can be inferred from the [API Reference > Endpoints](https://developer.tuya.com/en/docs/iot/api-request?id=Ka4a8uuo1j4t4#title-1-Endpoints) table based on the country code provided. Only manually set this value if you encounter login issues and need to specify the endpoint for your account location.
- `options.homeWhitelist` - **optional**: An array of integer values for the home IDs you want to whitelist. If provided, only devices with matching Home IDs will be included. You can find the Home ID in the Homebridge log.
- `options.debug` - **optional**: Includes debugging output in the Homebridge log. (Default: `false`)
- `options.debugLevel` - **optional**: An optional list of strings seperated with comma `,`. `api` represents for API and MQTT log, device ID represents for specific device log. If blank, all logs are outputed.


#### Advanced options
See [ADVANCED_OPTIONS.md](./ADVANCED_OPTIONS.md)


## Limitations
- **⚠️Don't forget to extend the API trial period every 6 months. Maybe you can set up a reminder in calendar.**
- Using the same app account for multiple Homebridge/HomeAssistant instances is not supported. Please use separate app accounts for each instance.
- The plugin requires an internet connection to the Tuya Cloud and does not support the LAN protocol. See [#90](https://github.com/0x5e/homebridge-tuya-platform/issues/90) for more information.

## FAQ

#### About Login issue

For most users, you can easily find your app account's data center through the [documentation](https://developer.tuya.com/en/docs/iot/oem-app-data-center-distributed?id=Kafi0ku9l07qb) and login without any issues. However, for some users, they may encounter error codes such as 1106 or 2406. If you encounter such errors, it's possible that there are differences between your data center and the documentation.

To determine the data center, follow these steps:

1. Open the app and navigate to "Me > Settings > Network Diagnosis".
2. Start the diagnosis and select "Upload Log > Copy the Log to Clipboard".
3. Paste the log anywhere and find the line beginning with "Region code:".
4. Look for the following codes: "AY" for China, "AZ" for the West US, "EU" for Central Europe, and "IN" for India.

Then manually specify endpoint in the plugin config.


#### What is "Standard DP" and "Non-standard DP"?

<!-- If your device is working properly, you don't need to know this. -->

"Standard DP" refers to device properties or functionalities that are specified in the Tuya IoT Development Platform documentation at [Tuya IoT Development Platform Documentation > Cloud Development > Standard Instruction Set](https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq).

For example, a light bulb should have a standard DP code of `switch_led` for power on/off, and optional codes `bright_value`/`bright_value_v2` for brightness, `temp_value`/`temp_value_v2` for color temperature, and `work_mode` for changing the working mode. These codes can be found in the above documentation.

If your light bulb can be adjusted in the Tuya app but not with the plugin, it most likely has "Non-standard DP."


#### Can "Non-standard DP" be supportd by this plugin?

Yes. The device must be listed in the support list and the following steps must be completed before it will work:
1. Change the device's control mode on the Tuya Platform:
  - Go to "[Tuya Platform Cloud Development](https://iot.tuya.com/cloud/) > Your Project > Devices > All Devices > View Devices by Product".
  - Find the product related to your device, click the "pencil" icon (Change Control Instruction Mode).
  - <img width="500" alt="image" src="https://user-images.githubusercontent.com/5144674/202967707-8b934e05-36d6-4b42-bb7b-87e5b24474c4.png">
  - In the "Table of Instructions", you can see the cloud mapping and determine which DP codes are missing and need to be manually mapped later.
  - <img width="500" alt="image" src="https://user-images.githubusercontent.com/5144674/202967528-4838f9a1-0547-4102-afbb-180dc9b198b1.png">
  - Select "DP Instruction" and save.
2. Override the device schema, see [ADVANCED_OPTIONS.md](./ADVANCED_OPTIONS.md).


#### Local support
See [#90](https://github.com/0x5e/homebridge-tuya-platform/issues/90).

Although the plugin didn't implemented tuya local protocol now, it still remains possibility in the future.


## Troubleshooting

If your device is not supported, please follow these steps to collect information.

#### 1. Get Device Information

After Homebridge has been successfully launched, the device information list will be saved in Homebridge's persist path. You can find the file path in the Homebridge log:
```
[2022/11/3 18:37:43] [TuyaPlatform] Device list saved at /path/to/TuyaDeviceList.{uid}.json
```

**⚠️Please make sure to remove sensitive information such as `ip`, `lon`, `lat`, `local_key`, and `uid` before submitting the file.**


#### 2. Enable Debug Mode

Add debug option in the plugin config, then restart Homebridge.

#### 3. Collect Logs

With debug mode enabled, you can now receive MQTT logs. Operate your device, either physically or through the Tuya App, to receive MQTT logs like this:

```
[2022/12/8 12:51:59] [TuyaPlatform] [TuyaOpenMQ] onMessage:
topic = cloud/token/in/xxx
protocol = 4
message = {
  "dataId": "xxx",
  "devId": "xxx",
  "productKey": "xxx",
  "status": [
    {
      "1": "double_click",
      "code": "switch1_value",
      "t": "1670475119766",
      "value": "double_click"
    }
  ]
}
```

If you are unable to receive any MQTT logs while controlling the device, it likely means that your device has "Non-standard DP".

By submitting the device information JSON and MQTT logs, you can help us support new device categories.


## Contributing

Please see https://github.com/homebridge/homebridge-plugin-template#setup-development-environment for setup development environment.

PRs and issues are welcome.

# 
Thank you for spend time using the project. If it helps you, don't hesitate to give it a star 🌟:-)


================================================
FILE: SUPPORTED_DEVICES.md
================================================
# Supported Tuya Devices

First-class category name, sedond-class category name, category code can be found here:
https://developer.tuya.com/docs/iot/standarddescription?id=K9i5ql6waswzq

Most category code is pinyin abbreviation of Chinese name.

## Lighting

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Light | 光源 | dj<br> dsd | Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorydj?id=Kaiuyzy3eheyy) |
| Ceiling Light | 吸顶灯 | xdd | Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/ceiling-light?id=Kaiuz03xxfc4r) |
| Ambiance Light | 氛围灯 | fwd | Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/ambient-light?id=Kaiuz06amhe6g) |
| String Lights | 灯串 | dc | Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/dc?id=Kaof7taxmvadu) |
| Strip Lights | 灯带 | dd | Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/dd?id=Kaof804aibg2l) |
| Motion Sensor Light | 感应灯 | gyd | Lightbulb<br> MotionSensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/gyd?id=Kaof8a8hycfmy) |
| Ceiling Fan Light | 风扇灯 | fsd | Lightbulb<br> Fanv2 | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/fsd?id=Kaof8eiei4c2v) |
| Solar Light | 太阳能灯 | tyndj | Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/tynd?id=Kaof8j02e1t98) |
| Dimmer | 调光器 | tgq | Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/tgq?id=Kaof8ke9il4k4) |
| Remote Control | 遥控器 | ykq | | | [Documentation](https://developer.tuya.com/en/docs/iot/ykq?id=Kaof8ljn81aov) |
| Spotlight | 射灯 | sxd | Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/sxd?id=Kb7jayalltstu) |
| White Noise Light | 白噪音灯 | bzyd | Lightbulb<br> Switch | ✅ | Documentation |


## Electrical Products

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Switch | 开关 | kg<br> tdq | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorykgczpc?id=Kaiuz08zj1l4y) |
| Socket | 插座 | cz | Outlet | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorykgczpc?id=Kaiuz08zj1l4y) |
| Power Strip | 排插 | pc | Outlet | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorykgczpc?id=Kaiuz08zj1l4y) |
| Scene Switch | 场景开关 | cjkg | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorycjkg?id=Kaiuz0bcukqc5) |
| Card Switch | 插卡取电开关 | ckqdkg | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryckqdkg?id=Kaiuz0e3wjryy) |
| Curtain Switch | 窗帘开关 | clkg | Window Covering | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/category-clkg?id=Kaiuz0gitil39) |
| Garage Door Opener | 车库门控制器 | ckmkzq | Garage Door Opener | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryckmkzq?id=Kaiuz0ipcboee) |
| Dimmer Switch | 调光开关 | tgkg | Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorytgkg?id=Kaiuz0ktx7m0o) |
| Fan Switch | 风扇开关 | fskg | Fanv2 | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryfskg?id=Kbcs129cl1gr9) |
| Wireless Switch | 无线开关 | wxkg | Stateless Programmable Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/wxkg?id=Kbeo9t3ryuqm5) |
| Scene Light Socket | 情景灯插座 | qjdcz | Switch | ✅ | Documentation |
| Temperature Control Socket | 温控插座 | wkcz | Switch<br> Temperature Sensor<br> Humidity Sensor | ✅ | Documentation |


## Large Home Appliances

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Heater | 热水器 | rs | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryrs?id=Kaiuz0nfferyx) |
| Ventilation System | 新风机 | xfj | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryxfj?id=Kaiuz0pphkowg) |
| Refrigerator | 冰箱 | bx | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorybx?id=Kaiuz0s58ia6h) |
| Bathtub | 浴缸 | yg | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryyg?id=Kaiuz0uoisp47) |
| Washing Machine | 洗衣机 | xy | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryxy?id=Kaiuz0wxh08jf) |
| Air Conditioner | 空调 | kt | Heater Cooler<br> Humidifier Dehumidifier<br> Fanv2<br> Temperature Sensor<br> Humidity Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorykt?id=Kaiuz0z71ov2n) |
| Air Conditioner Controller | 空调控制器 | ktkzq | Heater Cooler<br> Humidifier Dehumidifier<br> Fanv2<br> Temperature Sensor<br> Humidity Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryktkzq?id=Kaiuz11eqy892) |
| Boiler | 壁挂炉 | bgl | | | [Documentation](https://developer.tuya.com/en/docs/iot/boilerbgl?id=Kaiuz13shgrhp) |
| Sauna | 华氏度摄氏度两用(30-90) | qtwk | Lightbulb<br>Thermostat | ✅ | Documentation |


## Small Home Appliances

| Name                       | Name (zh) | Code          | Homebridge Service | Supported | Links                                                                                    |
|----------------------------| ---- |---------------| ---- | ---- |------------------------------------------------------------------------------------------|
| Robot Vacuum               | 扫地机 | sd            | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorysd?id=Kaiuz16b2s6yd)      |
| Heater                     | 取暖器 | qn            | Heater Cooler | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryqn?id=Kaiuz18kih0sm)      |
| Air Purifier               | 空气净化器 | kj            | Air Purifier | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorykj?id=Kaiuz1atqo5l7)      |
| Drying Rack                | 晾衣架 | lyj           | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorylyj?id=Kaiuz1cy926vh)     |
| Diffuser                   | 香薰机 | xxj           | Air Purifier<br> Lightbulb | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryxxj?id=Kaiuz1f9mo6bl)     |
| Extraction hood            | 香薰机 | yyj           | Air Purifier<br> Lightbulb | ✅ | Documentation        |
| Curtain                    | 窗帘 | cl            | Window Covering | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorycl?id=Kaiuz1hnpo7df)      |
| Door and Window Controller | 门窗控制器 | mc            | Window | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorymc?id=Kaiuz1jyoassg)      |
| Thermostat                 | 温控器 | wk            | Thermostat | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorywk?id=Kaiuz1m1xqnt6)      |
| Thermostat Valve           | 温控阀 | wkf           | Thermostat | ✅ | Documentation                                                                            |
| Bathroom Heater            | 浴霸 | yb            | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryyb?id=Kaiuz1oajgpib)      |
| Irrigator                  | 灌溉器 | ggq<br> sfkzq | Valve | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryggq?id=Kaiuz1qib7z0k)     |
| Humidifier                 | 加湿器 | jsq           | Humidifier Dehumidifier | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryjsq?id=Kaiuz1smr440b)     |
| Dehumidifier               | 除湿机 | cs            | Humidifier Dehumidifier | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorycs?id=Kaiuz1vcz4dha)      |
| Fan                        | 风扇 | fs            | Fanv2 | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryfs?id=Kaiuz1xweel1c)      |
| Water Purifier             | 净水器 | js            | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryjs?id=Kaiuz204l58n9)      |
| Electric Blanket           | 电热毯 | dr            | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorydr?id=Kaiuz22dyc66p)      |
| Pet Treat Feeder           | 宠物弹射喂食器 | cwtswsq       | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorycwtswsq?id=Kaiuz24lq3fq5) |
| Pet Ball Thrower           | 宠物网球发射器 | cwwqfsq       | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorycwwqfsq?id=Kaiuz26r7g1up) |
| HVAC                       | 暖通器 | ntq           | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryntq?id=Kaiuz292sjqcz)     |
| Pet Feeder                 | 宠物喂食器 | cwwsq         | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorycwwsq?id=Kaiuz2b6vydld)   |
| Pet Fountain               | 宠物饮水机 | cwysj         | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorycwysj?id=Kaiuz2dfro0nd)   |
| Sofa                       | 沙发 | sf            | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorysf?id=Kaiuz2fp9uqtt)      |
| Electric Fireplace         | 电壁炉 | dbl           | | | [Documentation](https://developer.tuya.com/en/docs/iot/electric-fireplace?id=Kaiuz2hz4iyp6) |
| Smart Milk Kettle          | 智能调奶器 | tnq           | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorytnq?id=Kakf01agbfkfa)     |
| Cat Toilet                 | 猫砂盆 | msp           | Switch, Lightbulb, OccupancySensor, FilterMaintenance | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorymsp?id=Kakg2t7714ky7)     |
| Towel Rack                 | 毛巾架 | mjj           | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorymjj?id=Kakkmlm9k4cir)     |
| Smart Indoor Garden        | 植物生长机 | sz            | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorysz?id=Kaiuz4e6h7up0)      |


## Kitchen Appliances

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Smart Kettle | 电茶壶 | bh | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorybh?id=Kaiuz2kly679h) |
| Bread Maker | 面包机 | mb | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorymb?id=Kaiuz2mrs0b2m) |
| Coffee Maker | 咖啡机 | kfj | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorykfj?id=Kaiuz2p12pc7f) |
| Bottle Warmer | 暖奶器 | nnq | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorynnq?id=Kaiuz2riz1s8d) |
| Milk Dispenser | 冲奶机 | cn | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorycn?id=Kaiuz2tosvw2a) |
| Sous Vide Cooker | 慢煮机 | mzj | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorymzj?id=Kaiuz2vy130ux) |
| Rice Cabinet | 米柜 | mg | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorymg?id=Kaiuz2yb04ocu) |
| Induction Cooker | 电磁炉 | dcl | | | [Documentation](https://developer.tuya.com/en/docs/iot/induction-cooker?id=Kaiuz30l7adxo) |
| Air Fryer | 空气炸锅 | kqzg | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorykqzg?id=Kakda4kug3k1j) |
| Bento Box | 智能饭盒 | znfh | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryznfh?id=Kako8jffneds3) |


## Security & Video Surveillance

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Alarm Host | 报警主机 | mal | Security System | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorymal?id=Kaiuz33clqxaf) |
| Smart Camera | 智能摄像机 | sp | Motion Sensor<br> Doorbell | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorysp?id=Kaiuz35leyo12) |
| Wireless Doorbell | 无线门铃 | wxml | StatelessProgrammableSwitch | ✅ | Documentation |
| Siren Alarm | 声光报警传感器 | sgbj | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorysgbj?id=Kaiuz37tlpbnu) |
| Gas Alarm | 燃气报警传感器 | rqbj | Leak Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryrqbj?id=Kaiuz3d162ubw) |
| Smoke Alarm | 烟雾报警传感器 | ywbj | Smoke Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryywbj?id=Kaiuz3f6sf952) |
| Temperature and Humidity Sensor | 温湿度传感器 | wsdcg | Temperature Sensor<br> Humidity Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorywsdcg?id=Kaiuz3hinij34) |
| Contact Sensor | 门磁传感器 | mcs | Contact Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorymcs?id=Kaiuz3bnflmh2) |
| Vibration Sensor | 震动传感器 | zd | Motion Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryzd?id=Kaiuz3a5vrzno) |
| Water Detector | 水浸传感器 | sj | Leak Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorysj?id=Kaiuz3iub2sli) |
| Luminance Sensor | 亮度传感器 | ldcg | Light Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryldcg?id=Kaiuz3n7u69l8) |
| Pressure Sensor | 压力传感器 | ylcg<br> ylcgq | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryylcg?id=Kaiuz3kc2e4gm) |
| Emergency Button | 紧急按钮 | sos | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorysos?id=Kaiuz3oi6agjy) |
| PM2.5 Detector | PM2.5传感器 | pm25<br> pm2.5<br> pm25cgq | Air Quality Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorypm25?id=Kaiuz3qof3yfu) |
| CO Detector | CO报警传感器 | cobj<br> cocgq | Carbon Monoxide Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorycobj?id=Kaiuz3u1j6q1v) |
| CO2 Detector | CO2报警传感器 | co2bj<br> co2cgq | Carbon Dioxide Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryco2bj?id=Kaiuz3wes7yuy) |
| Multi-functional Sensor | 多功能传感器 | dgnbj | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorydgnbj?id=Kaiuz3yorvzg3) |
| Methane Detector | 甲烷报警传感器 | jwbj | Leak Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryjwbj?id=Kaiuz40u98lkm) |
| Human Motion Sensor | 人体运动传感器 | pir | Motion Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorypir?id=Kaiuz3ss11b80) |
| Human Presence Sensor | 人体存在传感器 | hps | Occupancy Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryhps?id=Kaiuz42yhn1hs) |
| Smart Lock | 智能门锁 | ms<br> jtmspro | LockMechanism | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/ms?id=Kb0o2s20fn9sy) |
| Environmental Detector | 环境检测仪 | hjjcy | Air Quality Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/hjjcy?id=Kbeoad8y1nnlv) |


## Exercise & Health

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Massage Chair | 按摩椅 | amy | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryamy?id=Kaiuz4bmwxufp) |
| Physiotherapy Products| 理疗产品 | liliao | | | [Documentation](https://developer.tuya.com/en/docs/iot/categoryliliao?id=Kakobe16fjw3l) |
| Smart Jump Rope | 跳绳 | ts | | | [Documentation](https://developer.tuya.com/en/docs/iot/ts?id=Kat27rqhu47br) |
| Body Fat Scale | 体脂秤 | tzc1 | | | [Documentation](https://developer.tuya.com/en/docs/iot/tzc1?id=Kat27zmbbs56t) |
| Smart Watch/Fitness Tracker | 手表/手环 | sb | | | [Documentation](https://developer.tuya.com/en/docs/iot/sb?id=Kat28k7efsbi9) |
| Smart Pill Box | 智能药盒 | znyh | | | [Documentation](https://developer.tuya.com/en/docs/iot/znyh?id=Kb2yxpjfcojdt) |


## Gateway Control

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Multifunctional Gateway | 多功能网关 | wg | | | [Documentation](https://developer.tuya.com/en/docs/iot/wg2?id=Kau22nplrptfe) |


## Energy

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Smart Electricity Meter | 智能电表 | zndb | | | [Documentation](https://developer.tuya.com/en/docs/iot/smart-meter?id=Kaiuz4gv6ack7) |
| Smart Water Meter | 智能水表 | znsb | | | [Documentation](https://developer.tuya.com/en/docs/iot/smart-water-meter?id=Kaiuz4jf0jy9f) |
| Circuit Breaker | 断路器 | dlq | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/dlq?id=Kb0kidk9enyh8) |


## Digital Entertainment

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| TV | 电视 | ds | | | [Documentation](https://developer.tuya.com/en/docs/iot/ds?id=Kat8px3b6tb9o) |
| Projector | 投影仪 | tyy | | | [Documentation](https://developer.tuya.com/en/docs/iot/tyy?id=Kat8qpj75z0vv) |


## Outdoor Travel

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Tracker | 定位器 | tracker | | | [Documentation](https://developer.tuya.com/en/docs/iot/tracker?id=Kajk21wwy2mhi) |


## IR Remote Control

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Universal Remote Control | 万能遥控器 | wnykq<br> hwktwkq<br> wsdykq | Temperature Sensor<br> Humidity Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/ir-control-hub-open-service?id=Kb3oe2mk8ya72) |
| TV | 电视 | infrared_tv | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| STB | 机顶盒 | infrared_stb | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| TV Box | 电视盒子 | infrared_box | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| Air Conditioner | 空调 | infrared_ac | Heater Cooler<br> Humidifier Dehumidifier<br> Fanv2 | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-air-conditioner-apis?id=Kb3oe9ehg02fn) |
| Fan | 电风扇 | infrared_fan | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| Light | 灯 | infrared_light | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| Amplifier | 音响 | infrared_amplifier | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| Projector | 投影仪 | infrared_projector | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| DVD | DVD | qt | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| Camera | 相机 | qt | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| Water Heater | 热水器 | infrared_waterheater | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| Air Purifier | 净化器 | infrared_airpurifier | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-common-apis?id=Kb3oe2o7z0so9) |
| DIY | - | qt | Switch | ✅ | [Documentation](https://developer.tuya.com/en/docs/cloud/infrared-learning-apis?id=Kb3oeap4nqqm3) |


## Others

| Name | Name (zh) | Code | Homebridge Service | Supported | Links |
| ---- | ---- | ---- | ---- | ---- | ---- |
| Fingerbot | 手指机器人 | szjqr | Switch | ✅ | Documentation |


For the undocumented product category, you can try override it to the most similar one. See [ADVANCED_OPTIONS.md](./ADVANCED_OPTIONS.md).


================================================
FILE: config.schema.json
================================================
{
    "pluginAlias": "TuyaPlatform",
    "pluginType": "platform",
    "singular": true,
    "headerDisplay": "",
    "footerDisplay": "",
    "customUi": false,
    "schema": {
        "type": "object",
        "properties": {
            "name": {
                "type": "string",
                "title": "Name",
                "required": true,
                "default": "Tuya"
            },
            "options": {
                "title": "Project Info",
                "type": "object",
                "required": true,
                "properties": {
                    "projectType": {
                        "title": "Project Type (Development Method)",
                        "type": "string",
                        "default": "2",
                        "oneOf": [
                            {
                                "title": "Custom",
                                "enum": [
                                    "1"
                                ]
                            },
                            {
                                "title": "Smart Home",
                                "enum": [
                                    "2"
                                ]
                            }
                        ],
                        "required": true
                    },
                    "endpoint": {
                        "title": "Endpoint URL",
                        "type": "string",
                        "format": "url"
                    },
                    "accessId": {
                        "title": "Access ID",
                        "type": "string",
                        "required": true
                    },
                    "accessKey": {
                        "title": "Access Secret",
                        "type": "string",
                        "required": true
                    },
                    "countryCode": {
                        "title": "Country Code",
                        "type": "integer",
                        "minimum": 1,
                        "condition": {
                            "functionBody": "return model.options.projectType === '2';"
                        }
                    },
                    "username": {
                        "title": "Username",
                        "type": "string",
                        "condition": {
                            "functionBody": "return model.options.projectType === '2';"
                        }
                    },
                    "password": {
                        "title": "Password",
                        "type": "string",
                        "condition": {
                            "functionBody": "return model.options.projectType === '2';"
                        }
                    },
                    "appSchema": {
                        "title": "App",
                        "type": "string",
                        "default": "tuyaSmart",
                        "oneOf": [
                            {
                                "title": "Tuya Smart",
                                "enum": [
                                    "tuyaSmart"
                                ]
                            },
                            {
                                "title": "Smart Life",
                                "enum": [
                                    "smartlife"
                                ]
                            }
                        ],
                        "condition": {
                            "functionBody": "return model.options.projectType === '2';"
                        }
                    },
                    "homeWhitelist": {
                        "title": "Whitelisted Home IDs",
                        "description": "An optional list of Home IDs to match. If blank, all homes are matched.",
                        "type": "array",
                        "items": {
                            "title": "Home ID",
                            "type": "integer"
                        },
                        "condition": {
                            "functionBody": "return model.options.projectType === '2';"
                        }
                    },
                    "deviceOverrides": {
                        "title": "Device Overriding Configs",
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {
                                    "title": "ID",
                                    "description": "Device ID or Product ID or `global`",
                                    "type": "string",
                                    "required": true
                                },
                                "category": {
                                    "title": "Category",
                                    "description": "Category Code or `hidden`",
                                    "type": "string",
                                    "condition": {
                                        "functionBody": "return (model.options && model.options.deviceOverrides);"
                                    }
                                },
                                "unbridged": {
                                    "title": "Unbridge",
                                    "description": "Would you like to make this device be an external device?",
                                    "type": "boolean",
                                    "condition": {
                                        "functionBody": "return (model.options && model.options.deviceOverrides);"
                                    }
                                },
                                "schema": {
                                    "title": "Schema Overriding Configs",
                                    "type": "array",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "code": {
                                                "title": "DP Code",
                                                "type": "string",
                                                "required": true,
                                                "condition": {
                                                    "functionBody": "return (model.options && model.options.deviceOverrides);"
                                                }
                                            },
                                            "newCode": {
                                                "title": "New DP Code",
                                                "type": "string",
                                                "condition": {
                                                    "functionBody": "return (model.options && model.options.deviceOverrides && model.options.deviceOverrides[arrayIndices[0]].schema && model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].code && !model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].hidden);"
                                                }
                                            },
                                            "type": {
                                                "title": "New DP Type",
                                                "type": "string",
                                                "default": "",
                                                "oneOf": [
                                                    {
                                                        "title": "Boolean",
                                                        "enum": [
                                                            "Boolean"
                                                        ]
                                                    },
                                                    {
                                                        "title": "Integer",
                                                        "enum": [
                                                            "Integer"
                                                        ]
                                                    },
                                                    {
                                                        "title": "Enum",
                                                        "enum": [
                                                            "Enum"
                                                        ]
                                                    },
                                                    {
                                                        "title": "String",
                                                        "enum": [
                                                            "String"
                                                        ]
                                                    },
                                                    {
                                                        "title": "Json",
                                                        "enum": [
                                                            "Json"
                                                        ]
                                                    },
                                                    {
                                                        "title": "Raw",
                                                        "enum": [
                                                            "Raw"
                                                        ]
                                                    }
                                                ],
                                                "condition": {
                                                    "functionBody": "return (model.options && model.options.deviceOverrides && model.options.deviceOverrides[arrayIndices[0]].schema && model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].code && !model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].hidden);"
                                                }
                                            },
                                            "property": {
                                                "title": "New DP Property",
                                                "type": "object",
                                                "properties": {
                                                    "min": {
                                                        "title": "min",
                                                        "type": "integer",
                                                        "condition": {
                                                            "functionBody": "return (model.options && model.options.deviceOverrides && model.options.deviceOverrides[arrayIndices[0]].schema && model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].type === 'Integer' && !model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].hidden);"
                                                        }
                                                    },
                                                    "max": {
                                                        "title": "max",
                                                        "type": "integer",
                                                        "condition": {
                                                            "functionBody": "return (model.options && model.options.deviceOverrides && model.options.deviceOverrides[arrayIndices[0]].schema && model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].type === 'Integer' && !model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].hidden);"
                                                        }
                                                    },
                                                    "scale": {
                                                        "title": "scale",
                                                        "type": "integer",
                                                        "condition": {
                                                            "functionBody": "return (model.options && model.options.deviceOverrides && model.options.deviceOverrides[arrayIndices[0]].schema && model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].type === 'Integer' && !model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].hidden);"
                                                        }
                                                    },
                                                    "step": {
                                                        "title": "step",
                                                        "type": "integer",
                                                        "condition": {
                                                            "functionBody": "return (model.options && model.options.deviceOverrides && model.options.deviceOverrides[arrayIndices[0]].schema && model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].type === 'Integer' && !model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].hidden);"
                                                        }
                                                    },
                                                    "range": {
                                                        "title": "range",
                                                        "type": "array",
                                                        "items": {
                                                            "title": "value",
                                                            "type": "string"
                                                        },
                                                        "condition": {
                                                            "functionBody": "return (model.options && model.options.deviceOverrides && model.options.deviceOverrides[arrayIndices[0]].schema && model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].type === 'Enum' && !model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].hidden);"
                                                        }
                                                    }
                                                },
                                                "condition": {
                                                    "functionBody": "return (model.options && model.options.deviceOverrides && model.options.deviceOverrides[arrayIndices[0]].schema && model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].code && !model.options.deviceOverrides[arrayIndices[0]].schema[arrayIndices[1]].hidden);"
                                                }
                                            },
                                            "hidden": {
                                                "title": "Hidden",
                                                "type": "boolean",
                                                "condition": {
                                                    "functionBody": "return (model.options && model.options.deviceOverrides);"
                                                }
                                            }
                                        }
                                    },
                                    "condition": {
                                        "functionBody": "return (model.options && model.options.deviceOverrides);"
                                    }
                                }
                            }
                        }
                    },
                    "debug": {
                        "title": "Enable Debug Logging",
                        "type": "boolean",
                        "default": false
                    },
                    "debugLevel": {
                        "title": "Debug Level",
                        "description": "An optional list of strings seperated with comma `,`. `api` represents for API and MQTT log, device ID represents for specific device log. If blank, all logs are outputed.",
                        "type": "string",
                        "condition": {
                            "functionBody": "return (model.options && model.options.debug);"
                        }
                    }
                }
            }
        }
    },
    "layout": [
        {
            "type": "fieldset",
            "title": "Tuya Account Info",
            "expandable": true,
            "expanded": false,
            "items": [
                "options.projectType",
                "options.endpoint",
                "options.accessId",
                "options.accessKey",
                "options.countryCode",
                "options.username",
                "options.password",
                "options.appSchema"
            ]
        },
        {
            "type": "fieldset",
            "title": "Tuya Home Settings",
            "expandable": true,
            "expanded": false,
            "notitle": false,
            "items": [
                {
                    "key": "options.homeWhitelist",
                    "add": "Add Another Home ID",
                    "title": "{{ 'New Whitelisted Home' }}",
                    "type": "tabarray",
                    "notitle": true,
                    "items": [
                        {
                            "type": "div",
                            "displayFlex": true,
                            "flex-direction": "row",
                            "notitle": true,
                            "title": "{{ value }}",
                            "items": [
                                {
                                    "key": "options.homeWhitelist[]",
                                    "placeholder": "Home ID"
                                }
                            ]
                        }
                    ]
                }
            ]
        },
        {
            "type": "fieldset",
            "title": "Tuya Device Settings",
            "expandable": true,
            "expanded": true,
            "notitle": false,
            "items": [
                {
                    "key": "options.deviceOverrides",
                    "add": "Add Another Device Override",
                    "title": "{{ 'New Device Override' }}",
                    "type": "tabarray",
                    "notitle": true,
                    "items": [
                        {
                            "type": "div",
                            "displayFlex": false,
                            "flex-direction": "row",
                            "notitle": true,
                            "title": "{{ value.id }}",
                            "items": [
                                {
                                    "key": "options.deviceOverrides[].id"
                                },
                                {
                                    "key": "options.deviceOverrides[].category"
                                },
                                {
                                    "key": "options.deviceOverrides[].unbridged"
                                },
                                {
                                    "key": "options.deviceOverrides[].schema",
                                    "add": "Add New Schema",
                                    "title": "{{ 'New Schema' }}",
                                    "type": "tabarray",
                                    "notitle": true,
                                    "items": [
                                        {
                                            "type": "div",
                                            "displayFlex": true,
                                            "title": "{{ value.code }}",
                                            "flex-direction": "column",
                                            "notitle": false,
                                            "items": [
                                                {
                                                    "key": "options.deviceOverrides[].schema[].code"
                                                },
                                                {
                                                    "key": "options.deviceOverrides[].schema[].newCode"
                                                },
                                                {
                                                    "key": "options.deviceOverrides[].schema[].hidden"
                                                },
                                                {
                                                    "key": "options.deviceOverrides[].schema[].type"
                                                },
                                                {
                                                    "key": "options.deviceOverrides[].schema[].property",
                                                    "notitle": false,
                                                    "items": [
                                                        "options.deviceOverrides[].schema[].property.min",
                                                        "options.deviceOverrides[].schema[].property.max",
                                                        "options.deviceOverrides[].schema[].property.scale",
                                                        "options.deviceOverrides[].schema[].property.step",
                                                        {
                                                            "key": "options.deviceOverrides[].schema[].property.range",
                                                            "add": "Add Range",
                                                            "title": "{{ 'New Range' }}",
                                                            "type": "tabarray",
                                                            "notitle": true,
                                                            "items": [
                                                                {
                                                                    "type": "div",
                                                                    "displayFlex": true,
                                                                    "flex-direction": "row",
                                                                    "notitle": true,
                                                                    "title": "{{ value }}",
                                                                    "items": [
                                                                        {
                                                                            "key": "options.deviceOverrides[].schema[].property.range[]",
                                                                            "placeholder": "Range"
                                                                        }
                                                                    ]
                                                                }
                                                            ]
                                                        }
                                                    ]
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        },
        {
            "type": "fieldset",
            "title": "Advance Settings",
            "expandable": true,
            "expanded": false,
            "notitle": false,
            "items": [
                "options.debug",
                "options.debugLevel"
            ]
        }
    ]
}


================================================
FILE: jest.config.js
================================================
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};


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

================================================
FILE: package.json
================================================
{
  "name": "@0x5e/homebridge-tuya-platform",
  "displayName": "Tuya",
  "version": "1.7.0-beta.58",
  "description": "Fork version of official Tuya Homebridge plugin. Brings a bunch of bug fix and new device support.",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/0x5e/homebridge-tuya-platform"
  },
  "homepage": "https://github.com/0x5e/homebridge-tuya-platform#readme",
  "bugs": {
    "url": "https://github.com/0x5e/homebridge-tuya-platform/issues"
  },
  "funding": [
    {
      "type": "paypal",
      "url": "https://paypal.me/0x5e"
    }
  ],
  "engines": {
    "node": ">=14.18.1",
    "homebridge": ">=1.3.5"
  },
  "main": "dist/index.js",
  "scripts": {
    "lint": "eslint src/**/*.ts --max-warnings=0",
    "test": "jest",
    "watch": "npm run build && npm link && nodemon",
    "launch": "tsc && homebridge -I -D",
    "build": "rimraf ./dist && tsc",
    "prepublishOnly": "npm run lint && npm run build"
  },
  "keywords": [
    "homebridge-plugin",
    "homekit",
    "tuya"
  ],
  "dependencies": {
    "@homebridge/camera-utils": "^2.2.0",
    "async-await-retry": "^2.0.1",
    "color-convert": "^2.0.1",
    "crypto-js": "^4.1.1",
    "debounce": "^1.2.1",
    "jsonschema": "^1.4.1",
    "kelvin-to-rgb": "^1.0.2",
    "lodash.isequal": "^4.5.0",
    "mqtt": "^4.2.6",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "@types/color-convert": "^2.0.0",
    "@types/crypto-js": "^4.1.1",
    "@types/debounce": "^1.2.1",
    "@types/jest": "^29.1.2",
    "@types/lodash.isequal": "^4.5.6",
    "@types/node": "^18.11.9",
    "@types/uuid": "^8.3.4",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.1",
    "homebridge": "^1.3.5",
    "jest": "^29.1.2",
    "nodemon": "^2.0.13",
    "rimraf": "^3.0.2",
    "ts-jest": "^29.0.3",
    "ts-node": "^10.3.0",
    "typescript": "^4.8.4"
  }
}


================================================
FILE: src/accessory/AccessoryFactory.ts
================================================
import { PlatformAccessory } from 'homebridge';
import TuyaDevice from '../device/TuyaDevice';
import { TuyaPlatform } from '../platform';

import BaseAccessory from './BaseAccessory';
import LightAccessory from './LightAccessory';
import DimmerAccessory from './DimmerAccessory';
import OutletAccessory from './OutletAccessory';
import SwitchAccessory from './SwitchAccessory';
import WirelessSwitchAccessory from './WirelessSwitchAccessory';
import SceneSwitchAccessory from './SceneSwitchAccessory';
import FanAccessory from './FanAccessory';
import GarageDoorAccessory from './GarageDoorAccessory';
import WindowAccessory from './WindowAccessory';
import WindowCoveringAccessory from './WindowCoveringAccessory';
import LockAccessory from './LockAccessory';
import ThermostatAccessory from './ThermostatAccessory';
import HeaterAccessory from './HeaterAccessory';
import ValveAccessory from './ValveAccessory';
import ContactSensorAccessory from './ContactSensorAccessory';
import LeakSensorAccessory from './LeakSensorAccessory';
import CarbonMonoxideSensorAccessory from './CarbonMonoxideSensorAccessory';
import CarbonDioxideSensorAccessory from './CarbonDioxideSensorAccessory';
import SmokeSensorAccessory from './SmokeSensorAccessory';
import TemperatureHumiditySensorAccessory from './TemperatureHumiditySensorAccessory';
import LightSensorAccessory from './LightSensorAccessory';
import MotionSensorAccessory from './MotionSensorAccessory';
import AirQualitySensorAccessory from './AirQualitySensorAccessory';
import HumanPresenceSensorAccessory from './HumanPresenceSensorAccessory';
import HumidifierAccessory from './HumidifierAccessory';
import DehumidifierAccessory from './DehumidifierAccessory';
import DiffuserAccessory from './DiffuserAccessory';
import AirPurifierAccessory from './AirPurifierAccessory';
import ExtractionHoodAccessory from './ExtractionHoodAccessory';
import CameraAccessory from './CameraAccessory';
import SceneAccessory from './SceneAccessory';
import AirConditionerAccessory from './AirConditionerAccessory';
import IRControlHubAccessory from './IRControlHubAccessory';
import IRGenericAccessory from './IRGenericAccessory';
import IRAirConditionerAccessory from './IRAirConditionerAccessory';
import SecuritySystemAccessory from './SecuritySystemAccessory';
import VibrationSensorAccessory from './VibrationSensorAccessory';
import WeatherStationAccessory from './WeatherStationAccessory';
import DoorbellAccessory from './DoorbellAccessory';
import PetFeederAccessory from './PetFeederAccessory';
import CatToiletAccessory from './CatToiletAccessory';
import WhiteNoiseLightAccessory from './WhiteNoiseLightAccessory';
import SaunaAccessory from './SaunaAccessory';


export default class AccessoryFactory {
  static createAccessory(
    platform: TuyaPlatform,
    accessory: PlatformAccessory,
    device: TuyaDevice,
  ): BaseAccessory {

    let handler : BaseAccessory | undefined;
    switch (device.category) {

      // Lighting
      case 'dj':
      case 'dsd':
      case 'xdd':
      case 'fwd':
      case 'dc':
      case 'dd':
      case 'gyd':
      case 'tyndj':
      case 'sxd':
        handler = new LightAccessory(platform, accessory);
        break;
      case 'tgq':
      case 'tgkg':
        handler = new DimmerAccessory(platform, accessory);
        break;

      // Electrical Products
      case 'dlq':
      case 'kg':
      case 'tdq':
      case 'qjdcz':
      case 'szjqr':
        handler = new SwitchAccessory(platform, accessory);
        break;
      case 'cz':
      case 'pc':
      case 'wkcz':
        handler = new OutletAccessory(platform, accessory);
        break;
      case 'wxkg':
        handler = new WirelessSwitchAccessory(platform, accessory);
        break;
      case 'cjkg':
        handler = new SceneSwitchAccessory(platform, accessory);
        break;
      case 'bzyd':
        handler = new WhiteNoiseLightAccessory(platform, accessory);
        break;

      // Large Home Appliances
      case 'kt':
      case 'ktkzq':
        handler = new AirConditionerAccessory(platform, accessory);
        break;
      case 'qtwk':
        handler = new SaunaAccessory(platform, accessory);
        break;

      // Small Home Appliances
      case 'qn':
        handler = new HeaterAccessory(platform, accessory);
        break;
      case 'kj':
        handler = new AirPurifierAccessory(platform, accessory);
        break;
      case 'xxj':
        handler = new DiffuserAccessory(platform, accessory);
        break;
      case 'ckmkzq':
        handler = new GarageDoorAccessory(platform, accessory);
        break;
      case 'cl':
      case 'clkg':
        handler = new WindowCoveringAccessory(platform, accessory);
        break;
      case 'cwwsq':
        handler = new PetFeederAccessory(platform, accessory);
        break;
      case 'msp':
        handler = new CatToiletAccessory(platform, accessory);
        break;
      case 'mc':
        handler = new WindowAccessory(platform, accessory);
        break;
      case 'wk':
      case 'wkf':
        handler = new ThermostatAccessory(platform, accessory);
        break;
      case 'ggq':
      case 'sfkzq':
        handler = new ValveAccessory(platform, accessory);
        break;
      case 'jsq':
        handler = new HumidifierAccessory(platform, accessory);
        break;
      case 'cs':
        handler = new DehumidifierAccessory(platform, accessory);
        break;
      case 'fs':
      case 'fsd':
      case 'fskg':
        handler = new FanAccessory(platform, accessory);
        break;
      case 'yyj':
        handler = new ExtractionHoodAccessory(platform, accessory);
        break;

      // Security & Video Surveillance
      case 'sp':
        handler = new CameraAccessory(platform, accessory);
        break;
      case 'ywbj':
        handler = new SmokeSensorAccessory(platform, accessory);
        break;
      case 'mcs':
        handler = new ContactSensorAccessory(platform, accessory);
        break;
      case 'zd':
        handler = new VibrationSensorAccessory(platform, accessory);
        break;
      case 'rqbj':
      case 'jwbj':
      case 'sj':
        handler = new LeakSensorAccessory(platform, accessory);
        break;
      case 'cobj':
      case 'cocgq':
        handler = new CarbonMonoxideSensorAccessory(platform, accessory);
        break;
      case 'co2bj':
      case 'co2cgq':
        handler = new CarbonDioxideSensorAccessory(platform, accessory);
        break;
      case 'wsdcg':
        handler = new TemperatureHumiditySensorAccessory(platform, accessory);
        break;
      case 'ldcg':
        handler = new LightSensorAccessory(platform, accessory);
        break;
      case 'pir':
        handler = new MotionSensorAccessory(platform, accessory);
        break;
      case 'pm25':
      case 'pm2.5':
      case 'pm25cgq':
      case 'hjjcy':
        handler = new AirQualitySensorAccessory(platform, accessory);
        break;
      case 'hps':
        handler = new HumanPresenceSensorAccessory(platform, accessory);
        break;
      case 'ms':
      case 'jtmspro':
        handler = new LockAccessory(platform, accessory);
        break;
      case 'mal':
        handler = new SecuritySystemAccessory(platform, accessory);
        break;
      case 'wxml':
        handler = new DoorbellAccessory(platform, accessory);
        break;
      case 'qxj':
        handler = new WeatherStationAccessory(platform, accessory);
        break;

      // Other
      case 'scene':
        handler = new SceneAccessory(platform, accessory);
        break;
    }

    // IR Control Hub
    if (device.isIRControlHub()) {
      handler = new IRControlHubAccessory(platform, accessory);
    }

    // IR Remote Control
    if (device.isIRRemoteControl()) {
      switch (device.remote_keys?.category_id) {
        case 5: // AC
          handler = new IRAirConditionerAccessory(platform, accessory);
          break;
        default:
          handler = new IRGenericAccessory(platform, accessory);
          break;
      }
    }

    if (handler && !handler.checkRequirements()) {
      handler = undefined;
    }

    if (!handler) {
      platform.log.warn(`Unsupported device: ${device.name}.`);
      handler = new BaseAccessory(platform, accessory);
    }

    handler.configureServices();
    handler.configureStatusActive();
    handler.updateAllValues();
    handler.intialized = true;

    return handler;
  }
}


================================================
FILE: src/accessory/AirConditionerAccessory.ts
================================================
import { TuyaDeviceSchemaEnumProperty, TuyaDeviceSchemaIntegerProperty, TuyaDeviceStatus } from '../device/TuyaDevice';
import { limit } from '../util/util';
import BaseAccessory from './BaseAccessory';
import { configureCurrentRelativeHumidity } from './characteristic/CurrentRelativeHumidity';
import { configureCurrentTemperature } from './characteristic/CurrentTemperature';
import { configureLockPhysicalControls } from './characteristic/LockPhysicalControls';
import { configureRelativeHumidityDehumidifierThreshold } from './characteristic/RelativeHumidityDehumidifierThreshold';
import { configureRotationSpeedLevel } from './characteristic/RotationSpeed';
// import { configureSwingMode } from './characteristic/SwingMode';
import { configureTempDisplayUnits } from './characteristic/TemperatureDisplayUnits';

const SCHEMA_CODE = {
  // AirConditioner
  ACTIVE: ['switch'],
  MODE: ['mode'],
  WORK_STATE: ['work_status', 'mode'],
  CURRENT_TEMP: ['temp_current'],
  TARGET_TEMP: ['temp_set'],
  SPEED_LEVEL: ['fan_speed_enum', 'windspeed'],
  LOCK: ['lock', 'child_lock'],
  TEMP_UNIT_CONVERT: ['temp_unit_convert', 'c_f'],
  SWING: ['switch_horizontal', 'switch_vertical'],
  // Dehumidifier
  CURRENT_HUMIDITY: ['humidity_current'],
  TARGET_HUMIDITY: ['humidity_set'],
};

const AC_MODES = ['auto', 'cold', 'hot'];
const DEHUMIDIFIER_MODE = 'wet';
const FAN_MODE = 'wind';

export default class AirConditionerAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.ACTIVE, SCHEMA_CODE.MODE, SCHEMA_CODE.WORK_STATE, SCHEMA_CODE.CURRENT_TEMP];
  }

  configureServices() {
    this.configureAirConditioner();
    this.configureDehumidifier();
    this.configureFan();

    // Add extra sensors for home automation use.
    configureCurrentTemperature(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_TEMP));
    configureCurrentRelativeHumidity(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_HUMIDITY));
  }

  configureAirConditioner() {
    const activeSchema = this.getSchema(...SCHEMA_CODE.ACTIVE)!;
    const modeSchema = this.getSchema(...SCHEMA_CODE.MODE)!;
    const modeProperty = modeSchema.property as TuyaDeviceSchemaEnumProperty;

    const service = this.mainService();

    // Required Characteristics
    const { INACTIVE, ACTIVE } = this.Characteristic.Active;
    service.getCharacteristic(this.Characteristic.Active)
      .onGet(() => {
        const activeStatus = this.getStatus(activeSchema.code)!;
        const modeStatus = this.getStatus(modeSchema.code)!;
        return (activeStatus.value === true && AC_MODES.includes(modeStatus.value as string)) ? ACTIVE : INACTIVE;
      })
      .onSet(async value => {
        const commands: TuyaDeviceStatus[] = [{
          code: activeSchema.code,
          value: (value === ACTIVE) ? true : false,
        }];

        const modeStatus = this.getStatus(modeSchema.code)!;
        if (!AC_MODES.includes(modeStatus.value as string)) {
          for (const mode of AC_MODES) {
            if (modeProperty.range.includes(mode)) {
              commands.push({ code: modeStatus.code, value: mode });
              break;
            }
          }
        }

        await this.sendCommands(commands, true);
      });

    this.configureCurrentState();
    this.configureTargetState();
    configureCurrentTemperature(this, service, this.getSchema(...SCHEMA_CODE.CURRENT_TEMP));

    // Optional Characteristics
    configureLockPhysicalControls(this, service, this.getSchema(...SCHEMA_CODE.LOCK));
    configureRotationSpeedLevel(this, service, this.getSchema(...SCHEMA_CODE.SPEED_LEVEL), ['auto']);
    // configureSwingMode(this, service, this.getSchema(...SCHEMA_CODE.SWING));
    this.configureCoolingThreshouldTemp();
    this.configureHeatingThreshouldTemp();
    configureTempDisplayUnits(this, service, this.getSchema(...SCHEMA_CODE.TEMP_UNIT_CONVERT));
  }

  configureDehumidifier() {
    const activeSchema = this.getSchema(...SCHEMA_CODE.ACTIVE)!;
    const modeSchema = this.getSchema(...SCHEMA_CODE.MODE)!;
    const property = modeSchema.property as TuyaDeviceSchemaEnumProperty;
    if (!property.range.includes(DEHUMIDIFIER_MODE)) {
      return;
    }

    const service = this.dehumidifierService();

    // Required Characteristics
    const { INACTIVE, ACTIVE } = this.Characteristic.Active;
    service.getCharacteristic(this.Characteristic.Active)
      .onGet(() => {
        const activeStatus = this.getStatus(activeSchema.code)!;
        const modeStatus = this.getStatus(modeSchema.code)!;
        return (activeStatus.value === true && modeStatus.value === DEHUMIDIFIER_MODE) ? ACTIVE : INACTIVE;
      })
      .onSet(async value => {
        await this.sendCommands([{
          code: activeSchema.code,
          value: (value === ACTIVE) ? true : false,
        }, {
          code: modeSchema.code,
          value: DEHUMIDIFIER_MODE,
        }], true);
      });

    const { DEHUMIDIFYING } = this.Characteristic.CurrentHumidifierDehumidifierState;
    service.setCharacteristic(this.Characteristic.CurrentHumidifierDehumidifierState, DEHUMIDIFYING);

    const { DEHUMIDIFIER } = this.Characteristic.TargetHumidifierDehumidifierState;
    service.getCharacteristic(this.Characteristic.TargetHumidifierDehumidifierState)
      .updateValue(DEHUMIDIFIER)
      .setProps({ validValues: [DEHUMIDIFIER] });

    if (this.getSchema(...SCHEMA_CODE.CURRENT_HUMIDITY)) {
      configureCurrentRelativeHumidity(this, service, this.getSchema(...SCHEMA_CODE.CURRENT_HUMIDITY));
    } else {
      service.setCharacteristic(this.Characteristic.CurrentRelativeHumidity, 0);
    }

    // Optional Characteristics
    configureLockPhysicalControls(this, service, this.getSchema(...SCHEMA_CODE.LOCK));
    configureRotationSpeedLevel(this, service, this.getSchema(...SCHEMA_CODE.SPEED_LEVEL), ['auto']);
    configureRelativeHumidityDehumidifierThreshold(this, service, this.getSchema(...SCHEMA_CODE.TARGET_HUMIDITY));
    // configureSwingMode(this, service, this.getSchema(...SCHEMA_CODE.SWING));
  }

  configureFan() {
    const activeSchema = this.getSchema(...SCHEMA_CODE.ACTIVE)!;
    const modeSchema = this.getSchema(...SCHEMA_CODE.MODE)!;
    const property = modeSchema.property as TuyaDeviceSchemaEnumProperty;
    if (!property.range.includes(FAN_MODE)) {
      return;
    }

    const service = this.fanService();

    // Required Characteristics
    const { INACTIVE, ACTIVE } = this.Characteristic.Active;
    service.getCharacteristic(this.Characteristic.Active)
      .onGet(() => {
        const activeStatus = this.getStatus(activeSchema.code)!;
        const modeStatus = this.getStatus(modeSchema.code)!;
        return (activeStatus.value === true && modeStatus.value === FAN_MODE) ? ACTIVE : INACTIVE;
      })
      .onSet(async value => {
        await this.sendCommands([{
          code: activeSchema.code,
          value: (value === ACTIVE) ? true : false,
        }, {
          code: modeSchema.code,
          value: FAN_MODE,
        }], true);
      });

    // Optional Characteristics
    configureLockPhysicalControls(this, service, this.getSchema(...SCHEMA_CODE.LOCK));
    configureRotationSpeedLevel(this, service, this.getSchema(...SCHEMA_CODE.SPEED_LEVEL), ['auto']);
    // configureSwingMode(this, service, this.getSchema(...SCHEMA_CODE.SWING));
  }

  mainService() {
    return this.accessory.getService(this.Service.HeaterCooler)
      || this.accessory.addService(this.Service.HeaterCooler);
  }

  dehumidifierService() {
    return this.accessory.getService(this.Service.HumidifierDehumidifier)
      || this.accessory.addService(this.Service.HumidifierDehumidifier, this.accessory.displayName + ' Dehumidifier');
  }

  fanService() {
    return this.accessory.getService(this.Service.Fanv2)
      || this.accessory.addService(this.Service.Fanv2, this.accessory.displayName + ' Fan');
  }

  configureCurrentState() {
    const schema = this.getSchema(...SCHEMA_CODE.WORK_STATE);
    if (!schema) {
      return;
    }

    const { INACTIVE, HEATING, COOLING } = this.Characteristic.CurrentHeaterCoolerState;
    this.mainService().getCharacteristic(this.Characteristic.CurrentHeaterCoolerState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        if (status.value === 'heating' || status.value === 'hot') {
          return HEATING;
        } else if (status.value === 'cooling' || status.value === 'cold') {
          return COOLING;
        } else {
          return INACTIVE;
        }
      });
  }

  configureTargetState() {
    const schema = this.getSchema(...SCHEMA_CODE.MODE);
    if (!schema) {
      return;
    }

    const { AUTO, HEAT, COOL } = this.Characteristic.TargetHeaterCoolerState;

    const validValues: number[] = [];
    const property = schema.property as TuyaDeviceSchemaEnumProperty;
    if (property.range.includes('auto')) {
      validValues.push(AUTO);
    }
    if (property.range.includes('hot')) {
      validValues.push(HEAT);
    }
    if (property.range.includes('cold')) {
      validValues.push(COOL);
    }

    if (validValues.length === 0) {
      this.log.warn('Invalid mode range for TargetHeaterCoolerState:', property.range);
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.TargetHeaterCoolerState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        if (status.value === 'hot') {
          return HEAT;
        } else if (status.value === 'cold') {
          return COOL;
        }

        return validValues.includes(AUTO) ? AUTO : validValues[0];
      })
      .onSet(async value => {

        let mode: string;
        if (value === HEAT) {
          mode = 'hot';
        } else if (value === COOL) {
          mode = 'cold';
        } else {
          mode = 'auto';
        }

        await this.sendCommands([{ code: schema.code, value: mode }], true);
      })
      .setProps({ validValues });
  }

  configureCoolingThreshouldTemp() {
    const schema = this.getSchema(...SCHEMA_CODE.TARGET_TEMP);
    if (!schema) {
      return;
    }

    const property = schema.property as TuyaDeviceSchemaIntegerProperty;
    const multiple = Math.pow(10, property.scale);
    const props = {
      minValue: property.min / multiple,
      maxValue: property.max / multiple,
      minStep: Math.max(0.1, property.step / multiple),
    };
    this.log.debug('Set props for CoolingThresholdTemperature:', props);

    this.mainService().getCharacteristic(this.Characteristic.CoolingThresholdTemperature)
      .onGet(() => {
        const modeSchema = this.getSchema(...SCHEMA_CODE.MODE);
        if (modeSchema && this.getStatus(modeSchema.code)!.value === 'auto') {
          return props.minValue;
        }

        const status = this.getStatus(schema.code)!;
        const temp = status.value as number / multiple;
        return limit(temp, props.minValue, props.maxValue);
      })
      .onSet(async value => {
        const modeSchema = this.getSchema(...SCHEMA_CODE.MODE);
        if (modeSchema && this.getStatus(modeSchema.code)!.value === 'auto') {
          this.mainService().getCharacteristic(this.Characteristic.CoolingThresholdTemperature)
            .updateValue(props.minValue);
          return;
        }

        await this.sendCommands([{ code: schema.code, value: (value as number) * multiple}], true);
      })
      .setProps(props);
  }

  configureHeatingThreshouldTemp() {
    const schema = this.getSchema(...SCHEMA_CODE.TARGET_TEMP);
    if (!schema) {
      return;
    }

    const property = schema.property as TuyaDeviceSchemaIntegerProperty;
    const multiple = Math.pow(10, property.scale);
    const props = {
      minValue: property.min / multiple,
      maxValue: property.max / multiple,
      minStep: Math.max(0.1, property.step / multiple),
    };
    this.log.debug('Set props for HeatingThresholdTemperature:', props);

    this.mainService().getCharacteristic(this.Characteristic.HeatingThresholdTemperature)
      .onGet(() => {
        const modeSchema = this.getSchema(...SCHEMA_CODE.MODE);
        if (modeSchema && this.getStatus(modeSchema.code)!.value === 'auto') {
          return props.maxValue;
        }

        const status = this.getStatus(schema.code)!;
        const temp = status.value as number / multiple;
        return limit(temp, props.minValue, props.maxValue);
      })
      .onSet(async value => {
        const modeSchema = this.getSchema(...SCHEMA_CODE.MODE);
        if (modeSchema && this.getStatus(modeSchema.code)!.value === 'auto') {
          this.mainService().getCharacteristic(this.Characteristic.HeatingThresholdTemperature)
            .updateValue(props.maxValue);
          return;
        }

        await this.sendCommands([{ code: schema.code, value: (value as number) * multiple}], true);
      })
      .setProps(props);
  }

}


================================================
FILE: src/accessory/AirPurifierAccessory.ts
================================================
import { TuyaDeviceSchemaType } from '../device/TuyaDevice';
import BaseAccessory from './BaseAccessory';
import { configureActive } from './characteristic/Active';
import { configureAirQuality } from './characteristic/AirQuality';
import { configureLockPhysicalControls } from './characteristic/LockPhysicalControls';
import { configureRotationSpeed, configureRotationSpeedLevel } from './characteristic/RotationSpeed';

const SCHEMA_CODE = {
  ACTIVE: ['switch'],
  MODE: ['mode'],
  LOCK: ['lock'],
  SPEED: ['speed'],
  SPEED_LEVEL: ['fan_speed_enum', 'speed'],
  AIR_QUALITY: ['air_quality', 'pm25'],
  PM2_5: ['pm25'],
  VOC: ['tvoc'],
};

export default class AirPurifierAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.ACTIVE];
  }

  configureServices() {
    configureActive(this, this.mainService(), this.getSchema(...SCHEMA_CODE.ACTIVE));
    this.configureCurrentState();
    this.configureTargetState();
    configureLockPhysicalControls(this, this.mainService(), this.getSchema(...SCHEMA_CODE.LOCK));
    if (this.getFanSpeedSchema()) {
      configureRotationSpeed(this, this.mainService(), this.getFanSpeedSchema());
    } else if (this.getFanSpeedLevelSchema()) {
      configureRotationSpeedLevel(this, this.mainService(), this.getFanSpeedLevelSchema());
    }

    // Other
    configureAirQuality(
      this,
      undefined,
      this.getSchema(...SCHEMA_CODE.AIR_QUALITY),
      this.getSchema(...SCHEMA_CODE.PM2_5),
      undefined,
      this.getSchema(...SCHEMA_CODE.VOC),
    );
  }


  mainService() {
    return this.accessory.getService(this.Service.AirPurifier)
      || this.accessory.addService(this.Service.AirPurifier);
  }

  getFanSpeedSchema() {
    const schema = this.getSchema(...SCHEMA_CODE.SPEED);
    if (schema && schema.type === TuyaDeviceSchemaType.Integer) {
      return schema;
    }
    return undefined;
  }

  getFanSpeedLevelSchema() {
    const schema = this.getSchema(...SCHEMA_CODE.SPEED_LEVEL);
    if (schema && schema.type === TuyaDeviceSchemaType.Enum) {
      return schema;
    }
    return undefined;
  }


  configureCurrentState() {
    const schema = this.getSchema(...SCHEMA_CODE.ACTIVE);
    if (!schema) {
      return;
    }

    const { INACTIVE, PURIFYING_AIR } = this.Characteristic.CurrentAirPurifierState;
    this.mainService().getCharacteristic(this.Characteristic.CurrentAirPurifierState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return status.value as boolean ? PURIFYING_AIR : INACTIVE;
      });
  }

  configureTargetState() {
    const schema = this.getSchema(...SCHEMA_CODE.MODE);
    if (!schema) {
      return;
    }

    const { MANUAL, AUTO } = this.Characteristic.TargetAirPurifierState;
    this.mainService().getCharacteristic(this.Characteristic.TargetAirPurifierState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return (status.value === 'auto') ? AUTO : MANUAL;
      })
      .onSet(async value => {
        await this.sendCommands([{
          code: schema.code,
          value: (value === AUTO) ? 'auto' : 'manual',
        }], true);
      });
  }

}


================================================
FILE: src/accessory/AirQualitySensorAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';
import { configureAirQuality } from './characteristic/AirQuality';
import { configureCurrentRelativeHumidity } from './characteristic/CurrentRelativeHumidity';
import { configureCurrentTemperature } from './characteristic/CurrentTemperature';

const SCHEMA_CODE = {
  AIR_QUALITY: ['pm25_value'],
  PM2_5: ['pm25_value'],
  PM10: ['pm10_value', 'pm10'],
  VOC: ['voc_value'],
  CURRENT_TEMP: ['va_temperature', 'temp_indoor', 'temp_current'],
  CURRENT_HUMIDITY: ['va_humidity', 'humidity_value'],
};

export default class AirQualitySensorAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.AIR_QUALITY];
  }

  configureServices() {
    configureAirQuality(
      this,
      undefined,
      this.getSchema(...SCHEMA_CODE.AIR_QUALITY),
      this.getSchema(...SCHEMA_CODE.PM2_5),
      this.getSchema(...SCHEMA_CODE.PM10),
      this.getSchema(...SCHEMA_CODE.VOC),
    );

    // Other
    configureCurrentTemperature(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_TEMP));
    configureCurrentRelativeHumidity(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_HUMIDITY));
  }

}


================================================
FILE: src/accessory/BaseAccessory.ts
================================================
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { PlatformAccessory, Service, Characteristic, Nullable, CharacteristicValue } from 'homebridge';
import { debounce } from 'debounce';
import isEqual from 'lodash.isequal';

import { TuyaDeviceSchema, TuyaDeviceSchemaIntegerProperty, TuyaDeviceSchemaMode, TuyaDeviceStatus } from '../device/TuyaDevice';
import { TuyaPlatform } from '../platform';
import { limit } from '../util/util';
import { PrefixLogger } from '../util/Logger';

const MANUFACTURER = 'Tuya Inc.';

const SCHEMA_CODE = {
  BATTERY_STATE: ['battery_state'],
  BATTERY_PERCENT: ['battery_percentage', 'residual_electricity', 'wireless_electricity', 'va_battery', 'battery'],
  BATTERY_CHARGING: ['charge_state'],
};


/**
 * Homebridge Accessory Categories Documentation:
 *   https://developers.homebridge.io/#/categories
 * Tuya Standard Instruction Set Documentation:
 *   https://developer.tuya.com/en/docs/iot/standarddescription?id=K9i5ql6waswzq
 */
class BaseAccessory {
  public readonly Service: typeof Service = this.platform.api.hap.Service;
  public readonly Characteristic: typeof Characteristic = this.platform.api.hap.Characteristic;

  public deviceManager = this.platform.deviceManager!;
  public device = this.deviceManager.getDevice(this.accessory.context.deviceID)!;
  public log = new PrefixLogger(
    this.platform.log,
    this.device.name.length > 0 ? this.device.name : this.device.id,
    this.platform.options.debug && ((this.platform.options.debugLevel ?? '').length > 0
      ? this.platform.options.debugLevel?.includes(this.device.id)
      : true),
  );

  public intialized = false;

  public adaptiveLightingController?;

  constructor(
    public readonly platform: TuyaPlatform,
    public readonly accessory: PlatformAccessory,
  ) {
    this.addAccessoryInfoService();
    this.addBatteryService();
  }

  addAccessoryInfoService() {
    const service = this.accessory.getService(this.Service.AccessoryInformation)
      || this.accessory.addService(this.Service.AccessoryInformation);

    service
      .setCharacteristic(this.Characteristic.Manufacturer, MANUFACTURER)
      .setCharacteristic(this.Characteristic.Model, this.device.product_id)
      .setCharacteristic(this.Characteristic.Name, this.device.name)
      .setCharacteristic(this.Characteristic.ConfiguredName, this.device.name)
      .setCharacteristic(this.Characteristic.SerialNumber, this.device.uuid)
    ;
  }

  addBatteryService() {
    const percentSchema = this.getSchema(...SCHEMA_CODE.BATTERY_PERCENT);
    if (!percentSchema) {
      return;
    }

    const { BATTERY_LEVEL_NORMAL, BATTERY_LEVEL_LOW } = this.Characteristic.StatusLowBattery;
    const service = this.accessory.getService(this.Service.Battery)
      || this.accessory.addService(this.Service.Battery);

    const stateSchema = this.getSchema(...SCHEMA_CODE.BATTERY_STATE);
    if (stateSchema || percentSchema) {
      service.getCharacteristic(this.Characteristic.StatusLowBattery)
        .onGet(() => {
          if (stateSchema) {
            const status = this.getStatus(stateSchema.code)!;
            return (status!.value === 'low') ? BATTERY_LEVEL_LOW : BATTERY_LEVEL_NORMAL;
          }

          // fallback
          const status = this.getStatus(percentSchema.code)!;
          return (status!.value as number <= 20) ? BATTERY_LEVEL_LOW : BATTERY_LEVEL_NORMAL;
        });
    }

    const property = percentSchema.property as TuyaDeviceSchemaIntegerProperty;
    const multiple = Math.pow(10, property ? property.scale : 0);
    service.getCharacteristic(this.Characteristic.BatteryLevel)
      .onGet(() => {
        const status = this.getStatus(percentSchema.code)!;
        return limit(status.value as number / multiple, 0, 100);
      });

    const chargingSchema = this.getSchema(...SCHEMA_CODE.BATTERY_CHARGING);
    if (chargingSchema) {
      const { NOT_CHARGING, CHARGING } = this.Characteristic.ChargingState;
      service.getCharacteristic(this.Characteristic.ChargingState)
        .onGet(() => {
          const status = this.getStatus(chargingSchema.code)!;
          return (status.value as boolean) ? CHARGING : NOT_CHARGING;
        });
    }
  }

  configureStatusActive() {
    for (const service of this.accessory.services) {
      if (!service.testCharacteristic(this.Characteristic.StatusActive)) { // silence warning
        service.addOptionalCharacteristic(this.Characteristic.StatusActive);
      }
      service.getCharacteristic(this.Characteristic.StatusActive)
        .onGet(() => this.device.online);
    }
  }

  async updateAllValues() {
    for (const service of this.accessory.services) {
      for (const characteristic of service.characteristics) {
        if (characteristic.UUID === this.Characteristic.ProgrammableSwitchEvent.UUID) {
          continue;
        }

        let newValue: Nullable<CharacteristicValue> | Error = characteristic.value;
        const getHandler = characteristic['getHandler'];
        if (getHandler) {
          try {
            newValue = await getHandler();
          } catch (error) {
            // TODO: why `characteristic.updateValue(HapStatusError)` not working?
            // newValue = error as Error;
            continue;
          }
        }

        if (characteristic.value !== newValue && !(newValue instanceof Error)) {
          this.log.debug(
            '[%s/%s/%s] Update value: %o => %o',
            service.constructor.name,
            service.subtype,
            characteristic.constructor.name,
            characteristic.value,
            newValue,
          );
        }
        characteristic.updateValue(newValue);
      }
    }
  }

  checkOnlineStatus() {
    if (!this.device.online) {
      const { HapStatusError, HAPStatus } = this.platform.api.hap;
      throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
    }
  }

  getSchema(...codes: string[]) {
    for (const code of codes) {
      const schema = this.device.schema.find(schema => schema.code === code);
      if (!schema) {
        continue;
      }

      // Readable schema must have a status
      if ([TuyaDeviceSchemaMode.READ_WRITE, TuyaDeviceSchemaMode.READ_ONLY].includes(schema.mode)
        && !this.getStatus(schema.code)) {
        continue;
      }

      return schema;
    }
    return undefined;
  }

  getStatus(code: string) {
    return this.device.status.find(status => status.code === code);
  }

  private sendQueue = new Map<string, TuyaDeviceStatus>();
  private debounceSendCommands = debounce(async () => {
    const commands = [...this.sendQueue.values()];
    if (commands.length === 0) {
      return;
    }
    await this.deviceManager.sendCommands(this.device.id, commands);
    this.sendQueue.clear();
  }, 100);

  async sendCommands(commands: TuyaDeviceStatus[], debounce = false) {
    if (commands.length === 0) {
      return;
    }

    commands = commands.filter((status) => status.code && status.value !== undefined);

    if (this.device.online === false) {
      this.log.warn('Device is offline, skip send command.');
      this.updateAllValues();
      const { HapStatusError, HAPStatus } = this.platform.api.hap;
      throw new HapStatusError(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
      return;
    }

    // Update cache immediately
    for (const newStatus of commands) {
      const oldStatus = this.device.status.find(_status => _status.code === newStatus.code);
      if (oldStatus) {
        oldStatus.value = newStatus.value;
      }
    }

    if (debounce === false) {
      return await this.deviceManager.sendCommands(this.device.id, commands);
    }

    for (const newStatus of commands) {
      // Update send queue
      this.sendQueue.set(newStatus.code, newStatus);
    }

    this.debounceSendCommands();
  }

  checkRequirements() {
    let result = true;
    for (const codes of this.requiredSchema()) {
      const schema = this.getSchema(...codes);
      if (schema) {
        continue;
      }
      this.log.warn('Product Category: %s', this.device.category);
      this.log.warn('Missing one of the required schema: %s', codes);
      this.log.warn('Please switch device control mode to "DP Insctrution", and set `deviceOverrides` manually.');
      this.log.warn('Detail information: https://github.com/0x5e/homebridge-tuya-platform#faq');
      result = false;
    }

    if (!result) {
      this.log.warn('Existing schema: %o', this.device.schema);
    }

    return result;
  }

  requiredSchema(): string[][] {
    return [];
  }

  configureServices() {
    //
  }

  async onDeviceInfoUpdate(info) {
    this.updateAllValues();
  }

  async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {
    this.updateAllValues();
  }

}

// Overriding getSchema, getStatus, sendCommands
export default class OverridedBaseAccessory extends BaseAccessory {

  private eval = (script: string, device, value) => eval(script);

  private getOverridedSchema(code: string) {
    const schemaConfig = this.platform.getDeviceSchemaConfig(this.device, code);
    if (!schemaConfig) {
      return undefined;
    }

    const oldSchema = this.device.schema.find(schema => schema.code === schemaConfig.code);
    if (!oldSchema) {
      return undefined;
    }

    const schema = {
      code,
      mode: oldSchema.mode,
      type: schemaConfig.type || oldSchema.type,
      property: schemaConfig.property || oldSchema.property,
      _hidden: schemaConfig.hidden,
    } as TuyaDeviceSchema;

    if (!isEqual(oldSchema, schema)) {
      this.log.debug('Override schema %o => %o', oldSchema, schema);
    }

    return schema;
  }

  getSchema(...codes: string[]) {
    for (const code of codes) {
      const schema = this.getOverridedSchema(code) || super.getSchema(code);
      if (!schema) {
        continue;
      }
      if (schema['_hidden']) {
        return undefined;
      }
      return schema;
    }
    return undefined;
  }


  private getOverridedStatus(code: string) {
    const schemaConfig = this.platform.getDeviceSchemaConfig(this.device, code);
    if (!schemaConfig) {
      return undefined;
    }

    const oldStatus = super.getStatus(schemaConfig.code);
    if (!oldStatus) {
      return undefined;
    }

    const status = { code: schemaConfig.newCode || schemaConfig.code, value: oldStatus.value } as TuyaDeviceStatus;
    if (schemaConfig.onGet) {
      status.value = this.eval(schemaConfig.onGet, this.device, oldStatus.value);
    }

    if (!isEqual(oldStatus, status)) {
      this.log.debug('Override status %o => %o', oldStatus, status);
    }

    return status;
  }

  getStatus(code: string) {
    return this.getOverridedStatus(code) || super.getStatus(code);
  }


  async sendCommands(commands: TuyaDeviceStatus[], debounce?: boolean) {

    // convert to original commands
    for (const command of commands) {
      const schemaConfig = this.platform.getDeviceSchemaConfig(this.device, command.code);
      if (!schemaConfig) {
        continue;
      }

      const oldCommand = { code: schemaConfig.code, value: command.value } as TuyaDeviceStatus;
      if (schemaConfig.onSet) {
        oldCommand.value = this.eval(schemaConfig.onSet, this.device, command.value);
      }

      if (!isEqual(oldCommand, command)) {
        this.log.debug('Override command %o => %o', command, oldCommand);
        command.code = oldCommand.code;
        command.value = oldCommand.value;
      }
    }

    await super.sendCommands(commands, debounce);
  }
}


================================================
FILE: src/accessory/CameraAccessory.ts
================================================
import { TuyaDeviceStatus } from '../device/TuyaDevice';
import { TuyaStreamingDelegate } from '../util/TuyaStreamDelegate';
import BaseAccessory from './BaseAccessory';
import { configureLight } from './characteristic/Light';
import { configureOn } from './characteristic/On';
import { configureProgrammableSwitchEvent, onProgrammableSwitchEvent } from './characteristic/ProgrammableSwitchEvent';

const SCHEMA_CODE = {
  MOTION_ON: ['motion_switch'],
  MOTION_DETECT: ['movement_detect_pic'],
  // Indicates that this is possibly a doorbell
  DOORBELL: ['doorbell_ring_exist'],
  // Notifies when a doorbell ring occurs.
  DOORBELL_RING: ['doorbell_pic'],
  // Notifies when a doorbell ring occurs.
  ALARM_MESSAGE: ['alarm_message'],
  LIGHT_ON: ['floodlight_switch'],
  LIGHT_BRIGHT: ['floodlight_lightness'],
};

export default class CameraAccessory extends BaseAccessory {

  private stream: TuyaStreamingDelegate | undefined;

  requiredSchema() {
    return [];
  }

  configureServices() {
    this.configureDoorbell();
    this.configureCamera();
    this.configureMotion();
    this.configureFloodLight();
  }

  configureMotion() {
    const onSchema = this.getSchema(...SCHEMA_CODE.MOTION_ON);
    if (onSchema) {
      const onService = this.accessory.getService(onSchema.code)
        || this.accessory.addService(this.Service.Switch, onSchema.code, onSchema.code);

      configureOn(this, onService, onSchema);
    }

    this.getMotionService().setCharacteristic(this.Characteristic.MotionDetected, false);
  }

  configureDoorbell() {
    // Check to see if it is indeed a doorbell.
    if (!this.getSchema(...SCHEMA_CODE.DOORBELL)) {
      return;
    }

    const schema = this.getSchema(...SCHEMA_CODE.DOORBELL_RING, ...SCHEMA_CODE.ALARM_MESSAGE);
    if (!schema) {
      return;
    }

    configureProgrammableSwitchEvent(this, this.getDoorbellService(), schema);
  }

  configureCamera() {
    if (this.stream !== undefined) {
      return;
    }

    if (this.device.isVirtualDevice()) {
      return;
    }

    this.stream = new TuyaStreamingDelegate(this);
    this.accessory.configureController(this.stream.controller);
  }

  configureFloodLight() {
    if (!this.getSchema(...SCHEMA_CODE.LIGHT_ON)) {
      return;
    }

    configureLight(
      this,
      this.getLightService(),
      this.getSchema(...SCHEMA_CODE.LIGHT_ON),
      this.getSchema(...SCHEMA_CODE.LIGHT_BRIGHT),
      undefined,
      undefined,
      undefined,
    );
  }

  getLightService() {
    return this.accessory.getService(this.Service.Lightbulb)
      || this.accessory.addService(this.Service.Lightbulb, this.accessory.displayName + ' Floodlight');
  }

  getDoorbellService() {
    return this.accessory.getService(this.Service.Doorbell)
      || this.accessory.addService(this.Service.Doorbell);
  }

  getMotionService() {
    return this.accessory.getService(this.Service.MotionSensor)
      || this.accessory.addService(this.Service.MotionSensor, this.accessory.displayName + ' Motion Detect');
  }

  async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {
    super.onDeviceStatusUpdate(status);

    const doorbellRingSchema = this.getSchema(...SCHEMA_CODE.DOORBELL_RING);
    const alarmMessageSchema = this.getSchema(...SCHEMA_CODE.ALARM_MESSAGE);
    if (this.getSchema(...SCHEMA_CODE.DOORBELL) && (doorbellRingSchema || alarmMessageSchema)) {
      const doorbellRingStatus = doorbellRingSchema && status.find(_status => _status.code === doorbellRingSchema.code);
      const alarmMessageStatus = alarmMessageSchema && status.find(_status => _status.code === alarmMessageSchema.code);
      if (doorbellRingStatus && (doorbellRingStatus.value as string).length > 1) { // Compared with '1' in order to filter value '$'
        onProgrammableSwitchEvent(this, this.getDoorbellService(), doorbellRingStatus);
      } else if (alarmMessageStatus && (alarmMessageStatus.value as string).length > 1) {
        onProgrammableSwitchEvent(this, this.getDoorbellService(), alarmMessageStatus);
      }
    }

    const motionSchema = this.getSchema(...SCHEMA_CODE.MOTION_DETECT);
    if (motionSchema) {
      const motionStatus = status.find(_status => _status.code === motionSchema.code);
      motionStatus && this.onMotionDetected(motionStatus);
    }
  }

  private timer?: NodeJS.Timeout;
  onMotionDetected(status: TuyaDeviceStatus) {
    if (!this.intialized) {
      return;
    }

    const data = Buffer.from(status.value as string, 'base64').toString('binary');
    if (data.length === 0) {
      return;
    }

    this.log.info('Motion event:', data);
    const characteristic = this.getMotionService().getCharacteristic(this.Characteristic.MotionDetected);
    characteristic.updateValue(true);

    this.timer && clearTimeout(this.timer);
    this.timer = setTimeout(() => characteristic.updateValue(false), 30 * 1000);
  }

}


================================================
FILE: src/accessory/CarbonDioxideSensorAccessory.ts
================================================
import { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDevice';
import { limit } from '../util/util';
import BaseAccessory from './BaseAccessory';

const SCHEMA_CODE = {
  CO2_STATUS: ['co2_state'],
  CO2_LEVEL: ['co2_value'],
};


export default class CarbonDioxideSensorAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.CO2_STATUS];
  }

  configureServices() {
    this.configureCarbonDioxideDetected();
    this.configureCarbonDioxideLevel();
  }


  mainService() {
    return this.accessory.getService(this.Service.CarbonDioxideSensor)
      || this.accessory.addService(this.Service.CarbonDioxideSensor);
  }

  configureCarbonDioxideDetected() {
    const schema = this.getSchema(...SCHEMA_CODE.CO2_STATUS);
    if (!schema) {
      return;
    }

    const { CO2_LEVELS_ABNORMAL, CO2_LEVELS_NORMAL } = this.Characteristic.CarbonDioxideDetected;
    this.mainService().getCharacteristic(this.Characteristic.CarbonDioxideDetected)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return (status.value === 'alarm') ? CO2_LEVELS_ABNORMAL : CO2_LEVELS_NORMAL;
      });
  }

  configureCarbonDioxideLevel() {
    const schema = this.getSchema(...SCHEMA_CODE.CO2_LEVEL);
    if (!schema) {
      return;
    }

    const property = schema.property as TuyaDeviceSchemaIntegerProperty;
    const multiple = Math.pow(10, property ? property.scale : 0);
    this.mainService().getCharacteristic(this.Characteristic.CarbonDioxideLevel)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        const value = limit(status.value as number / multiple, 0, 100000);
        return value;
      });
  }

}


================================================
FILE: src/accessory/CarbonMonoxideSensorAccessory.ts
================================================
import { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDevice';
import { limit } from '../util/util';
import BaseAccessory from './BaseAccessory';

const SCHEMA_CODE = {
  CO_STATUS: ['co_status', 'co_state'],
  CO_LEVEL: ['co_value'],
};

export default class CarbonMonoxideSensorAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.CO_STATUS];
  }

  configureServices() {
    this.configureCarbonMonoxideDetected();
    this.configureCarbonMonoxideLevel();
  }


  mainService() {
    return this.accessory.getService(this.Service.CarbonMonoxideSensor)
      || this.accessory.addService(this.Service.CarbonMonoxideSensor);
  }

  configureCarbonMonoxideDetected() {
    const schema = this.getSchema(...SCHEMA_CODE.CO_STATUS);
    if (!schema) {
      return;
    }

    const { CO_LEVELS_ABNORMAL, CO_LEVELS_NORMAL } = this.Characteristic.CarbonMonoxideDetected;
    this.mainService().getCharacteristic(this.Characteristic.CarbonMonoxideDetected)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return (status.value === 'alarm' || status.value === '1') ? CO_LEVELS_ABNORMAL : CO_LEVELS_NORMAL;
      });
  }

  configureCarbonMonoxideLevel() {
    const schema = this.getSchema(...SCHEMA_CODE.CO_LEVEL);
    if (!schema) {
      return;
    }

    const property = schema.property as TuyaDeviceSchemaIntegerProperty;
    const multiple = Math.pow(10, property ? property.scale : 0);
    this.mainService().getCharacteristic(this.Characteristic.CarbonMonoxideLevel)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        const value = limit(status.value as number / multiple, 0, 100);
        return value;
      });
  }
}


================================================
FILE: src/accessory/CatToiletAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';
import { configureName } from './characteristic/Name';
import { configureOn } from './characteristic/On';

const SCHEMA_CODE = {
  SWITCH: ['switch'],
  AUTO_CLEAN: ['auto_clean'],
  MANUAL_CLEAN: ['manual_clean'],
  DEODORIZATION: ['deodorization'],
  UV: ['uv'],
  LIGHT: ['light'],
  STATUS: ['status'],
  CAT_WEIGHT: ['cat_weight'],
  EXCRETION_TIMES: ['excretion_times_day'],
  EXCRETION_TIME: ['excretion_time_day'],
  NOTIFICATION: ['notification'],
  FAULT: ['fault'],
};

export default class CatToiletAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.SWITCH];
  }

  configureServices() {
    // Main power switch
    configureOn(this, this.mainService(), this.getSchema(...SCHEMA_CODE.SWITCH));
    configureName(this, this.mainService(), this.device.name);

    // Additional switches
    this.configureSwitch(SCHEMA_CODE.AUTO_CLEAN, 'Auto Clean');
    this.configureSwitch(SCHEMA_CODE.MANUAL_CLEAN, 'Manual Clean');
    this.configureSwitch(SCHEMA_CODE.DEODORIZATION, 'Deodorization');
    this.configureSwitch(SCHEMA_CODE.UV, 'UV Sterilization');

    // Mood light as Lightbulb
    this.configureLight();

    // Occupancy sensor for active status
    this.configureOccupancySensor();

    // Filter maintenance for garbage box full
    this.configureFilterMaintenance();

    // Fault handling
    this.configureFault();
  }

  mainService() {
    return this.accessory.getService(this.Service.Switch)
      || this.accessory.addService(this.Service.Switch, this.device.name, 'switch');
  }

  configureSwitch(schemaCodes: string[], name: string) {
    const schema = this.getSchema(...schemaCodes);
    if (!schema) {
      return;
    }

    const service = this.accessory.getService(schema.code)
      || this.accessory.addService(this.Service.Switch, name, schema.code);

    configureName(this, service, name);
    configureOn(this, service, schema);
  }

  configureLight() {
    const schema = this.getSchema(...SCHEMA_CODE.LIGHT);
    if (!schema) {
      return;
    }

    const service = this.accessory.getService(schema.code)
      || this.accessory.addService(this.Service.Lightbulb, 'Mood Light', schema.code);

    configureName(this, service, 'Mood Light');
    service.getCharacteristic(this.Characteristic.On)
      .onGet(() => {
        this.checkOnlineStatus();
        const status = this.getStatus(schema.code)!;
        return status.value as boolean;
      })
      .onSet(async value => {
        await this.sendCommands([{
          code: schema.code,
          value: value as boolean,
        }], true);
      });
  }

  configureOccupancySensor() {
    const schema = this.getSchema(...SCHEMA_CODE.STATUS);
    if (!schema) {
      return;
    }

    const service = this.accessory.getService(this.Service.OccupancySensor)
      || this.accessory.addService(this.Service.OccupancySensor, 'Status', 'status');

    configureName(this, service, 'Status');

    const { OCCUPANCY_DETECTED, OCCUPANCY_NOT_DETECTED } = this.Characteristic.OccupancyDetected;
    service.getCharacteristic(this.Characteristic.OccupancyDetected)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        const activeStates = ['cleaning', 'uv', 'deodorization'];
        return activeStates.includes(status.value as string)
          ? OCCUPANCY_DETECTED
          : OCCUPANCY_NOT_DETECTED;
      });
  }

  configureFilterMaintenance() {
    const schema = this.getSchema(...SCHEMA_CODE.NOTIFICATION);
    if (!schema) {
      return;
    }

    const service = this.accessory.getService(this.Service.FilterMaintenance)
      || this.accessory.addService(this.Service.FilterMaintenance, 'Waste Box', 'notification');

    configureName(this, service, 'Waste Box');

    const { CHANGE_FILTER, FILTER_OK } = this.Characteristic.FilterChangeIndication;
    service.getCharacteristic(this.Characteristic.FilterChangeIndication)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        // Bit 0 = garbage_box_full
        const value = status.value as number;
        return (value & 1) ? CHANGE_FILTER : FILTER_OK;
      });
  }

  configureFault() {
    const schema = this.getSchema(...SCHEMA_CODE.FAULT);
    if (!schema) {
      return;
    }

    // Add fault status to main service
    this.mainService().getCharacteristic(this.Characteristic.StatusFault)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        const { GENERAL_FAULT, NO_FAULT } = this.Characteristic.StatusFault;
        return (status.value as number) > 0 ? GENERAL_FAULT : NO_FAULT;
      });
  }
}


================================================
FILE: src/accessory/ContactSensorAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';

const SCHEMA_CODE = {
  CONTACT_STATE: ['doorcontact_state', 'switch'],
};

export default class ContaceSensor extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.CONTACT_STATE];
  }

  configureServices() {
    const schema = this.getSchema(...SCHEMA_CODE.CONTACT_STATE);
    if (!schema) {
      return;
    }
    const service = this.accessory.getService(this.Service.ContactSensor)
    || this.accessory.addService(this.Service.ContactSensor);

    const { CONTACT_NOT_DETECTED, CONTACT_DETECTED } = this.Characteristic.ContactSensorState;
    service.getCharacteristic(this.Characteristic.ContactSensorState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return status.value ? CONTACT_NOT_DETECTED : CONTACT_DETECTED;
      });
  }

}


================================================
FILE: src/accessory/DehumidifierAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';
import { configureActive } from './characteristic/Active';
import { configureCurrentTemperature } from './characteristic/CurrentTemperature';
import { configureCurrentRelativeHumidity } from './characteristic/CurrentRelativeHumidity';
import { configureRotationSpeedLevel } from './characteristic/RotationSpeed';
import { configureSwingMode } from './characteristic/SwingMode';
import { configureLockPhysicalControls } from './characteristic/LockPhysicalControls';
import { configureRelativeHumidityDehumidifierThreshold } from './characteristic/RelativeHumidityDehumidifierThreshold';

const SCHEMA_CODE = {
  ACTIVE: ['switch'],
  CURRENT_HUMIDITY: ['humidity_indoor'],
  TARGET_HUMIDITY: ['dehumidify_set_value'],
  CURRENT_TEMP: ['temp_indoor'],
  SPEED_LEVEL: ['fan_speed_enum'],
  SWING: ['swing'],
  LOCK: ['child_lock'],
};

export default class DehumidifierAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.ACTIVE, SCHEMA_CODE.CURRENT_HUMIDITY];
  }

  configureServices() {
    // Required Characteristics
    configureActive(this, this.mainService(), this.getSchema(...SCHEMA_CODE.ACTIVE));
    this.configureCurrentState();
    this.configureTargetState();
    configureCurrentRelativeHumidity(this, this.mainService(), this.getSchema(...SCHEMA_CODE.CURRENT_HUMIDITY));

    // Optional Characteristics
    configureLockPhysicalControls(this, this.mainService(), this.getSchema(...SCHEMA_CODE.LOCK));
    configureRelativeHumidityDehumidifierThreshold(this, this.mainService(), this.getSchema(...SCHEMA_CODE.TARGET_HUMIDITY));
    configureRotationSpeedLevel(this, this.mainService(), this.getSchema(...SCHEMA_CODE.SPEED_LEVEL));
    configureSwingMode(this, this.mainService(), this.getSchema(...SCHEMA_CODE.SWING));

    // Other
    configureCurrentTemperature(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_TEMP));
  }

  mainService() {
    return this.accessory.getService(this.Service.HumidifierDehumidifier)
      || this.accessory.addService(this.Service.HumidifierDehumidifier);
  }


  configureCurrentState() {
    const schema = this.getSchema(...SCHEMA_CODE.ACTIVE);
    if (!schema) {
      this.log.warn('CurrentHumidifierDehumidifierState not supported.');
      return;
    }

    const { INACTIVE, DEHUMIDIFYING } = this.Characteristic.CurrentHumidifierDehumidifierState;

    this.mainService().getCharacteristic(this.Characteristic.CurrentHumidifierDehumidifierState)
      .onGet(() => {
        const status = this.getStatus(schema.code);
        return (status?.value as boolean) ? DEHUMIDIFYING : INACTIVE;
      });
  }

  configureTargetState() {
    const { DEHUMIDIFIER } = this.Characteristic.TargetHumidifierDehumidifierState;
    const validValues = [DEHUMIDIFIER];

    this.mainService().getCharacteristic(this.Characteristic.TargetHumidifierDehumidifierState)
      .onGet(() => {
        return DEHUMIDIFIER;
      }).setProps({ validValues });
  }

}


================================================
FILE: src/accessory/DiffuserAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';
import { configureActive } from './characteristic/Active';
import { configureLight } from './characteristic/Light';
import { configureOn } from './characteristic/On';
import { configureRotationSpeedLevel } from './characteristic/RotationSpeed';

const SCHEMA_CODE = {
  ON: ['switch'],
  SPRAY_ON: ['switch_spray'],
  SPRAY_MODE: ['mode'],
  SPRAY_LEVEL: ['level'],
  LIGHT_ON: ['switch_led'],
  LIGHT_MODE: ['work_mode'],
  LIGHT_BRIGHT: ['bright_value', 'bright_value_v2'],
  LIGHT_COLOR: ['colour_data', 'colour_data_hsv'],
  SOUND_ON: ['switch_sound'],
};

export default class DiffuserAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.SPRAY_ON];
  }

  configureServices() {
    // Main Switch
    configureOn(this, undefined, this.getSchema(...SCHEMA_CODE.ON));

    this.configureDiffuser();

    configureLight(
      this,
      undefined,
      this.getSchema(...SCHEMA_CODE.LIGHT_ON),
      this.getSchema(...SCHEMA_CODE.LIGHT_BRIGHT),
      undefined,
      this.getSchema(...SCHEMA_CODE.LIGHT_COLOR),
      this.getSchema(...SCHEMA_CODE.LIGHT_MODE),
    );

    configureOn(this, undefined, this.getSchema(...SCHEMA_CODE.SOUND_ON)); // Sound Switch
  }

  mainService() {
    return this.accessory.getService(this.Service.AirPurifier)
      || this.accessory.addService(this.Service.AirPurifier);
  }

  configureDiffuser() {
    const sprayOnSchema = this.getSchema(...SCHEMA_CODE.SPRAY_ON)!;

    // Required Characteristics
    configureActive(this, this.mainService(), sprayOnSchema);

    const { INACTIVE, PURIFYING_AIR } = this.Characteristic.CurrentAirPurifierState;
    this.mainService().getCharacteristic(this.Characteristic.CurrentAirPurifierState)
      .onGet(() => {
        const status = this.getStatus(sprayOnSchema.code)!;
        return (status.value as boolean) ? PURIFYING_AIR : INACTIVE;
      });

    // const { MANUAL } = this.Characteristic.TargetAirPurifierState;
    // this.mainService().getCharacteristic(this.Characteristic.TargetAirPurifierState)
    //   .setProps({ validValues: [MANUAL] });


    // Optional Characteristics
    configureRotationSpeedLevel(this, this.mainService(), this.getSchema(...SCHEMA_CODE.SPRAY_LEVEL));
  }

}


================================================
FILE: src/accessory/DimmerAccessory.ts
================================================
import { Service } from 'homebridge';
import { TuyaDeviceSchemaIntegerProperty, TuyaDeviceStatus } from '../device/TuyaDevice';
import { remap, limit } from '../util/util';
import BaseAccessory from './BaseAccessory';
import { configureName } from './characteristic/Name';
import { configureOn } from './characteristic/On';

const SCHEMA_CODE = {
  ON: ['switch', 'switch_led', 'switch_1', 'switch_led_1'],
  BRIGHTNESS: ['bright_value', 'bright_value_1'],
};

export default class DimmerAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.ON, SCHEMA_CODE.BRIGHTNESS];
  }

  configureServices() {

    const oldService = this.accessory.getService(this.Service.Lightbulb);
    if (oldService && oldService?.subtype === undefined) {
      this.platform.log.warn('Remove old service:', oldService.UUID);
      this.accessory.removeService(oldService);
    }

    const schema = this.device.schema.filter((schema) => schema.code.startsWith('bright_value'));
    for (const _schema of schema) {
      const suffix = _schema.code.replace('bright_value', '');
      const name = (schema.length === 1) ? this.device.name : _schema.code;

      const service = this.accessory.getService(_schema.code)
        || this.accessory.addService(this.Service.Lightbulb, name, _schema.code);

      configureName(this, service, name);
      configureOn(this, service, this.getSchema('switch' + suffix, 'switch_led' + suffix));
      this.configureBrightness(service, suffix);
    }
  }


  configureBrightness(service: Service, suffix: string) {
    const schema = this.getSchema('bright_value' + suffix);
    if (!schema) {
      return;
    }

    const { min, max } = schema.property as TuyaDeviceSchemaIntegerProperty;
    const range = max; // not max - min
    const props = {
      minValue: 0,
      maxValue: 100,
      minStep: 1,
    };

    const minStatus = this.getStatus('brightness_min' + suffix);
    const maxStatus = this.getStatus('brightness_max' + suffix);
    if (minStatus && maxStatus && maxStatus.value > minStatus.value) {
      const minValue = Math.ceil(remap(minStatus.value as number, 0, range, 0, 100));
      const maxValue = Math.floor(remap(maxStatus.value as number, 0, range, 0, 100));
      props.minValue = Math.max(props.minValue, minValue);
      props.maxValue = Math.min(props.maxValue, maxValue);
    }
    this.log.debug('Set props for Brightness:', props);

    service.getCharacteristic(this.Characteristic.Brightness)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        let value = status.value as number;
        value = remap(value, 0, range, 0, 100);
        value = Math.round(value);
        value = limit(value, props.minValue, props.maxValue);
        return value;
      })
      .onSet(async value => {
        this.log.debug(`Characteristic.Brightness set to: ${value}`);
        let brightValue = value as number;
        brightValue = remap(brightValue, 0, 100, 0, range);
        brightValue = Math.round(brightValue);
        brightValue = limit(brightValue, min, max);
        await this.sendCommands([{ code: schema.code, value: brightValue }], true);
      })
      .setProps(props);

  }

  async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {

    // brightness range updated
    if (status.length !== this.device.status.length) {
      for (const _status of status) {
        if (!_status.code.startsWith('brightness_min')
          && !_status.code.startsWith('brightness_max')) {
          continue;
        }

        this.platform.log.warn('Brightness range updated, please restart homebridge to take effect.');
        // TODO updating props
        // this.platform.log.debug('Brightness range updated, resetting props...');
        // this.configure();
        break;
      }
    }

    super.onDeviceStatusUpdate(status);
  }

}


================================================
FILE: src/accessory/DoorbellAccessory.ts
================================================
import { TuyaDeviceStatus } from '../device/TuyaDevice';
import BaseAccessory from './BaseAccessory';
import { configureProgrammableSwitchEvent, onProgrammableSwitchEvent } from './characteristic/ProgrammableSwitchEvent';

const SCHEMA_CODE = {
  // ALARM_MESSAGE: ['alarm_message'],
  // ALARM_SWITCH: ['alarm_propel_switch'],
  // VOLUME: ['doorbell_volume_value'],
  DOORBELL_CALL: ['doorbell_call'],
};

export default class DoorbellAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.DOORBELL_CALL];
  }

  configureServices() {
    this.log.warn('HomeKit Doorbell service does not work without camera anymore.');
    this.log.warn('Downgrade to StatelessProgrammableSwitch. "Mute" and "Volume" not available.');
    configureProgrammableSwitchEvent(this, this.getDoorbellService(), this.getSchema(...SCHEMA_CODE.DOORBELL_CALL));
    // this.configureMute();
    // this.configureVolume();
  }

  /*
  configureMute() {
    const schema = this.getSchema(...SCHEMA_CODE.ALARM_SWITCH);
    if (!schema) {
      return;
    }

    this.getDoorbellService().getCharacteristic(this.Characteristic.Mute)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        const value = !(status.value as boolean);
        return value;
      })
      .onSet(async value => {
        const mute = !(value as boolean);
        await this.sendCommands([{ code: schema.code, value: mute }], true);
      });
  }

  configureVolume() {
    const schema = this.getSchema(...SCHEMA_CODE.VOLUME);
    if (!schema) {
      return;
    }

    const property = schema.property as TuyaDeviceSchemaIntegerProperty;
    const multiple = Math.pow(10, property.scale);
    const props = {
      minValue: property.min / multiple,
      maxValue: property.max / multiple,
      minStep: Math.max(1, property.step / multiple),
    };
    this.getDoorbellService().getCharacteristic(this.Characteristic.Volume)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        const value = status.value as number / multiple;
        return value;
      })
      .onSet(async value => {
        const volume = (value as number) * multiple;
        await this.sendCommands([{ code: schema.code, value: volume }], true);
      })
      .setProps(props);
  }

  getDoorbellService() {
    return this.accessory.getService(this.Service.Doorbell)
      || this.accessory.addService(this.Service.Doorbell);
  }
  */

  getDoorbellService() {
    return this.accessory.getService(this.Service.StatelessProgrammableSwitch)
      || this.accessory.addService(this.Service.StatelessProgrammableSwitch);
  }

  async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {
    super.onDeviceStatusUpdate(status);

    const doorbellCallSchema = this.getSchema(...SCHEMA_CODE.DOORBELL_CALL);
    if (doorbellCallSchema) {
      const doorbellCallStatus = status.find(_status => _status.code === doorbellCallSchema.code);
      doorbellCallStatus && onProgrammableSwitchEvent(this, this.getDoorbellService(), doorbellCallStatus);
    }
  }

}


================================================
FILE: src/accessory/ExtractionHoodAccessory.ts
================================================
import { TuyaDeviceSchemaType } from '../device/TuyaDevice';
import BaseAccessory from './BaseAccessory';
import { configureActive } from './characteristic/Active';
import { configureLockPhysicalControls } from './characteristic/LockPhysicalControls';
import { configureRotationSpeed, configureRotationSpeedLevel } from './characteristic/RotationSpeed';
import {configureLight} from './characteristic/Light';
import {configureOn} from './characteristic/On';

const SCHEMA_CODE = {
  ACTIVE: ['switch'],
  MODE: ['mode'],
  LOCK: ['lock'],
  SPEED: ['speed'],
  SPEED_LEVEL: ['fan_speed_enum', 'speed'],
  AIR_QUALITY: ['air_quality', 'pm25'],
  PM2_5: ['pm25'],
  VOC: ['tvoc'],
  LIGHT_ON: ['light', 'switch_led'],
  LIGHT_MODE: ['work_mode'],
  LIGHT_BRIGHT: ['bright_value', 'bright_value_v2'],
  LIGHT_TEMP: ['temp_value', 'temp_value_v2'],
  LIGHT_COLOR: ['colour_data'],
};

export default class ExtractionHoodAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.ACTIVE];
  }

  configureServices() {
    configureActive(this, this.mainService(), this.getSchema(...SCHEMA_CODE.ACTIVE));
    this.configureCurrentState();
    this.configureTargetState();
    configureLockPhysicalControls(this, this.mainService(), this.getSchema(...SCHEMA_CODE.LOCK));
    if (this.getFanSpeedSchema()) {
      configureRotationSpeed(this, this.mainService(), this.getFanSpeedSchema());
    } else if (this.getFanSpeedLevelSchema()) {
      configureRotationSpeedLevel(this, this.mainService(), this.getFanSpeedLevelSchema());
    }

    // Light
    if (this.getSchema(...SCHEMA_CODE.LIGHT_ON)) {
      if (this.lightServiceType() === this.Service.Lightbulb) {
        configureLight(
          this,
          this.lightService(),
          this.getSchema(...SCHEMA_CODE.LIGHT_ON),
          this.getSchema(...SCHEMA_CODE.LIGHT_BRIGHT),
          this.getSchema(...SCHEMA_CODE.LIGHT_TEMP),
          this.getSchema(...SCHEMA_CODE.LIGHT_COLOR),
          this.getSchema(...SCHEMA_CODE.LIGHT_MODE),
        );
      } else if (this.lightServiceType() === this.Service.Switch) {
        configureOn(this, undefined, this.getSchema(...SCHEMA_CODE.LIGHT_ON));
        const unusedService = this.accessory.getService(this.Service.Lightbulb);
        unusedService && this.accessory.removeService(unusedService);
      }
    }
  }


  mainService() {
    return this.accessory.getService(this.Service.AirPurifier)
      || this.accessory.addService(this.Service.AirPurifier);
  }

  getFanSpeedSchema() {
    const schema = this.getSchema(...SCHEMA_CODE.SPEED);
    if (schema && schema.type === TuyaDeviceSchemaType.Integer) {
      return schema;
    }
    return undefined;
  }

  getFanSpeedLevelSchema() {
    const schema = this.getSchema(...SCHEMA_CODE.SPEED_LEVEL);
    if (schema && schema.type === TuyaDeviceSchemaType.Enum) {
      return schema;
    }
    return undefined;
  }


  configureCurrentState() {
    const schema = this.getSchema(...SCHEMA_CODE.ACTIVE);
    if (!schema) {
      return;
    }

    const { INACTIVE, PURIFYING_AIR } = this.Characteristic.CurrentAirPurifierState;
    this.mainService().getCharacteristic(this.Characteristic.CurrentAirPurifierState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return status.value as boolean ? PURIFYING_AIR : INACTIVE;
      });
  }

  configureTargetState() {
    const schema = this.getSchema(...SCHEMA_CODE.MODE);
    if (!schema) {
      return;
    }

    const { MANUAL, AUTO } = this.Characteristic.TargetAirPurifierState;
    this.mainService().getCharacteristic(this.Characteristic.TargetAirPurifierState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return (status.value === 'auto') ? AUTO : MANUAL;
      })
      .onSet(async value => {
        await this.sendCommands([{
          code: schema.code,
          value: (value === AUTO) ? 'auto' : 'manual',
        }], true);
      });
  }

  lightServiceType() {
    if (this.getSchema(...SCHEMA_CODE.LIGHT_BRIGHT)
        || this.getSchema(...SCHEMA_CODE.LIGHT_TEMP)
        || this.getSchema(...SCHEMA_CODE.LIGHT_COLOR)
        || this.getSchema(...SCHEMA_CODE.LIGHT_MODE)) {
      return this.Service.Lightbulb;
    }
    return this.Service.Switch;
  }

  lightService() {
    return this.accessory.getService(this.Service.Lightbulb)
        || this.accessory.addService(this.Service.Lightbulb);
  }

}


================================================
FILE: src/accessory/FanAccessory.ts
================================================
import { TuyaDeviceSchemaType } from '../device/TuyaDevice';
import BaseAccessory from './BaseAccessory';
import { configureActive } from './characteristic/Active';
import { configureLight } from './characteristic/Light';
import { configureLockPhysicalControls } from './characteristic/LockPhysicalControls';
import { configureOn } from './characteristic/On';
import { configureRotationSpeed, configureRotationSpeedLevel, configureRotationSpeedOn } from './characteristic/RotationSpeed';
import { configureSwingMode } from './characteristic/SwingMode';

const SCHEMA_CODE = {
  FAN_ON: ['switch_fan', 'fan_switch', 'switch'],
  FAN_DIRECTION: ['fan_direction'],
  FAN_SPEED: ['fan_speed', 'fan_speed_percent'],
  FAN_SPEED_LEVEL: ['fan_speed_enum', 'fan_speed'],
  FAN_LOCK: ['child_lock'],
  FAN_SWING: ['switch_horizontal', 'switch_vertical'],
  LIGHT_ON: ['light', 'switch_led'],
  LIGHT_MODE: ['work_mode'],
  LIGHT_BRIGHT: ['bright_value', 'bright_value_v2'],
  LIGHT_TEMP: ['temp_value', 'temp_value_v2'],
  LIGHT_COLOR: ['colour_data'],
};

export default class FanAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.FAN_ON];
  }

  configureServices() {

    if (this.fanServiceType() === this.Service.Fan) {
      const unusedService = this.accessory.getService(this.Service.Fanv2);
      unusedService && this.accessory.removeService(unusedService);

      configureOn(this, this.fanService(), this.getSchema(...SCHEMA_CODE.FAN_ON));
    } else if (this.fanServiceType() === this.Service.Fanv2) {
      const unusedService = this.accessory.getService(this.Service.Fan);
      unusedService && this.accessory.removeService(unusedService);

      configureActive(this, this.fanService(), this.getSchema(...SCHEMA_CODE.FAN_ON));
      configureLockPhysicalControls(this, this.fanService(), this.getSchema(...SCHEMA_CODE.FAN_LOCK));
      configureSwingMode(this, this.fanService(), this.getSchema(...SCHEMA_CODE.FAN_SWING));
    }

    // Common Characteristics
    if (this.getFanSpeedSchema()) {
      configureRotationSpeed(this, this.fanService(), this.getFanSpeedSchema());
    } else if (this.getFanSpeedLevelSchema()) {
      configureRotationSpeedLevel(this, this.fanService(), this.getFanSpeedLevelSchema());
    } else {
      configureRotationSpeedOn(this, this.fanService(), this.getSchema(...SCHEMA_CODE.FAN_ON));
    }

    this.configureRotationDirection();

    // Dual-light: two independent light channels (warm + white)
    const warmOn = this.getSchema('light');
    const warmBright = this.getSchema('bright_value');
    const coldOn = this.getSchema('switch_led');
    const coldBright = this.getSchema('bright_value_1');

    if (warmOn && warmBright && coldOn && coldBright) {
      const warmService = this.accessory.getService('Warm Light')
        || this.accessory.addService(this.Service.Lightbulb, 'Warm Light', 'warm_light');
      configureLight(this, warmService, warmOn, warmBright);

      const whiteService = this.accessory.getService('White Light')
        || this.accessory.addService(this.Service.Lightbulb, 'White Light', 'white_light');
      configureLight(this, whiteService, coldOn, coldBright);
    } else if (this.getSchema(...SCHEMA_CODE.LIGHT_ON)) {
      if (this.lightServiceType() === this.Service.Lightbulb) {
        configureLight(
          this,
          this.lightService(),
          this.getSchema(...SCHEMA_CODE.LIGHT_ON),
          this.getSchema(...SCHEMA_CODE.LIGHT_BRIGHT),
          this.getSchema(...SCHEMA_CODE.LIGHT_TEMP),
          this.getSchema(...SCHEMA_CODE.LIGHT_COLOR),
          this.getSchema(...SCHEMA_CODE.LIGHT_MODE),
        );
      } else if (this.lightServiceType() === this.Service.Switch) {
        configureOn(this, undefined, this.getSchema(...SCHEMA_CODE.LIGHT_ON));
        const unusedService = this.accessory.getService(this.Service.Lightbulb);
        unusedService && this.accessory.removeService(unusedService);
      }
    }
  }

  fanServiceType() {
    if (this.getSchema(...SCHEMA_CODE.FAN_LOCK)
      || this.getSchema(...SCHEMA_CODE.FAN_SWING)) {
      return this.Service.Fanv2;
    }
    return this.Service.Fan;
  }

  fanService() {
    const serviceType = this.fanServiceType();
    return this.accessory.getService(serviceType)
      || this.accessory.addService(serviceType);
  }

  lightServiceType() {
    if (this.getSchema(...SCHEMA_CODE.LIGHT_BRIGHT)
      || this.getSchema(...SCHEMA_CODE.LIGHT_TEMP)
      || this.getSchema(...SCHEMA_CODE.LIGHT_COLOR)
      || this.getSchema(...SCHEMA_CODE.LIGHT_MODE)) {
      return this.Service.Lightbulb;
    }
    return this.Service.Switch;
  }

  lightService() {
    return this.accessory.getService(this.Service.Lightbulb)
    || this.accessory.addService(this.Service.Lightbulb);
  }


  getFanSpeedSchema() {
    const schema = this.getSchema(...SCHEMA_CODE.FAN_SPEED);
    if (schema && schema.type === TuyaDeviceSchemaType.Integer) {
      return schema;
    }
    return undefined;
  }

  getFanSpeedLevelSchema() {
    const schema = this.getSchema(...SCHEMA_CODE.FAN_SPEED_LEVEL);
    if (schema && schema.type === TuyaDeviceSchemaType.Enum) {
      return schema;
    }
    return undefined;
  }

  configureRotationDirection() {
    const schema = this.getSchema(...SCHEMA_CODE.FAN_DIRECTION);
    if (!schema) {
      return;
    }

    const { CLOCKWISE, COUNTER_CLOCKWISE } = this.Characteristic.RotationDirection;
    this.fanService().getCharacteristic(this.Characteristic.RotationDirection)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return (status.value !== 'reverse') ? CLOCKWISE : COUNTER_CLOCKWISE;
      })
      .onSet(async value => {
        await this.sendCommands([{ code: schema.code, value: (value === CLOCKWISE) ? 'forward' : 'reverse' }]);
      });
  }

}


================================================
FILE: src/accessory/GarageDoorAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';

const SCHEMA_CODE = {
  CURRENT_DOOR_STATE: ['doorcontact_state'],
  TARGET_DOOR_STATE: ['switch_1'],
};

export default class GarageDoorAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.TARGET_DOOR_STATE];
  }

  configureServices() {

    this.configureCurrentDoorState();
    this.configureTargetDoorState();
  }


  mainService() {
    return this.accessory.getService(this.Service.GarageDoorOpener)
      || this.accessory.addService(this.Service.GarageDoorOpener);
  }

  configureCurrentDoorState() {
    const { OPEN, CLOSED, OPENING, CLOSING, STOPPED } = this.Characteristic.CurrentDoorState;
    this.mainService().getCharacteristic(this.Characteristic.CurrentDoorState)
      .onGet(() => {
        const currentSchema = this.getSchema(...SCHEMA_CODE.CURRENT_DOOR_STATE);
        const targetSchema = this.getSchema(...SCHEMA_CODE.TARGET_DOOR_STATE);
        if (!currentSchema || !targetSchema) {
          return STOPPED;
        }

        const currentStatus = this.getStatus(currentSchema.code)!;
        const targetStatus = this.getStatus(targetSchema.code)!;
        if (currentStatus.value === true && targetStatus.value === true) {
          return OPEN;
        } else if (currentStatus.value === false && targetStatus.value === false) {
          return CLOSED;
        } else if (currentStatus.value === false && targetStatus.value === true) {
          return OPENING;
        } else if (currentStatus.value === true && targetStatus.value === false) {
          return CLOSING;
        }

        return STOPPED;
      });
  }

  configureTargetDoorState() {
    const schema = this.getSchema(...SCHEMA_CODE.TARGET_DOOR_STATE);
    if (!schema) {
      return;
    }

    const { OPEN, CLOSED } = this.Characteristic.TargetDoorState;
    this.mainService().getCharacteristic(this.Characteristic.TargetDoorState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return status.value as boolean ? OPEN : CLOSED;
      })
      .onSet(async value => {
        await this.sendCommands([{
          code: schema.code,
          value: (value === OPEN) ? true : false,
        }]);
      });
  }
}


================================================
FILE: src/accessory/HeaterAccessory.ts
================================================
/* eslint-disable @typescript-eslint/no-unused-vars */
import { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDevice';
import { limit } from '../util/util';
import BaseAccessory from './BaseAccessory';
import { configureActive } from './characteristic/Active';
import { configureCurrentTemperature } from './characteristic/CurrentTemperature';
import { configureLockPhysicalControls } from './characteristic/LockPhysicalControls';
import { configureSwingMode } from './characteristic/SwingMode';
import { configureTempDisplayUnits } from './characteristic/TemperatureDisplayUnits';

const SCHEMA_CODE = {
  ACTIVE: ['switch'],
  WORK_STATE: ['work_state'],
  CURRENT_TEMP: ['temp_current'],
  TARGET_TEMP: ['temp_set'],
  LOCK: ['lock'],
  SWING: ['shake'],
  TEMP_UNIT_CONVERT: ['temp_unit_convert', 'c_f'],
};

export default class HeaterAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.ACTIVE];
  }

  configureServices() {
    configureActive(this, this.mainService(), this.getSchema(...SCHEMA_CODE.ACTIVE));
    this.configureCurrentState();
    this.configureTargetState();
    configureCurrentTemperature(this, this.mainService(), this.getSchema(...SCHEMA_CODE.CURRENT_TEMP));
    configureLockPhysicalControls(this, this.mainService(), this.getSchema(...SCHEMA_CODE.LOCK));
    configureSwingMode(this, this.mainService(), this.getSchema(...SCHEMA_CODE.SWING));
    this.configureHeatingThreshouldTemp();
    configureTempDisplayUnits(this, this.mainService(), this.getSchema(...SCHEMA_CODE.TEMP_UNIT_CONVERT));
  }


  mainService() {
    return this.accessory.getService(this.Service.HeaterCooler)
      || this.accessory.addService(this.Service.HeaterCooler);
  }

  configureCurrentState() {
    const schema = this.getSchema(...SCHEMA_CODE.WORK_STATE);
    const { INACTIVE, IDLE, HEATING } = this.Characteristic.CurrentHeaterCoolerState;
    this.mainService().getCharacteristic(this.Characteristic.CurrentHeaterCoolerState)
      .onGet(() => {
        if (!schema) {
          return IDLE;
        }
        const status = this.getStatus(schema.code)!;
        if (status.value === 'heating') {
          return HEATING;
        } else if (status.value === 'warming') {
          return IDLE;
        }

        return INACTIVE;
      });
  }

  configureTargetState() {
    const { AUTO, HEAT, COOL } = this.Characteristic.TargetHeaterCoolerState;
    const validValues = [ AUTO ];
    this.mainService().getCharacteristic(this.Characteristic.TargetHeaterCoolerState)
      .onGet(() => {
        return AUTO;
      })
      .onSet(async value => {
        // TODO
      })
      .setProps({ validValues });
  }

  configureHeatingThreshouldTemp() {
    const schema = this.getSchema(...SCHEMA_CODE.TARGET_TEMP);
    if (!schema) {
      return;
    }

    const property = schema.property as TuyaDeviceSchemaIntegerProperty;
    const multiple = property ? Math.pow(10, property.scale) : 1;
    const props = {
      minValue: property.min / multiple,
      maxValue: property.max / multiple,
      minStep: Math.max(0.1, property.step / multiple),
    };
    this.log.debug('Set props for HeatingThresholdTemperature:', props);

    this.mainService().getCharacteristic(this.Characteristic.HeatingThresholdTemperature)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        const temp = status.value as number / multiple;
        return limit(temp, props.minValue, props.maxValue);
      })
      .onSet(async value => {
        await this.sendCommands([{ code: schema.code, value: (value as number) * multiple}]);
      })
      .setProps(props);
  }

}


================================================
FILE: src/accessory/HumanPresenceSensorAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';
import { configureOccupancyDetected } from './characteristic/OccupancyDetected';

const SCHEMA_CODE = {
  PRESENCE: ['presence_state'],
};

export default class HumanPresenceSensorAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.PRESENCE];
  }

  configureServices() {
    configureOccupancyDetected(this, undefined, this.getSchema(...SCHEMA_CODE.PRESENCE));
  }

}


================================================
FILE: src/accessory/HumidifierAccessory.ts
================================================
import { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDevice';
import { limit, remap } from '../util/util';
import BaseAccessory from './BaseAccessory';
import { configureActive } from './characteristic/Active';
import { configureCurrentTemperature } from './characteristic/CurrentTemperature';
import { configureCurrentRelativeHumidity } from './characteristic/CurrentRelativeHumidity';
import { configureLight } from './characteristic/Light';

const SCHEMA_CODE = {
  ACTIVE: ['switch'],
  CURRENT_HUMIDITY: ['humidity_current'],
  TARGET_HUMIDITY: ['humidity_set'],
  CURRENT_TEMP: ['temp_current'],
  LIGHT_ON: ['switch_led'],
  LIGHT_MODE: ['work_mode'],
  LIGHT_BRIGHT: ['bright_value', 'bright_value_v2'],
  LIGHT_COLOR: ['colour_data', 'colour_data_hsv'],
};

export default class HumidifierAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.ACTIVE];
  }

  configureServices() {
    // Required Characteristics
    configureActive(this, this.mainService(), this.getSchema(...SCHEMA_CODE.ACTIVE));
    this.configureCurrentState();
    this.configureTargetState();
    configureCurrentRelativeHumidity(this, this.mainService(), this.getSchema(...SCHEMA_CODE.CURRENT_HUMIDITY));

    // Optional Characteristics
    this.configureRelativeHumidityHumidifierThreshold();
    this.configureRotationSpeed();

    // Other
    configureCurrentTemperature(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_TEMP));
    configureLight(
      this,
      undefined,
      this.getSchema(...SCHEMA_CODE.LIGHT_ON),
      this.getSchema(...SCHEMA_CODE.LIGHT_BRIGHT),
      undefined,
      this.getSchema(...SCHEMA_CODE.LIGHT_COLOR),
      this.getSchema(...SCHEMA_CODE.LIGHT_MODE),
    );
  }


  mainService() {
    return this.accessory.getService(this.Service.HumidifierDehumidifier)
      || this.accessory.addService(this.Service.HumidifierDehumidifier);
  }


  configureTargetState() {
    const { HUMIDIFIER } = this.Characteristic.TargetHumidifierDehumidifierState;
    const validValues = [HUMIDIFIER];

    this.mainService().getCharacteristic(this.Characteristic.TargetHumidifierDehumidifierState)
      .onGet(() => {
        return HUMIDIFIER;
      }).setProps({ validValues });
  }

  configureCurrentState() {
    const schema = this.getSchema(...SCHEMA_CODE.ACTIVE);
    if (!schema) {
      this.log.warn('CurrentHumidifierDehumidifierState not supported.');
      return;
    }

    const { INACTIVE, HUMIDIFYING } = this.Characteristic.CurrentHumidifierDehumidifierState;

    this.mainService().getCharacteristic(this.Characteristic.CurrentHumidifierDehumidifierState)
      .onGet(() => {
        const status = this.getStatus(schema.code);
        return (status?.value as boolean) ? HUMIDIFYING : INACTIVE;
      });
  }

  configureRelativeHumidityHumidifierThreshold() {
    const schema = this.getSchema(...SCHEMA_CODE.TARGET_HUMIDITY);
    if (!schema) {
      this.log.warn('Humidity setting is not supported.');
      return;
    }

    const property = schema.property as TuyaDeviceSchemaIntegerProperty;
    const multiple = Math.pow(10, property ? property.scale : 0);
    const props = {
      minValue: 0,
      maxValue: 100,
      minStep: Math.max(1, property.step / multiple),
    };
    this.log.debug('Set props for RelativeHumidityHumidifierThreshold:', props);

    this.mainService().getCharacteristic(this.Characteristic.RelativeHumidityHumidifierThreshold)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return limit(status.value as number / multiple, 0, 100);
      })
      .onSet(async value => {
        const humidity_set = limit(value as number * multiple, property.min, property.max);
        await this.sendCommands([{ code: schema.code, value: humidity_set }]);
        // also set spray mode to humidity
        await this.setSprayModeToHumidity();
      }).setProps(props);
  }


  configureRotationSpeed() {
    const schema = this.getSchema('mode');
    if (!schema) {
      this.log.warn('Mode setting is not supported.');
      return;
    }

    const unusedService = this.accessory.getService(this.Service.Fan);
    unusedService && this.accessory.removeService(unusedService);

    this.mainService().getCharacteristic(this.Characteristic.RotationSpeed)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        let v = 3;
        switch (status.value as string) {
          case 'small':
            v = 1;
            break;
          case 'middle':
            v = 2;
            break;
        }
        return remap(v, 0, 3, 0, 100);
      }).onSet(async value => {
        value = Math.round(remap(value as number, 0, 100, 0, 3));
        let mode = 'small';
        switch (value) {
          case 2:
            mode = 'middle';
            break;
          case 3:
            mode = 'large';
            break;
        }
        await this.sendCommands([{ code: schema.code, value: mode }]);
      });
  }

  async setSprayModeToHumidity() {
    const schema = this.getSchema('spray_mode');
    if (!schema) {
      this.log.debug('Spray mode not supported.');
      return;
    }
    await this.sendCommands([{ code: schema.code, value: 'humidity' }]);
  }

}


================================================
FILE: src/accessory/IRAirConditionerAccessory.ts
================================================
import debounce from 'debounce';
import BaseAccessory from './BaseAccessory';

const POWER_OFF = 0;
const POWER_ON = 1;

const AC_MODE_COOL = 0;
const AC_MODE_HEAT = 1;
const AC_MODE_AUTO = 2;
const AC_MODE_FAN = 3;
const AC_MODE_DEHUMIDIFIER = 4;

const FAN_SPEED_AUTO = 0;
const FAN_SPEED_LOW = 1;
// const FAN_SPEED_MEDIUM = 2;
const FAN_SPEED_HIGH = 3;

export default class IRAirConditionerAccessory extends BaseAccessory {

  configureServices() {
    this.configureAirConditioner();
    this.configureDehumidifier();
    this.configureFan();
  }

  configureAirConditioner() {

    const service = this.mainService();
    const { INACTIVE, ACTIVE } = this.Characteristic.Active;

    // Required Characteristics
    service.getCharacteristic(this.Characteristic.Active)
      .onGet(() => {
        return ([AC_MODE_COOL, AC_MODE_HEAT, AC_MODE_AUTO].includes(this.getMode()) && this.getPower() === POWER_ON) ? ACTIVE : INACTIVE;
      })
      .onSet(async value => {
        if (value === ACTIVE) {
          // Turn off Dehumidifier & Fan
          this.supportDehumidifier() && this.dehumidifierService().getCharacteristic(this.Characteristic.Active).updateValue(INACTIVE);
          this.supportFan() && this.fanService().getCharacteristic(this.Characteristic.Active).updateValue(INACTIVE);
          this.fanService().getCharacteristic(this.Characteristic.Active).value = INACTIVE;
        }

        if (value === ACTIVE && ![AC_MODE_COOL, AC_MODE_HEAT, AC_MODE_AUTO].includes(this.getMode())) {
          this.setMode(AC_MODE_AUTO);
        }
        this.setPower((value === ACTIVE) ? POWER_ON : POWER_OFF);
      });

    const { IDLE } = this.Characteristic.CurrentHeaterCoolerState;
    service.setCharacteristic(this.Characteristic.CurrentHeaterCoolerState, IDLE);

    this.configureTargetState();
    this.configureCurrentTemperature();

    // Optional Characteristics
    this.configureRotationSpeed(service);

    const key_range = this.device.remote_keys?.key_range || [];
    if (key_range.find(item => item.mode === AC_MODE_HEAT)) {
      const [minValue, maxValue] = this.getTempRange(AC_MODE_HEAT)!;
      service.getCharacteristic(this.Characteristic.HeatingThresholdTemperature)
        .onGet(() => {
          if (this.getMode() === AC_MODE_AUTO) {
            return minValue;
          }
          return this.getTemp();
        })
        .onSet(async value => {
          if (this.getMode() === AC_MODE_AUTO) {
            return;
          }
          this.setTemp(value);
        })
        .setProps({ minValue, maxValue, minStep: 1 });
    }
    if (key_range.find(item => item.mode === AC_MODE_COOL)) {
      const [minValue, maxValue] = this.getTempRange(AC_MODE_COOL)!;
      service.getCharacteristic(this.Characteristic.CoolingThresholdTemperature)
        .onGet(this.getTemp.bind(this))
        .onSet(this.setTemp.bind(this))
        .setProps({ minValue, maxValue, minStep: 1 });
    }
  }

  configureDehumidifier() {
    if (!this.supportDehumidifier()) {
      return;
    }

    const service = this.dehumidifierService();
    const { INACTIVE, ACTIVE } = this.Characteristic.Active;

    // Required Characteristics
    service.getCharacteristic(this.Characteristic.Active)
      .onGet(() => {
        return (this.getMode() === AC_MODE_DEHUMIDIFIER && this.getPower() === POWER_ON) ? ACTIVE : INACTIVE;
      })
      .onSet(async value => {
        if (value === ACTIVE) {
          // Turn off AC & Fan
          this.mainService().getCharacteristic(this.Characteristic.Active).updateValue(INACTIVE);
          this.supportFan() && this.fanService().getCharacteristic(this.Characteristic.Active).updateValue(INACTIVE);
        }

        this.setMode(AC_MODE_DEHUMIDIFIER);
        this.setPower((value === ACTIVE) ? POWER_ON : POWER_OFF);
      });

    const { DEHUMIDIFYING } = this.Characteristic.CurrentHumidifierDehumidifierState;
    service.setCharacteristic(this.Characteristic.CurrentHumidifierDehumidifierState, DEHUMIDIFYING);

    const { DEHUMIDIFIER } = this.Characteristic.TargetHumidifierDehumidifierState;
    service.getCharacteristic(this.Characteristic.TargetHumidifierDehumidifierState)
      .updateValue(DEHUMIDIFIER)
      .setProps({ validValues: [DEHUMIDIFIER] });

    service.getCharacteristic(this.Characteristic.CurrentRelativeHumidity)
      .onGet(() => {
        const handler = this.getParentAccessory().accessory
          .getService(this.Service.HumiditySensor)
          ?.getCharacteristic(this.Characteristic.CurrentRelativeHumidity)['getHandler'];
        const humidity = handler ? handler() : 0;
        return humidity;
      });

    // Optional Characteristics
    this.configureRotationSpeed(service);
  }

  configureFan() {
    if (!this.supportFan()) {
      return;
    }

    const service = this.fanService();
    const { INACTIVE, ACTIVE } = this.Characteristic.Active;

    // Required Characteristics
    service.getCharacteristic(this.Characteristic.Active)
      .onGet(() => {
        return (this.getMode() === AC_MODE_FAN && this.getPower() === POWER_ON) ? ACTIVE : INACTIVE;
      })
      .onSet(async value => {
        if (value === ACTIVE) {
          // Turn off AC & Dehumidifier
          this.mainService().getCharacteristic(this.Characteristic.Active).updateValue(INACTIVE);
          this.supportDehumidifier() && this.dehumidifierService().getCharacteristic(this.Characteristic.Active).updateValue(INACTIVE);
        }

        this.setMode(AC_MODE_FAN);
        this.setPower((value === ACTIVE) ? POWER_ON : POWER_OFF);
      });

    // Optional Characteristics
    this.configureTargetFanState(service);
    this.configureRotationSpeed(service);
  }

  mainService() {
    return this.accessory.getService(this.Service.HeaterCooler)
      || this.accessory.addService(this.Service.HeaterCooler);
  }

  dehumidifierService() {
    return this.accessory.getService(this.Service.HumidifierDehumidifier)
      || this.accessory.addService(this.Service.HumidifierDehumidifier, this.accessory.displayName + ' Dehumidifier');
  }

  fanService() {
    return this.accessory.getService(this.Service.Fanv2)
      || this.accessory.addService(this.Service.Fanv2, this.accessory.displayName + ' Fan');
  }

  getPower() {
    const value = this.getStatus('power')?.value || '0';
    return (value === true || parseInt(value.toString()) === 1) ? POWER_ON : POWER_OFF;
  }

  setPower(value) {
    this.getStatus('power')!.value = value;
    this.debounceSendACCommands();
  }

  getMode() {
    const value = this.getStatus('mode')?.value || '0';
    return parseInt(value.toString());
  }

  setMode(value) {
    this.getStatus('mode')!.value = value;
    this.debounceSendACCommands();
  }

  getWind() {
    const value = this.getStatus('wind')?.value || '0';
    return parseInt(value.toString());
  }

  setWind(value) {
    this.getStatus('wind')!.value = value;
    this.debounceSendACCommands();
  }

  getTemp() {
    const value = this.getStatus('temp')?.value || '0';
    return parseInt(value.toString());
  }

  setTemp(value) {
    this.getStatus('temp')!.value = value;
    this.debounceSendACCommands();
  }

  getKeyRangeItem(mode: number) {
    const key_range = this.device.remote_keys?.key_range || [];
    return key_range.find(item => item.mode === mode);
  }

  supportDehumidifier() {
    return this.getKeyRangeItem(AC_MODE_DEHUMIDIFIER) !== undefined;
  }

  supportFan() {
    return this.getKeyRangeItem(AC_MODE_FAN) !== undefined;
  }

  getTempRange(mode: number) {
    const keyRangeItem = this.getKeyRangeItem(mode);
    if (!keyRangeItem || !keyRangeItem.temp_list || keyRangeItem.temp_list.length === 0) {
      return undefined;
    }

    const tempList = keyRangeItem.temp_list.map((temp) => temp.temp);

    const min = Math.min(...tempList);
    const max = Math.max(...tempList);
    return [min, max];
  }

  getParentAccessory() {
    return this.platform.accessoryHandlers.find(accessory => accessory.device.id === this.device.parent_id)!;
  }

  configureTargetState() {
    const { AUTO, HEAT, COOL } = this.Characteristic.TargetHeaterCoolerState;

    const validValues: number[] = [];
    const key_range = this.device.remote_keys?.key_range || [];
    if (key_range.find(item => item.mode === AC_MODE_AUTO)) {
      validValues.push(AUTO);
    }
    if (key_range.find(item => item.mode === AC_MODE_HEAT)) {
      validValues.push(HEAT);
    }
    if (key_range.find(item => item.mode === AC_MODE_COOL)) {
      validValues.push(COOL);
    }

    if (validValues.length === 0) {
      this.log.warn('Invalid mode range for TargetHeaterCoolerState:', key_range);
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.TargetHeaterCoolerState)
      .onGet(() => ({
        [AC_MODE_COOL.toString()]: COOL,
        [AC_MODE_HEAT.toString()]: HEAT,
        [AC_MODE_AUTO.toString()]: AUTO,
      }[this.getMode().toString()] || AUTO))
      .onSet(async value => {
        this.setMode({
          [COOL.toString()]: AC_MODE_COOL,
          [HEAT.toString()]: AC_MODE_HEAT,
          [AUTO.toString()]: AC_MODE_AUTO,
        }[value.toString()]);
      })
      .setProps({ validValues });
  }

  configureCurrentTemperature() {
    this.mainService().getCharacteristic(this.Characteristic.CurrentTemperature)
      .onGet(() => {
        const handler = this.getParentAccessory().accessory
          .getService(this.Service.TemperatureSensor)
          ?.getCharacteristic(this.Characteristic.CurrentTemperature)['getHandler'];
        const temp = handler ? handler() : this.getTemp();
        return temp;
      });
  }

  configureTargetFanState(service) {
    const { MANUAL, AUTO } = this.Characteristic.TargetFanState;
    service.getCharacteristic(this.Characteristic.TargetFanState)
      .onGet(() => (this.getWind() === FAN_SPEED_AUTO) ? AUTO : MANUAL)
      .onSet(async value => {
        this.setWind((value === AUTO) ? FAN_SPEED_AUTO : FAN_SPEED_LOW);
      });
  }

  configureRotationSpeed(service) {
    service.getCharacteristic(this.Characteristic.RotationSpeed)
      .onGet(() => (this.getWind() === FAN_SPEED_AUTO) ? FAN_SPEED_HIGH : this.getWind())
      .onSet(async value => {
        // if (this.getWind() === FAN_SPEED_AUTO) {
        //   return;
        // }
        if (value !== 0) {
          this.setWind(value);
        }
      })
      .setProps({ minValue: 0, maxValue: 3, minStep: 1, unit: 'speed' });
  }

  debounceSendACCommands = debounce(this.sendACCommands, 100);

  async sendACCommands() {
    const { parent_id, id } = this.device;
    await this.deviceManager.sendInfraredACCommands(parent_id!, id, this.getPower(), this.getMode(), this.getTemp(), this.getWind());
  }
}


================================================
FILE: src/accessory/IRControlHubAccessory.ts
================================================
import { TuyaDeviceStatus } from '../device/TuyaDevice';
import BaseAccessory from './BaseAccessory';
import { configureCurrentRelativeHumidity } from './characteristic/CurrentRelativeHumidity';
import { configureCurrentTemperature } from './characteristic/CurrentTemperature';

const SCHEMA_CODE = {
  CURRENT_TEMP: ['va_temperature'],
  CURRENT_HUMIDITY: ['va_humidity', 'humidity_value'],
};

export default class IRControlHubAccessory extends BaseAccessory {

  requiredSchema() {
    return [];
  }

  configureServices() {
    configureCurrentTemperature(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_TEMP));
    configureCurrentRelativeHumidity(this, undefined, this.getSchema(...SCHEMA_CODE.CURRENT_HUMIDITY));
  }

  getSubAccessories() {
    return this.platform.accessoryHandlers.filter(accessory => accessory.device.parent_id === this.device.id);
  }

  async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {
    super.onDeviceStatusUpdate(status);

    // Trigger sub device update temperature & humidity from parent device.
    for (const subAccessory of this.getSubAccessories()) {
      await subAccessory.updateAllValues();
    }
  }
}


================================================
FILE: src/accessory/IRGenericAccessory.ts
================================================
import { TuyaIRRemoteKeyListItem } from '../device/TuyaDevice';
import BaseAccessory from './BaseAccessory';
import { configureName } from './characteristic/Name';

export default class IRGenericAccessory extends BaseAccessory {

  configureServices() {
    let key_list = this.device.remote_keys?.key_list || [];

    // Max 99 services allowed (one for AccessoryInformation)
    if (key_list.length > 99) {
      this.log.warn(`Skipping ${key_list.length - 99} keys for ${this.device.name}, ` +
        'as we reached the limit of HomeKit (100 services per accessory)');
    }
    key_list = key_list.slice(0, 99);

    for (const key of key_list) {
      this.configureSwitch(key);
    }
  }

  configureSwitch(key: TuyaIRRemoteKeyListItem) {
    const service = this.accessory.getService(key.key)
      || this.accessory.addService(this.Service.Switch, key.key, key.key);

    configureName(this, service, key.key_name);

    service.getCharacteristic(this.Characteristic.On)
      .onGet(() => false)
      .onSet(async value => {
        if (value === false) {
          return;
        }

        this.sendInfraredCommands(key);
        setTimeout(() => {
          service.getCharacteristic(this.Characteristic.On).updateValue(false);
        }, 150);

      });
  }

  async sendInfraredCommands(key: TuyaIRRemoteKeyListItem) {
    const { parent_id, id } = this.device;
    const { category_id, remote_index } = this.device.remote_keys!;
    if (key.learning_code) {
      await this.deviceManager.sendInfraredDIYCommands(parent_id!, id, key.learning_code);
    } else {
      await this.deviceManager.sendInfraredCommands(parent_id!, id, category_id, remote_index, key.key, key.key_id);
    }
  }

}


================================================
FILE: src/accessory/LeakSensorAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';

const SCHEMA_CODE = {
  LEAK: ['gas_sensor_status', 'gas_sensor_state', 'ch4_sensor_state', 'watersensor_state'],
};

export default class LeakSensor extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.LEAK];
  }

  configureServices() {
    const { LEAK_NOT_DETECTED, LEAK_DETECTED } = this.Characteristic.LeakDetected;
    const service = this.accessory.getService(this.Service.LeakSensor)
      || this.accessory.addService(this.Service.LeakSensor);

    service.getCharacteristic(this.Characteristic.LeakDetected)
      .onGet(() => {
        const gas = this.getStatus('gas_sensor_status')
          || this.getStatus('gas_sensor_state');
        const ch4 = this.getStatus('ch4_sensor_state');
        const water = this.getStatus('watersensor_state');

        if ((gas && (gas.value === 'alarm' || gas.value === '1'))
          || (ch4 && ch4.value === 'alarm')
          || (water && water.value === 'alarm')) {
          return LEAK_DETECTED;
        } else {
          return LEAK_NOT_DETECTED;
        }
      });

  }

}


================================================
FILE: src/accessory/LightAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';
import { configureOn } from './characteristic/On';
import { configureMotionDetected } from './characteristic/MotionDetected';
import { configureLight } from './characteristic/Light';

const SCHEMA_CODE = {
  ON: ['switch_led'],
  BRIGHTNESS: ['bright_value', 'bright_value_v2'],
  COLOR_TEMP: ['temp_value', 'temp_value_v2'],
  COLOR: ['colour_data', 'colour_data_v2'],
  WORK_MODE: ['work_mode'],
  PIR: ['pir_state'],
  PIR_ON: ['switch_pir'],
  POWER_SWITCH: ['switch'],
};

export default class LightAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.ON];
  }

  configureServices() {

    const service = this.accessory.getService(this.Service.Lightbulb)
      || this.accessory.addService(this.Service.Lightbulb);

    configureLight(
      this,
      service,
      this.getSchema(...SCHEMA_CODE.ON),
      this.getSchema(...SCHEMA_CODE.BRIGHTNESS),
      this.getSchema(...SCHEMA_CODE.COLOR_TEMP),
      this.getSchema(...SCHEMA_CODE.COLOR),
      this.getSchema(...SCHEMA_CODE.WORK_MODE),
    );

    // PIR
    configureOn(this, undefined, this.getSchema(...SCHEMA_CODE.PIR_ON));
    configureMotionDetected(this, undefined, this.getSchema(...SCHEMA_CODE.PIR));

    // RGB Power Switch
    configureOn(this, undefined, this.getSchema(...SCHEMA_CODE.POWER_SWITCH));
  }

}


================================================
FILE: src/accessory/LightSensorAccessory.ts
================================================
import { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDevice';
import { limit } from '../util/util';
import BaseAccessory from './BaseAccessory';

const SCHEMA_CODE = {
  BRIGHT_LEVEL: ['bright_value'],
};

export default class LightSensorAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.BRIGHT_LEVEL];
  }

  configureServices() {
    const schema = this.getSchema(...SCHEMA_CODE.BRIGHT_LEVEL);
    if (!schema) {
      return;
    }

    const service = this.accessory.getService(this.Service.LightSensor)
      || this.accessory.addService(this.Service.LightSensor);

    const property = schema.property as TuyaDeviceSchemaIntegerProperty;
    const multiple = Math.pow(10, property ? property.scale : 0);
    service.getCharacteristic(this.Characteristic.CurrentAmbientLightLevel)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return limit(status.value as number / multiple, 0.0001, 100000);
      });

  }

}


================================================
FILE: src/accessory/LockAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';

const SCHEMA_CODE = {
  LOCK_CURRENT_STATE: ['open_close', 'closed_opened', 'lock_motor_state'],
  LOCK_TARGET_STATE: ['lock_motor_state'],
};

export default class LockAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.LOCK_CURRENT_STATE];
  }

  configureServices() {
    this.configureLockCurrentState();
    this.configureLockTargetState();
  }

  mainService() {
    return this.accessory.getService(this.Service.LockMechanism)
      || this.accessory.addService(this.Service.LockMechanism);
  }

  configureLockCurrentState() {
    const schema = this.getSchema(...SCHEMA_CODE.LOCK_CURRENT_STATE);
    if (!schema) {
      return;
    }

    const { UNSECURED, SECURED } = this.Characteristic.LockCurrentState;
    this.mainService().getCharacteristic(this.Characteristic.LockCurrentState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return (status.value as boolean) ? UNSECURED : SECURED;
      });
  }

  configureLockTargetState() {
    const schema = this.getSchema(...SCHEMA_CODE.LOCK_TARGET_STATE);
    if (!schema) {
      return;
    }

    const { UNSECURED, SECURED } = this.Characteristic.LockTargetState;
    this.mainService().getCharacteristic(this.Characteristic.LockTargetState)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return (status.value as boolean) ? UNSECURED : SECURED;
      })
      .onSet(async value => {
        const res = await this.deviceManager.getLockTemporaryKey(this.device.id);
        if (!res.success) {
          return;
        }
        await this.deviceManager.sendLockCommands(this.device.id, res.result.ticket_id, (value === UNSECURED));
      });
  }

}


================================================
FILE: src/accessory/MotionSensorAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';
import { configureMotionDetected } from './characteristic/MotionDetected';

const SCHEMA_CODE = {
  PIR: ['pir'],
};

export default class MotionSensorAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.PIR];
  }

  configureServices() {
    configureMotionDetected(this, undefined, this.getSchema(...SCHEMA_CODE.PIR));
  }

}


================================================
FILE: src/accessory/OutletAccessory.ts
================================================
import SwitchAccessory from './SwitchAccessory';

export default class OutletAccessory extends SwitchAccessory {
  mainService() {
    return this.Service.Outlet;
  }
}


================================================
FILE: src/accessory/PetFeederAccessory.ts
================================================
import BaseAccessory from './BaseAccessory';
import { configureActive } from './characteristic/Active';
import { CharacteristicValue } from 'homebridge';

const SCHEMA_CODE = {
  ACTIVE: ['switch'],
  LIGHT: ['light'],
  QUICK_FEED: ['quick_feed'],
  SLOW_FEED: ['slow_feed'],
  MANUAL_FEED: ['manual_feed'],
  MEAL_PLAN: ['meal_plan'],
  BATTERY_PERCENTAGE: ['battery_percentage'],
  FEED_REPORT: ['feed_report'],
  FEED_STATE: ['feed_state'],
};

export default class PetFeederAccessory extends BaseAccessory {

  requiredSchema() {
    return [SCHEMA_CODE.ACTIVE];
  }

  configureServices() {
    configureActive(this, this.mainService(), this.getSchema(...SCHEMA_CODE.ACTIVE));
    this.configureLight();
    this.configureQuickFeed();
    this.configureSlowFeed();
    this.configureManualFeed();
    this.configureMealPlan();
    this.configureBatteryPercentage();
    this.configureFeedReport();
    this.configureFeedState();
  }

  mainService() {
    return this.accessory.getService(this.Service.Switch)
      || this.accessory.addService(this.Service.Switch);
  }

  configureLight() {
    const schema = this.getSchema(...SCHEMA_CODE.LIGHT);
    if (!schema) {
      this.log.warn('Light is not supported.');
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.On)
      .onSet(async (value: CharacteristicValue) => {
        await this.sendCommands([{ code: schema.code, value: value as boolean }]);
      });
  }

  configureQuickFeed() {
    const schema = this.getSchema(...SCHEMA_CODE.QUICK_FEED);
    if (!schema) {
      this.log.warn('Quick feed is not supported.');
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.On)
      .onSet(async (value: CharacteristicValue) => {
        if (value as boolean) {
          await this.sendCommands([{ code: schema.code, value: true }]);
        }
      });
  }

  configureSlowFeed() {
    const schema = this.getSchema(...SCHEMA_CODE.SLOW_FEED);
    if (!schema) {
      this.log.warn('Slow feed is not supported.');
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.On)
      .onSet(async (value: CharacteristicValue) => {
        if (value as boolean) {
          await this.sendCommands([{ code: schema.code, value: true }]);
        }
      });
  }

  configureManualFeed() {
    const schema = this.getSchema(...SCHEMA_CODE.MANUAL_FEED);
    if (!schema) {
      this.log.warn('Manual feed is not supported.');
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.On)
      .onSet(async (value: CharacteristicValue) => {
        if (value as boolean) {
          await this.sendCommands([{ code: schema.code, value: 1 }]);
        }
      });
  }

  configureMealPlan() {
    const schema = this.getSchema(...SCHEMA_CODE.MEAL_PLAN);
    if (!schema) {
      this.log.warn('Meal plan is not supported.');
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.On)
      .onSet(async (value: CharacteristicValue) => {
        if (value as boolean) {
          await this.sendCommands([{ code: schema.code, value: value as boolean }]);
        }
      });
  }

  configureBatteryPercentage() {
    const schema = this.getSchema(...SCHEMA_CODE.BATTERY_PERCENTAGE);
    if (!schema) {
      this.log.warn('Battery percentage is not supported.');
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.BatteryLevel)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return status.value as number;
      });
  }

  configureFeedReport() {
    const schema = this.getSchema(...SCHEMA_CODE.FEED_REPORT);
    if (!schema) {
      this.log.warn('Feed report is not supported.');
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.StatusActive)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return status.value as number;
      });
  }

  configureFeedState() {
    const schema = this.getSchema(...SCHEMA_CODE.FEED_STATE);
    if (!schema) {
      this.log.warn('Feed state is not supported.');
      return;
    }

    this.mainService().getCharacteristic(this.Characteristic.StatusActive)
      .onGet(() => {
        const status = this.getStatus(schema.code)!;
        return status.value === 'feeding';
      });
  }
}


================================================
FILE: src/accessory/SaunaAccessory.ts
================================================
import { TuyaDeviceSchemaIntegerProperty, TuyaDeviceStatus } from '../device/TuyaDevice';
import { limit } from '../util/util';
import BaseAccessory from './BaseAccessory';
import { configureCurrentTemperature } from './characteristic/CurrentTemperature';
import { configureTempDisplayUnits } from './characteristic/TemperatureDisplayUnits';
import { configureLight } from './characteristic/Light';


const SCHEMA_CODE = {
  ON: ['powerswitch'],
  CURRENT_TEMP: ['currtemp', 'settemp'],
  TARGET_TEMP: ['settemp'],
  TEMP_UNIT_CONVERT: ['temp_unit_convert', 'c_f'],
  LIGHT: ['lightswitch'],
  LED: ['ledswitch'],
  // TIMER: ['settime'], // Not currently supppored by homekit
};

export default class SaunaAccessory extends BaseAccessory {


  requiredSchema() {
    return [SCHEMA_CODE.CURRENT_TEMP, SCHEMA_CODE.TARGET_TEMP];
  }

  configureServices() {
    this.configureCurrentState(
Download .txt
gitextract_mundw2wv/

├── .eslintrc
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── login_issue.yml
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .npmignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── ADVANCED_OPTIONS.md
├── CHANGELOG.md
├── LICENSE
├── README.md
├── SUPPORTED_DEVICES.md
├── config.schema.json
├── jest.config.js
├── nodemon.json
├── package.json
├── src/
│   ├── accessory/
│   │   ├── AccessoryFactory.ts
│   │   ├── AirConditionerAccessory.ts
│   │   ├── AirPurifierAccessory.ts
│   │   ├── AirQualitySensorAccessory.ts
│   │   ├── BaseAccessory.ts
│   │   ├── CameraAccessory.ts
│   │   ├── CarbonDioxideSensorAccessory.ts
│   │   ├── CarbonMonoxideSensorAccessory.ts
│   │   ├── CatToiletAccessory.ts
│   │   ├── ContactSensorAccessory.ts
│   │   ├── DehumidifierAccessory.ts
│   │   ├── DiffuserAccessory.ts
│   │   ├── DimmerAccessory.ts
│   │   ├── DoorbellAccessory.ts
│   │   ├── ExtractionHoodAccessory.ts
│   │   ├── FanAccessory.ts
│   │   ├── GarageDoorAccessory.ts
│   │   ├── HeaterAccessory.ts
│   │   ├── HumanPresenceSensorAccessory.ts
│   │   ├── HumidifierAccessory.ts
│   │   ├── IRAirConditionerAccessory.ts
│   │   ├── IRControlHubAccessory.ts
│   │   ├── IRGenericAccessory.ts
│   │   ├── LeakSensorAccessory.ts
│   │   ├── LightAccessory.ts
│   │   ├── LightSensorAccessory.ts
│   │   ├── LockAccessory.ts
│   │   ├── MotionSensorAccessory.ts
│   │   ├── OutletAccessory.ts
│   │   ├── PetFeederAccessory.ts
│   │   ├── SaunaAccessory.ts
│   │   ├── SceneAccessory.ts
│   │   ├── SceneSwitchAccessory.ts
│   │   ├── SecuritySystemAccessory.ts
│   │   ├── SmokeSensorAccessory.ts
│   │   ├── SwitchAccessory.ts
│   │   ├── TemperatureHumiditySensorAccessory.ts
│   │   ├── ThermostatAccessory.ts
│   │   ├── ValveAccessory.ts
│   │   ├── VibrationSensorAccessory.ts
│   │   ├── WeatherStationAccessory.ts
│   │   ├── WhiteNoiseLightAccessory.ts
│   │   ├── WindowAccessory.ts
│   │   ├── WindowCoveringAccessory.ts
│   │   ├── WirelessSwitchAccessory.ts
│   │   └── characteristic/
│   │       ├── Active.ts
│   │       ├── AirQuality.ts
│   │       ├── CurrentRelativeHumidity.ts
│   │       ├── CurrentTemperature.ts
│   │       ├── EnergyUsage.ts
│   │       ├── Light.ts
│   │       ├── LockPhysicalControls.ts
│   │       ├── MotionDetected.ts
│   │       ├── Name.ts
│   │       ├── OccupancyDetected.ts
│   │       ├── On.ts
│   │       ├── ProgrammableSwitchEvent.ts
│   │       ├── RelativeHumidityDehumidifierThreshold.ts
│   │       ├── RotationSpeed.ts
│   │       ├── SecuritySystemState.ts
│   │       ├── SwingMode.ts
│   │       └── TemperatureDisplayUnits.ts
│   ├── config.ts
│   ├── core/
│   │   ├── TuyaOpenAPI.ts
│   │   └── TuyaOpenMQ.ts
│   ├── device/
│   │   ├── TuyaCustomDeviceManager.ts
│   │   ├── TuyaDevice.ts
│   │   ├── TuyaDeviceManager.ts
│   │   └── TuyaHomeDeviceManager.ts
│   ├── index.ts
│   ├── platform.ts
│   ├── settings.ts
│   └── util/
│       ├── FfmpegStreamingProcess.ts
│       ├── Logger.ts
│       ├── TuyaRecordingDelegate.ts
│       ├── TuyaStreamDelegate.ts
│       ├── color.ts
│       └── util.ts
├── test/
│   ├── FanAccessory.test.ts
│   ├── Light.test.ts
│   ├── custom.test.ts
│   ├── home.test.ts
│   └── util.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (554 symbols across 80 files)

FILE: src/accessory/AccessoryFactory.ts
  class AccessoryFactory (line 51) | class AccessoryFactory {
    method createAccessory (line 52) | static createAccessory(

FILE: src/accessory/AirConditionerAccessory.ts
  constant SCHEMA_CODE (line 12) | const SCHEMA_CODE = {
  constant AC_MODES (line 28) | const AC_MODES = ['auto', 'cold', 'hot'];
  constant DEHUMIDIFIER_MODE (line 29) | const DEHUMIDIFIER_MODE = 'wet';
  constant FAN_MODE (line 30) | const FAN_MODE = 'wind';
  class AirConditionerAccessory (line 32) | class AirConditionerAccessory extends BaseAccessory {
    method requiredSchema (line 34) | requiredSchema() {
    method configureServices (line 38) | configureServices() {
    method configureAirConditioner (line 48) | configureAirConditioner() {
    method configureDehumidifier (line 95) | configureDehumidifier() {
    method configureFan (line 144) | configureFan() {
    method mainService (line 178) | mainService() {
    method dehumidifierService (line 183) | dehumidifierService() {
    method fanService (line 188) | fanService() {
    method configureCurrentState (line 193) | configureCurrentState() {
    method configureTargetState (line 213) | configureTargetState() {
    method configureCoolingThreshouldTemp (line 265) | configureCoolingThreshouldTemp() {
    method configureHeatingThreshouldTemp (line 304) | configureHeatingThreshouldTemp() {

FILE: src/accessory/AirPurifierAccessory.ts
  constant SCHEMA_CODE (line 8) | const SCHEMA_CODE = {
  class AirPurifierAccessory (line 19) | class AirPurifierAccessory extends BaseAccessory {
    method requiredSchema (line 21) | requiredSchema() {
    method configureServices (line 25) | configureServices() {
    method mainService (line 48) | mainService() {
    method getFanSpeedSchema (line 53) | getFanSpeedSchema() {
    method getFanSpeedLevelSchema (line 61) | getFanSpeedLevelSchema() {
    method configureCurrentState (line 70) | configureCurrentState() {
    method configureTargetState (line 84) | configureTargetState() {

FILE: src/accessory/AirQualitySensorAccessory.ts
  constant SCHEMA_CODE (line 6) | const SCHEMA_CODE = {
  class AirQualitySensorAccessory (line 15) | class AirQualitySensorAccessory extends BaseAccessory {
    method requiredSchema (line 17) | requiredSchema() {
    method configureServices (line 21) | configureServices() {

FILE: src/accessory/BaseAccessory.ts
  constant MANUFACTURER (line 12) | const MANUFACTURER = 'Tuya Inc.';
  constant SCHEMA_CODE (line 14) | const SCHEMA_CODE = {
  class BaseAccessory (line 27) | class BaseAccessory {
    method constructor (line 45) | constructor(
    method addAccessoryInfoService (line 53) | addAccessoryInfoService() {
    method addBatteryService (line 66) | addBatteryService() {
    method configureStatusActive (line 110) | configureStatusActive() {
    method updateAllValues (line 120) | async updateAllValues() {
    method checkOnlineStatus (line 154) | checkOnlineStatus() {
    method getSchema (line 161) | getSchema(...codes: string[]) {
    method getStatus (line 179) | getStatus(code: string) {
    method sendCommands (line 193) | async sendCommands(commands: TuyaDeviceStatus[], debounce = false) {
    method checkRequirements (line 228) | checkRequirements() {
    method requiredSchema (line 249) | requiredSchema(): string[][] {
    method configureServices (line 253) | configureServices() {
    method onDeviceInfoUpdate (line 257) | async onDeviceInfoUpdate(info) {
    method onDeviceStatusUpdate (line 261) | async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {
  class OverridedBaseAccessory (line 268) | class OverridedBaseAccessory extends BaseAccessory {
    method getOverridedSchema (line 272) | private getOverridedSchema(code: string) {
    method getSchema (line 298) | getSchema(...codes: string[]) {
    method getOverridedStatus (line 313) | private getOverridedStatus(code: string) {
    method getStatus (line 336) | getStatus(code: string) {
    method sendCommands (line 341) | async sendCommands(commands: TuyaDeviceStatus[], debounce?: boolean) {

FILE: src/accessory/CameraAccessory.ts
  constant SCHEMA_CODE (line 8) | const SCHEMA_CODE = {
  class CameraAccessory (line 21) | class CameraAccessory extends BaseAccessory {
    method requiredSchema (line 25) | requiredSchema() {
    method configureServices (line 29) | configureServices() {
    method configureMotion (line 36) | configureMotion() {
    method configureDoorbell (line 48) | configureDoorbell() {
    method configureCamera (line 62) | configureCamera() {
    method configureFloodLight (line 75) | configureFloodLight() {
    method getLightService (line 91) | getLightService() {
    method getDoorbellService (line 96) | getDoorbellService() {
    method getMotionService (line 101) | getMotionService() {
    method onDeviceStatusUpdate (line 106) | async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {
    method onMotionDetected (line 129) | onMotionDetected(status: TuyaDeviceStatus) {

FILE: src/accessory/CarbonDioxideSensorAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class CarbonDioxideSensorAccessory (line 11) | class CarbonDioxideSensorAccessory extends BaseAccessory {
    method requiredSchema (line 13) | requiredSchema() {
    method configureServices (line 17) | configureServices() {
    method mainService (line 23) | mainService() {
    method configureCarbonDioxideDetected (line 28) | configureCarbonDioxideDetected() {
    method configureCarbonDioxideLevel (line 42) | configureCarbonDioxideLevel() {

FILE: src/accessory/CarbonMonoxideSensorAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class CarbonMonoxideSensorAccessory (line 10) | class CarbonMonoxideSensorAccessory extends BaseAccessory {
    method requiredSchema (line 12) | requiredSchema() {
    method configureServices (line 16) | configureServices() {
    method mainService (line 22) | mainService() {
    method configureCarbonMonoxideDetected (line 27) | configureCarbonMonoxideDetected() {
    method configureCarbonMonoxideLevel (line 41) | configureCarbonMonoxideLevel() {

FILE: src/accessory/CatToiletAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class CatToiletAccessory (line 20) | class CatToiletAccessory extends BaseAccessory {
    method requiredSchema (line 22) | requiredSchema() {
    method configureServices (line 26) | configureServices() {
    method mainService (line 50) | mainService() {
    method configureSwitch (line 55) | configureSwitch(schemaCodes: string[], name: string) {
    method configureLight (line 68) | configureLight() {
    method configureOccupancySensor (line 92) | configureOccupancySensor() {
    method configureFilterMaintenance (line 114) | configureFilterMaintenance() {
    method configureFault (line 135) | configureFault() {

FILE: src/accessory/ContactSensorAccessory.ts
  constant SCHEMA_CODE (line 3) | const SCHEMA_CODE = {
  class ContaceSensor (line 7) | class ContaceSensor extends BaseAccessory {
    method requiredSchema (line 9) | requiredSchema() {
    method configureServices (line 13) | configureServices() {

FILE: src/accessory/DehumidifierAccessory.ts
  constant SCHEMA_CODE (line 10) | const SCHEMA_CODE = {
  class DehumidifierAccessory (line 20) | class DehumidifierAccessory extends BaseAccessory {
    method requiredSchema (line 22) | requiredSchema() {
    method configureServices (line 26) | configureServices() {
    method mainService (line 43) | mainService() {
    method configureCurrentState (line 49) | configureCurrentState() {
    method configureTargetState (line 65) | configureTargetState() {

FILE: src/accessory/DiffuserAccessory.ts
  constant SCHEMA_CODE (line 7) | const SCHEMA_CODE = {
  class DiffuserAccessory (line 19) | class DiffuserAccessory extends BaseAccessory {
    method requiredSchema (line 21) | requiredSchema() {
    method configureServices (line 25) | configureServices() {
    method mainService (line 44) | mainService() {
    method configureDiffuser (line 49) | configureDiffuser() {

FILE: src/accessory/DimmerAccessory.ts
  constant SCHEMA_CODE (line 8) | const SCHEMA_CODE = {
  class DimmerAccessory (line 13) | class DimmerAccessory extends BaseAccessory {
    method requiredSchema (line 15) | requiredSchema() {
    method configureServices (line 19) | configureServices() {
    method configureBrightness (line 42) | configureBrightness(service: Service, suffix: string) {
    method onDeviceStatusUpdate (line 87) | async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {

FILE: src/accessory/DoorbellAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class DoorbellAccessory (line 12) | class DoorbellAccessory extends BaseAccessory {
    method requiredSchema (line 14) | requiredSchema() {
    method configureServices (line 18) | configureServices() {
    method getDoorbellService (line 77) | getDoorbellService() {
    method onDeviceStatusUpdate (line 82) | async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {

FILE: src/accessory/ExtractionHoodAccessory.ts
  constant SCHEMA_CODE (line 9) | const SCHEMA_CODE = {
  class ExtractionHoodAccessory (line 25) | class ExtractionHoodAccessory extends BaseAccessory {
    method requiredSchema (line 27) | requiredSchema() {
    method configureServices (line 31) | configureServices() {
    method mainService (line 63) | mainService() {
    method getFanSpeedSchema (line 68) | getFanSpeedSchema() {
    method getFanSpeedLevelSchema (line 76) | getFanSpeedLevelSchema() {
    method configureCurrentState (line 85) | configureCurrentState() {
    method configureTargetState (line 99) | configureTargetState() {
    method lightServiceType (line 119) | lightServiceType() {
    method lightService (line 129) | lightService() {

FILE: src/accessory/FanAccessory.ts
  constant SCHEMA_CODE (line 10) | const SCHEMA_CODE = {
  class FanAccessory (line 24) | class FanAccessory extends BaseAccessory {
    method requiredSchema (line 26) | requiredSchema() {
    method configureServices (line 30) | configureServices() {
    method fanServiceType (line 90) | fanServiceType() {
    method fanService (line 98) | fanService() {
    method lightServiceType (line 104) | lightServiceType() {
    method lightService (line 114) | lightService() {
    method getFanSpeedSchema (line 120) | getFanSpeedSchema() {
    method getFanSpeedLevelSchema (line 128) | getFanSpeedLevelSchema() {
    method configureRotationDirection (line 136) | configureRotationDirection() {

FILE: src/accessory/GarageDoorAccessory.ts
  constant SCHEMA_CODE (line 3) | const SCHEMA_CODE = {
  class GarageDoorAccessory (line 8) | class GarageDoorAccessory extends BaseAccessory {
    method requiredSchema (line 10) | requiredSchema() {
    method configureServices (line 14) | configureServices() {
    method mainService (line 21) | mainService() {
    method configureCurrentDoorState (line 26) | configureCurrentDoorState() {
    method configureTargetDoorState (line 52) | configureTargetDoorState() {

FILE: src/accessory/HeaterAccessory.ts
  constant SCHEMA_CODE (line 11) | const SCHEMA_CODE = {
  class HeaterAccessory (line 21) | class HeaterAccessory extends BaseAccessory {
    method requiredSchema (line 23) | requiredSchema() {
    method configureServices (line 27) | configureServices() {
    method mainService (line 39) | mainService() {
    method configureCurrentState (line 44) | configureCurrentState() {
    method configureTargetState (line 63) | configureTargetState() {
    method configureHeatingThreshouldTemp (line 76) | configureHeatingThreshouldTemp() {

FILE: src/accessory/HumanPresenceSensorAccessory.ts
  constant SCHEMA_CODE (line 4) | const SCHEMA_CODE = {
  class HumanPresenceSensorAccessory (line 8) | class HumanPresenceSensorAccessory extends BaseAccessory {
    method requiredSchema (line 10) | requiredSchema() {
    method configureServices (line 14) | configureServices() {

FILE: src/accessory/HumidifierAccessory.ts
  constant SCHEMA_CODE (line 9) | const SCHEMA_CODE = {
  class HumidifierAccessory (line 20) | class HumidifierAccessory extends BaseAccessory {
    method requiredSchema (line 22) | requiredSchema() {
    method configureServices (line 26) | configureServices() {
    method mainService (line 51) | mainService() {
    method configureTargetState (line 57) | configureTargetState() {
    method configureCurrentState (line 67) | configureCurrentState() {
    method configureRelativeHumidityHumidifierThreshold (line 83) | configureRelativeHumidityHumidifierThreshold() {
    method configureRotationSpeed (line 113) | configureRotationSpeed() {
    method setSprayModeToHumidity (line 151) | async setSprayModeToHumidity() {

FILE: src/accessory/IRAirConditionerAccessory.ts
  constant POWER_OFF (line 4) | const POWER_OFF = 0;
  constant POWER_ON (line 5) | const POWER_ON = 1;
  constant AC_MODE_COOL (line 7) | const AC_MODE_COOL = 0;
  constant AC_MODE_HEAT (line 8) | const AC_MODE_HEAT = 1;
  constant AC_MODE_AUTO (line 9) | const AC_MODE_AUTO = 2;
  constant AC_MODE_FAN (line 10) | const AC_MODE_FAN = 3;
  constant AC_MODE_DEHUMIDIFIER (line 11) | const AC_MODE_DEHUMIDIFIER = 4;
  constant FAN_SPEED_AUTO (line 13) | const FAN_SPEED_AUTO = 0;
  constant FAN_SPEED_LOW (line 14) | const FAN_SPEED_LOW = 1;
  constant FAN_SPEED_HIGH (line 16) | const FAN_SPEED_HIGH = 3;
  class IRAirConditionerAccessory (line 18) | class IRAirConditionerAccessory extends BaseAccessory {
    method configureServices (line 20) | configureServices() {
    method configureAirConditioner (line 26) | configureAirConditioner() {
    method configureDehumidifier (line 86) | configureDehumidifier() {
    method configureFan (line 131) | configureFan() {
    method mainService (line 160) | mainService() {
    method dehumidifierService (line 165) | dehumidifierService() {
    method fanService (line 170) | fanService() {
    method getPower (line 175) | getPower() {
    method setPower (line 180) | setPower(value) {
    method getMode (line 185) | getMode() {
    method setMode (line 190) | setMode(value) {
    method getWind (line 195) | getWind() {
    method setWind (line 200) | setWind(value) {
    method getTemp (line 205) | getTemp() {
    method setTemp (line 210) | setTemp(value) {
    method getKeyRangeItem (line 215) | getKeyRangeItem(mode: number) {
    method supportDehumidifier (line 220) | supportDehumidifier() {
    method supportFan (line 224) | supportFan() {
    method getTempRange (line 228) | getTempRange(mode: number) {
    method getParentAccessory (line 241) | getParentAccessory() {
    method configureTargetState (line 245) | configureTargetState() {
    method configureCurrentTemperature (line 281) | configureCurrentTemperature() {
    method configureTargetFanState (line 292) | configureTargetFanState(service) {
    method configureRotationSpeed (line 301) | configureRotationSpeed(service) {
    method sendACCommands (line 317) | async sendACCommands() {

FILE: src/accessory/IRControlHubAccessory.ts
  constant SCHEMA_CODE (line 6) | const SCHEMA_CODE = {
  class IRControlHubAccessory (line 11) | class IRControlHubAccessory extends BaseAccessory {
    method requiredSchema (line 13) | requiredSchema() {
    method configureServices (line 17) | configureServices() {
    method getSubAccessories (line 22) | getSubAccessories() {
    method onDeviceStatusUpdate (line 26) | async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {

FILE: src/accessory/IRGenericAccessory.ts
  class IRGenericAccessory (line 5) | class IRGenericAccessory extends BaseAccessory {
    method configureServices (line 7) | configureServices() {
    method configureSwitch (line 22) | configureSwitch(key: TuyaIRRemoteKeyListItem) {
    method sendInfraredCommands (line 43) | async sendInfraredCommands(key: TuyaIRRemoteKeyListItem) {

FILE: src/accessory/LeakSensorAccessory.ts
  constant SCHEMA_CODE (line 3) | const SCHEMA_CODE = {
  class LeakSensor (line 7) | class LeakSensor extends BaseAccessory {
    method requiredSchema (line 9) | requiredSchema() {
    method configureServices (line 13) | configureServices() {

FILE: src/accessory/LightAccessory.ts
  constant SCHEMA_CODE (line 6) | const SCHEMA_CODE = {
  class LightAccessory (line 17) | class LightAccessory extends BaseAccessory {
    method requiredSchema (line 19) | requiredSchema() {
    method configureServices (line 23) | configureServices() {

FILE: src/accessory/LightSensorAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class LightSensorAccessory (line 9) | class LightSensorAccessory extends BaseAccessory {
    method requiredSchema (line 11) | requiredSchema() {
    method configureServices (line 15) | configureServices() {

FILE: src/accessory/LockAccessory.ts
  constant SCHEMA_CODE (line 3) | const SCHEMA_CODE = {
  class LockAccessory (line 8) | class LockAccessory extends BaseAccessory {
    method requiredSchema (line 10) | requiredSchema() {
    method configureServices (line 14) | configureServices() {
    method mainService (line 19) | mainService() {
    method configureLockCurrentState (line 24) | configureLockCurrentState() {
    method configureLockTargetState (line 38) | configureLockTargetState() {

FILE: src/accessory/MotionSensorAccessory.ts
  constant SCHEMA_CODE (line 4) | const SCHEMA_CODE = {
  class MotionSensorAccessory (line 8) | class MotionSensorAccessory extends BaseAccessory {
    method requiredSchema (line 10) | requiredSchema() {
    method configureServices (line 14) | configureServices() {

FILE: src/accessory/OutletAccessory.ts
  class OutletAccessory (line 3) | class OutletAccessory extends SwitchAccessory {
    method mainService (line 4) | mainService() {

FILE: src/accessory/PetFeederAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class PetFeederAccessory (line 17) | class PetFeederAccessory extends BaseAccessory {
    method requiredSchema (line 19) | requiredSchema() {
    method configureServices (line 23) | configureServices() {
    method mainService (line 35) | mainService() {
    method configureLight (line 40) | configureLight() {
    method configureQuickFeed (line 53) | configureQuickFeed() {
    method configureSlowFeed (line 68) | configureSlowFeed() {
    method configureManualFeed (line 83) | configureManualFeed() {
    method configureMealPlan (line 98) | configureMealPlan() {
    method configureBatteryPercentage (line 113) | configureBatteryPercentage() {
    method configureFeedReport (line 127) | configureFeedReport() {
    method configureFeedState (line 141) | configureFeedState() {

FILE: src/accessory/SaunaAccessory.ts
  constant SCHEMA_CODE (line 9) | const SCHEMA_CODE = {
  class SaunaAccessory (line 19) | class SaunaAccessory extends BaseAccessory {
    method requiredSchema (line 22) | requiredSchema() {
    method configureServices (line 26) | configureServices() {
    method mainService (line 37) | mainService() {
    method configureCurrentState (line 42) | configureCurrentState() {
    method configureTargetState (line 58) | configureTargetState() {
    method configureTargetTemp (line 95) | configureTargetTemp() {
    method configureLight (line 137) | configureLight() {

FILE: src/accessory/SceneAccessory.ts
  class SceneAccessory (line 6) | class SceneAccessory extends BaseAccessory {
    method constructor (line 8) | constructor(platform: TuyaPlatform, accessory: PlatformAccessory) {

FILE: src/accessory/SceneSwitchAccessory.ts
  class SceneSwitchAccessory (line 5) | class SceneSwitchAccessory extends BaseAccessory {
    method configureServices (line 7) | configureServices() {
    method configureSwitch (line 15) | configureSwitch(schema: TuyaDeviceSchema, name: string) {

FILE: src/accessory/SecuritySystemAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class SecuritySystemAccessory (line 10) | class SecuritySystemAccessory extends BaseAccessory {
    method requiredSchema (line 12) | requiredSchema() {
    method configureServices (line 18) | configureServices() {

FILE: src/accessory/SmokeSensorAccessory.ts
  constant SCHEMA_CODE (line 3) | const SCHEMA_CODE = {
  class SmokeSensor (line 7) | class SmokeSensor extends BaseAccessory {
    method requiredSchema (line 9) | requiredSchema() {
    method configureServices (line 13) | configureServices() {

FILE: src/accessory/SwitchAccessory.ts
  constant SCHEMA_CODE (line 9) | const SCHEMA_CODE = {
  class SwitchAccessory (line 20) | class SwitchAccessory extends BaseAccessory {
    method requiredSchema (line 22) | requiredSchema() {
    method configureServices (line 26) | configureServices() {
    method mainService (line 51) | mainService() {
    method configureSwitch (line 55) | configureSwitch(schema: TuyaDeviceSchema, name: string) {
    method configureInching (line 76) | configureInching() {

FILE: src/accessory/TemperatureHumiditySensorAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class TemperatureHumiditySensorAccessory (line 11) | class TemperatureHumiditySensorAccessory extends BaseAccessory {
    method requiredSchema (line 13) | requiredSchema() {
    method configureServices (line 17) | configureServices(): void {

FILE: src/accessory/ThermostatAccessory.ts
  constant SCHEMA_CODE (line 7) | const SCHEMA_CODE = {
  class ThermostatAccessory (line 16) | class ThermostatAccessory extends BaseAccessory {
    method requiredSchema (line 18) | requiredSchema() {
    method configureServices (line 22) | configureServices() {
    method mainService (line 31) | mainService() {
    method configureCurrentState (line 36) | configureCurrentState() {
    method configureTargetState (line 82) | configureTargetState() {
    method configureTargetTemp (line 167) | configureTargetTemp() {

FILE: src/accessory/ValveAccessory.ts
  constant SCHEMA_CODE (line 6) | const SCHEMA_CODE = {
  class ValveAccessory (line 10) | class ValveAccessory extends BaseAccessory {
    method requiredSchema (line 12) | requiredSchema() {
    method configureServices (line 16) | configureServices(): void {
    method configureValve (line 33) | configureValve(schema: TuyaDeviceSchema, name: string) {

FILE: src/accessory/VibrationSensorAccessory.ts
  constant SCHEMA_CODE (line 4) | const SCHEMA_CODE = {
  class VibrationSensorAccessory (line 8) | class VibrationSensorAccessory extends BaseAccessory {
    method requiredSchema (line 10) | requiredSchema() {
    method configureServices (line 14) | configureServices() {
    method getMotionService (line 18) | getMotionService() {
    method onDeviceStatusUpdate (line 23) | async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {
    method onMotionDetected (line 32) | onMotionDetected(status: TuyaDeviceStatus) {

FILE: src/accessory/WeatherStationAccessory.ts
  class TemperatureHumiditySensorAccessory (line 3) | class TemperatureHumiditySensorAccessory extends BaseAccessory {
    method configureServices (line 4) | configureServices(): void {
    method getDynamicSchemaCodes (line 46) | private getDynamicSchemaCodes() {

FILE: src/accessory/WhiteNoiseLightAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class WhiteNoiseLightAccessory (line 11) | class WhiteNoiseLightAccessory extends BaseAccessory {
    method requiredSchema (line 12) | requiredSchema() {
    method configureServices (line 16) | configureServices() {
    method lightColorSchema (line 38) | lightColorSchema() {
    method lightServiceType (line 57) | lightServiceType() {
    method lightService (line 64) | lightService() {

FILE: src/accessory/WindowAccessory.ts
  class WindowAccessory (line 3) | class WindowAccessory extends WindowCoveringAccessory {
    method mainService (line 4) | mainService() {

FILE: src/accessory/WindowCoveringAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = [
  class WindowCoveringAccessory (line 20) | class WindowCoveringAccessory extends BaseAccessory {
    method requiredSchema (line 22) | requiredSchema() {
    method configureServices (line 26) | configureServices() {
    method configureCurrentPosition (line 46) | configureCurrentPosition(i : number) {
    method configurePositionState (line 78) | configurePositionState(i : number) {
    method configureTargetPositionPercent (line 105) | configureTargetPositionPercent(i : number) {
    method configureTargetPositionControl (line 124) | configureTargetPositionControl(i : number) {

FILE: src/accessory/WirelessSwitchAccessory.ts
  constant SCHEMA_CODE (line 5) | const SCHEMA_CODE = {
  class SwitchAccessory (line 9) | class SwitchAccessory extends BaseAccessory {
    method requiredSchema (line 11) | requiredSchema() {
    method configureServices (line 15) | configureServices() {
    method configureSwitch (line 23) | configureSwitch(schema: TuyaDeviceSchema, name: string) {
    method onDeviceStatusUpdate (line 35) | async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {

FILE: src/accessory/characteristic/Active.ts
  function configureActive (line 5) | function configureActive(accessory: BaseAccessory, service: Service, sch...

FILE: src/accessory/characteristic/AirQuality.ts
  function configureAirQuality (line 6) | function configureAirQuality(
  function configureDensity (line 64) | function configureDensity(

FILE: src/accessory/characteristic/CurrentRelativeHumidity.ts
  function configureCurrentRelativeHumidity (line 6) | function configureCurrentRelativeHumidity(accessory: BaseAccessory, serv...

FILE: src/accessory/characteristic/CurrentTemperature.ts
  function configureCurrentTemperature (line 6) | function configureCurrentTemperature(accessory: BaseAccessory, service?:...

FILE: src/accessory/characteristic/EnergyUsage.ts
  function configureEnergyUsage (line 6) | function configureEnergyUsage(
  function isUnit (line 51) | function isUnit(schema: TuyaDeviceSchema, ...units: string[]): boolean {
  function createStatusGetter (line 55) | function createStatusGetter(accessory: BaseAccessory, schema: TuyaDevice...
  function createAmperesCharacteristic (line 65) | function createAmperesCharacteristic(api: API) {
  function createWattsCharacteristic (line 79) | function createWattsCharacteristic(api: API) {
  function createVoltsCharacteristic (line 93) | function createVoltsCharacteristic(api: API) {
  function createKilowattHourCharacteristic (line 107) | function createKilowattHourCharacteristic(api: API) {

FILE: src/accessory/characteristic/Light.ts
  constant DEFAULT_COLOR_TEMPERATURE_KELVIN (line 8) | const DEFAULT_COLOR_TEMPERATURE_KELVIN = 6500;
  type LightType (line 10) | enum LightType {
  type TuyaDeviceSchemaColorProperty (line 20) | type TuyaDeviceSchemaColorProperty = {
  function getLightType (line 26) | function getLightType(
  function getColorValue (line 57) | function getColorValue(accessory: BaseAccessory, schema: TuyaDeviceSchem...
  function inWhiteMode (line 71) | function inWhiteMode(
  function inColorMode (line 89) | function inColorMode(
  function configureLightOn (line 107) | function configureLightOn(
  function configureBrightness (line 134) | function configureBrightness(
  function configureColourTemperature (line 185) | function configureColourTemperature(
  function configureHue (line 233) | function configureHue(
  function configureSaturation (line 268) | function configureSaturation(
  function configureLight (line 303) | function configureLight(
  function configureAdaptiveLighting (line 357) | function configureAdaptiveLighting(

FILE: src/accessory/characteristic/LockPhysicalControls.ts
  function configureLockPhysicalControls (line 5) | function configureLockPhysicalControls(accessory: BaseAccessory, service...

FILE: src/accessory/characteristic/MotionDetected.ts
  function configureMotionDetected (line 5) | function configureMotionDetected(accessory: BaseAccessory, service?: Ser...

FILE: src/accessory/characteristic/Name.ts
  function configureName (line 4) | function configureName(accessory: BaseAccessory, service: Service, name:...

FILE: src/accessory/characteristic/OccupancyDetected.ts
  function configureOccupancyDetected (line 5) | function configureOccupancyDetected(accessory: BaseAccessory, service?: ...

FILE: src/accessory/characteristic/On.ts
  function configureOn (line 5) | function configureOn(accessory: BaseAccessory, service?: Service, schema...

FILE: src/accessory/characteristic/ProgrammableSwitchEvent.ts
  constant SINGLE_PRESS (line 5) | const SINGLE_PRESS = 0;
  constant DOUBLE_PRESS (line 6) | const DOUBLE_PRESS = 1;
  constant LONG_PRESS (line 7) | const LONG_PRESS = 2;
  function configureProgrammableSwitchEvent (line 9) | function configureProgrammableSwitchEvent(accessory: BaseAccessory, serv...
  function onProgrammableSwitchEvent (line 30) | function onProgrammableSwitchEvent(accessory: BaseAccessory, service: Se...
  function GetStatelessSwitchProps (line 72) | function GetStatelessSwitchProps(single_press: boolean, double_press: bo...

FILE: src/accessory/characteristic/RelativeHumidityDehumidifierThreshold.ts
  function configureRelativeHumidityDehumidifierThreshold (line 6) | function configureRelativeHumidityDehumidifierThreshold(accessory: BaseA...

FILE: src/accessory/characteristic/RotationSpeed.ts
  function configureRotationSpeed (line 6) | function configureRotationSpeed(
  function configureRotationSpeedLevel (line 37) | function configureRotationSpeedLevel(
  function configureRotationSpeedOn (line 83) | function configureRotationSpeedOn(

FILE: src/accessory/characteristic/SecuritySystemState.ts
  constant TUYA_CODES (line 6) | const TUYA_CODES = {
  function getTuyaHomebridgeMap (line 14) | function getTuyaHomebridgeMap(accessory: BaseAccessory) {
  function configureSecuritySystemCurrentState (line 28) | function configureSecuritySystemCurrentState(accessory: SecuritySystemAc...
  function configureSecuritySystemTargetState (line 54) | function configureSecuritySystemTargetState(accessory: SecuritySystemAcc...

FILE: src/accessory/characteristic/SwingMode.ts
  function configureSwingMode (line 5) | function configureSwingMode(accessory: BaseAccessory, service: Service, ...

FILE: src/accessory/characteristic/TemperatureDisplayUnits.ts
  function configureTempDisplayUnits (line 5) | function configureTempDisplayUnits(accessory: BaseAccessory, service: Se...

FILE: src/config.ts
  type TuyaPlatformDeviceSchemaConfig (line 4) | interface TuyaPlatformDeviceSchemaConfig {
  type TuyaPlatformDeviceConfig (line 14) | interface TuyaPlatformDeviceConfig {
  type TuyaPlatformCustomConfigOptions (line 22) | interface TuyaPlatformCustomConfigOptions {
  type TuyaPlatformHomeConfigOptions (line 34) | interface TuyaPlatformHomeConfigOptions {
  type TuyaPlatformConfigOptions (line 49) | type TuyaPlatformConfigOptions = TuyaPlatformCustomConfigOptions | TuyaP...
  type TuyaPlatformConfig (line 51) | interface TuyaPlatformConfig extends PlatformConfig {

FILE: src/core/TuyaOpenAPI.ts
  type Endpoints (line 15) | enum Endpoints {
  constant DEFAULT_ENDPOINTS (line 24) | const DEFAULT_ENDPOINTS = {
  constant LOGIN_ERROR_MESSAGES (line 31) | const LOGIN_ERROR_MESSAGES = {
  constant API_NOT_SUBSCRIBED_ERROR (line 39) | const API_NOT_SUBSCRIBED_ERROR = `
  constant API_ERROR_MESSAGES (line 48) | const API_ERROR_MESSAGES = {
  type TuyaOpenAPIResponseSuccess (line 55) | type TuyaOpenAPIResponseSuccess = {
  type TuyaOpenAPIResponseError (line 63) | type TuyaOpenAPIResponseError = {
  type TuyaOpenAPIResponse (line 72) | type TuyaOpenAPIResponse = TuyaOpenAPIResponseSuccess | TuyaOpenAPIRespo...
  class TuyaOpenAPI (line 74) | class TuyaOpenAPI {
    method constructor (line 83) | constructor(
    method getDefaultEndpoint (line 94) | static getDefaultEndpoint(countryCode: number) {
    method isLogin (line 104) | isLogin() {
    method isTokenExpired (line 108) | isTokenExpired() {
    method isTokenManagementAPI (line 112) | isTokenManagementAPI(path: string) {
    method _refreshAccessTokenIfNeed (line 119) | async _refreshAccessTokenIfNeed(path: string) {
    method getToken (line 155) | async getToken() {
    method homeLogin (line 177) | async homeLogin(countryCode: number, username: string, password: strin...
    method customGetUserInfo (line 214) | async customGetUserInfo(username: string) {
    method customCreateUser (line 226) | async customCreateUser(username: string, password: string, country_cod...
    method customLogin (line 241) | async customLogin(username: string, password: string) {
    method request (line 261) | async request(method: string, path: string, params?, body?) {
    method get (line 329) | async get(path: string, params?) {
    method post (line 333) | async post(path: string, params?) {
    method delete (line 337) | async delete(path: string, params?) {
    method _getSign (line 341) | _getSign(accessId: string, accessKey: string, accessToken = '', timest...
    method _getStringToSign (line 347) | _getStringToSign(method: string, path: string, params, body) {
    method _getSignUrl (line 357) | _getSignUrl(path: string, params) {
    method _isSaltedPassword (line 374) | _isSaltedPassword(password: string) {

FILE: src/core/TuyaOpenMQ.ts
  constant GCM_TAG_LENGTH (line 9) | const GCM_TAG_LENGTH = 16;
  type TuyaMQTTConfigSourceTopic (line 11) | interface TuyaMQTTConfigSourceTopic {
  type TuyaMQTTConfig (line 15) | interface TuyaMQTTConfig {
  type TuyaMQTTCallback (line 25) | type TuyaMQTTCallback = (topic: string, protocol: number, data) => void;
  class TuyaOpenMQ (line 27) | class TuyaOpenMQ {
    method constructor (line 37) | constructor(
    method start (line 45) | start() {
    method stop (line 49) | stop() {
    method _connect (line 59) | async _connect() {
    method _getMQConfig (line 90) | async _getMQConfig(linkType: string) {
    method _onConnect (line 101) | _onConnect() {
    method _onError (line 105) | _onError(error: Error) {
    method _onEnd (line 109) | _onEnd() {
    method _onMessage (line 113) | async _onMessage(topic: string, payload: Buffer) {
    method _fixWrongOrderMessage (line 132) | _fixWrongOrderMessage(protocol: number, message, t: number) {
    method _decodeMQMessage_1_0 (line 186) | _decodeMQMessage_1_0(b64msg: string, password: string) {
    method _decodeMQMessage_2_0 (line 195) | _decodeMQMessage_2_0(data: string, password: string, t: number) {
    method _decodeMQMessage (line 216) | _decodeMQMessage(data: string, password: string, t: number) {
    method addMessageListener (line 224) | addMessageListener(listener: TuyaMQTTCallback) {
    method removeMessageListener (line 228) | removeMessageListener(listener: TuyaMQTTCallback) {

FILE: src/device/TuyaCustomDeviceManager.ts
  class TuyaCustomDeviceManager (line 5) | class TuyaCustomDeviceManager extends TuyaDeviceManager {
    method constructor (line 7) | constructor(
    method getAssetList (line 15) | async getAssetList(parent_asset_id = -1) {
    method authorizeAssetList (line 24) | async authorizeAssetList(uid: string, asset_ids: string[] = [], author...
    method getAssetDeviceIDList (line 32) | async getAssetDeviceIDList(assetID: string) {
    method updateDevices (line 50) | async updateDevices(assetIDList: string[]) {

FILE: src/device/TuyaDevice.ts
  type TuyaDeviceSchemaMode (line 2) | enum TuyaDeviceSchemaMode {
  type TuyaDeviceSchemaType (line 9) | enum TuyaDeviceSchemaType {
  type TuyaDeviceSchemaIntegerProperty (line 18) | type TuyaDeviceSchemaIntegerProperty = {
  type TuyaDeviceSchemaEnumProperty (line 26) | type TuyaDeviceSchemaEnumProperty = {
  type TuyaDeviceSchemaStringProperty (line 30) | type TuyaDeviceSchemaStringProperty = string;
  type TuyaDeviceSchemaJSONProperty (line 32) | type TuyaDeviceSchemaJSONProperty = object;
  type TuyaDeviceSchemaProperty (line 34) | type TuyaDeviceSchemaProperty = TuyaDeviceSchemaIntegerProperty
  type TuyaDeviceSchema (line 39) | type TuyaDeviceSchema = {
  type TuyaDeviceStatus (line 47) | type TuyaDeviceStatus = {
  type TuyaIRRemoteKeyListItem (line 52) | type TuyaIRRemoteKeyListItem = {
  type TuyaIRRemoteTempListItem (line 60) | type TuyaIRRemoteTempListItem = {
  type TuyaIRRemoteKeyRangeItem (line 66) | type TuyaIRRemoteKeyRangeItem = {
  type TuyaIRRemoteFanListItem (line 72) | type TuyaIRRemoteFanListItem = {
  type TuyaIRRemoteKeys (line 77) | type TuyaIRRemoteKeys = {
  class TuyaDevice (line 87) | class TuyaDevice {
    method constructor (line 123) | constructor(obj: Partial<TuyaDevice>) {
    method isVirtualDevice (line 128) | isVirtualDevice() {
    method isIRControlHub (line 132) | isIRControlHub() {
    method isIRRemoteControl (line 137) | isIRRemoteControl() {

FILE: src/device/TuyaDeviceManager.ts
  type Events (line 12) | enum Events {
  type TuyaMQTTProtocol (line 19) | enum TuyaMQTTProtocol {
  class TuyaDeviceManager (line 24) | class TuyaDeviceManager extends EventEmitter {
    method constructor (line 33) | constructor(
    method getDevice (line 46) | getDevice(deviceID: string) {
    method updateDevices (line 51) | async updateDevices(ownerIDs: []): Promise<TuyaDevice[]> {
    method updateDevice (line 55) | async updateDevice(deviceID: string) {
    method getDeviceInfo (line 75) | async getDeviceInfo(deviceID: string) {
    method getDeviceListInfo (line 80) | async getDeviceListInfo(deviceIDs: string[] = []) {
    method getDeviceSchema (line 85) | async getDeviceSchema(deviceID: string) {
    method getInfraredRemotes (line 122) | async getInfraredRemotes(infraredID: string) {
    method getInfraredKeys (line 127) | async getInfraredKeys(infraredID: string, remoteID: string) {
    method getInfraredACStatus (line 132) | async getInfraredACStatus(infraredID: string, remoteID: string) {
    method getInfraredDIYKeys (line 137) | async getInfraredDIYKeys(infraredID: string, remoteID: string) {
    method updateInfraredRemotes (line 142) | async updateInfraredRemotes(allDevices: TuyaDevice[]) {
    method sendInfraredCommands (line 193) | async sendInfraredCommands(infraredID: string, remoteID: string, categ...
    method sendInfraredACCommands (line 200) | async sendInfraredACCommands(infraredID: string, remoteID: string, pow...
    method sendInfraredDIYCommands (line 209) | async sendInfraredDIYCommands(infraredID: string, remoteID: string, co...
    method getLockTemporaryKey (line 215) | async getLockTemporaryKey(deviceID: string) {
    method sendLockCommands (line 224) | async sendLockCommands(deviceID: string, ticketID: string, open: boole...
    method sendCommands (line 234) | async sendCommands(deviceID: string, commands: TuyaDeviceStatus[]) {
    method onMQTTMessage (line 240) | async onMQTTMessage(topic: string, protocol: TuyaMQTTProtocol, message) {

FILE: src/device/TuyaHomeDeviceManager.ts
  class TuyaHomeDeviceManager (line 4) | class TuyaHomeDeviceManager extends TuyaDeviceManager {
    method getHomeList (line 6) | async getHomeList() {
    method getHomeDeviceList (line 11) | async getHomeDeviceList(homeID: number) {
    method updateDevices (line 16) | async updateDevices(homeIDList: number[]) {
    method getSceneList (line 36) | async getSceneList(homeID: number) {
    method executeScene (line 64) | async executeScene(homeID: string | number, sceneID: string) {

FILE: src/platform.ts
  class TuyaPlatform (line 23) | class TuyaPlatform implements DynamicPlatformPlugin {
    method validate (line 35) | validate() {
    method validateDeviceOverrides (line 60) | validateDeviceOverrides() {
    method validateSchema (line 82) | validateSchema() {
    method constructor (line 109) | constructor(
    method configureAccessory (line 136) | configureAccessory(accessory: PlatformAccessory) {
    method initDevices (line 148) | async initDevices() {
    method getDeviceConfig (line 212) | getDeviceConfig(device: TuyaDevice) {
    method getDeviceSchemaConfig (line 224) | getDeviceSchemaConfig(device: TuyaDevice, code: string) {
    method initCustomProject (line 247) | async initCustomProject() {
    method initHomeProject (line 339) | async initHomeProject() {
    method addAccessory (line 412) | addAccessory(device: TuyaDevice) {
    method updateAccessoryInfo (line 460) | updateAccessoryInfo(device: TuyaDevice, info) {
    method updateAccessoryStatus (line 470) | updateAccessoryStatus(device: TuyaDevice, status: TuyaDeviceStatus[]) {
    method removeAccessory (line 480) | removeAccessory(deviceID: string) {
    method getAccessoryHandler (line 495) | getAccessoryHandler(deviceID: string) {

FILE: src/settings.ts
  constant PLATFORM_NAME (line 12) | const PLATFORM_NAME = platformName;
  constant PLUGIN_NAME (line 17) | const PLUGIN_NAME = pluginName;

FILE: src/util/FfmpegStreamingProcess.ts
  type StreamingDelegate (line 14) | interface StreamingDelegate {
  type FfmpegProgress (line 19) | type FfmpegProgress = {
  class FfmpegStreamingProcess (line 33) | class FfmpegStreamingProcess {
    method constructor (line 38) | constructor(
    method parseProgress (line 120) | parseProgress(data: Uint8Array): FfmpegProgress | undefined {
    method getStdin (line 152) | getStdin() {
    method stop (line 156) | public stop(): void {

FILE: src/util/Logger.ts
  type Logger (line 3) | interface Logger {
  class PrefixLogger (line 10) | class PrefixLogger {
    method constructor (line 11) | constructor(
    method debug (line 19) | debug(message?: any, ...args: any[]) {
    method info (line 27) | info(message?: any, ...args: any[]) {
    method warn (line 31) | warn(message?: any, ...args: any[]) {
    method error (line 35) | error(message?: any, ...args: any[]) {

FILE: src/util/TuyaRecordingDelegate.ts
  class TuyaRecordingDelegate (line 9) | class TuyaRecordingDelegate implements CameraRecordingDelegate {
    method updateRecordingActive (line 10) | updateRecordingActive(active: boolean): void {
    method updateRecordingConfiguration (line 14) | updateRecordingConfiguration(configuration: CameraRecordingConfigurati...
    method handleRecordingStreamRequest (line 18) | handleRecordingStreamRequest(streamId: number): AsyncGenerator<Recordi...
    method acknowledgeStream (line 22) | acknowledgeStream?(streamId: number): void {
    method closeRecordingStream (line 26) | closeRecordingStream(streamId: number, reason: HDSProtocolSpecificErro...

FILE: src/util/TuyaStreamDelegate.ts
  type SessionInfo (line 43) | interface SessionInfo {
  type ActiveSession (line 60) | type ActiveSession = {
  class TuyaStreamingDelegate (line 76) | class TuyaStreamingDelegate implements CameraStreamingDelegate, FfmpegSt...
    method constructor (line 84) | constructor(camera: CameraAccessory) {
    method stopStream (line 174) | stopStream(sessionId: string): void {
    method forceStopStream (line 206) | forceStopStream(sessionId: string) {
    method handleSnapshotRequest (line 210) | async handleSnapshotRequest(
    method prepareStream (line 227) | async prepareStream(
    method handleStreamRequest (line 277) | async handleStreamRequest(
    method retrieveDeviceRTSP (line 306) | private async retrieveDeviceRTSP(): Promise<string> {
    method startStream (line 317) | private async startStream(request: StartStreamRequest, callback: Strea...
    method fetchSnapshot (line 434) | private async fetchSnapshot(): Promise<Buffer> {

FILE: src/util/color.ts
  function kelvinToHSV (line 4) | function kelvinToHSV(kevin: number) {
  function kelvinToMired (line 11) | function kelvinToMired(kelvin: number) {
  function miredToKelvin (line 15) | function miredToKelvin(mired: number) {

FILE: src/util/util.ts
  function remap (line 1) | function remap(
  function limit (line 13) | function limit(

FILE: test/FanAccessory.test.ts
  function createMockDevice (line 110) | function createMockDevice(codes: string[]): TuyaDevice {
  function setupAndConfigure (line 139) | function setupAndConfigure(codes: string[]) {
  function getDualLightServices (line 147) | function getDualLightServices() {
  function expectNoDualLight (line 153) | function expectNoDualLight() {
  function expectLightCall (line 157) | function expectLightCall(index: number, onCode: string, brightCode?: str...

FILE: test/Light.test.ts
  function makeSchema (line 61) | function makeSchema(code: string, type = 'Boolean', property: any = {}) {
  function brightSchema (line 65) | function brightSchema(code = 'bright_value') {

FILE: test/util.ts
  function expectDevice (line 12) | function expectDevice(device: TuyaDevice) {
  function expectDeviceSchema (line 30) | function expectDeviceSchema(schema: TuyaDeviceSchema) {
  function expectSuccessResponse (line 37) | function expectSuccessResponse(res: TuyaOpenAPIResponse) {
Condensed preview — 104 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (395K chars).
[
  {
    "path": ".eslintrc",
    "chars": 1339,
    "preview": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:@typescript-eslint/eslin"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 734,
    "preview": "# These are supported funding model platforms\n\ngithub: ['0x5e']\npatreon: # Replace with a single Patreon username\nopen_c"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 1659,
    "preview": "name: Bug report\ndescription: Create a report to help us improve\nlabels: ['bug']\nbody:\n- type: checkboxes\n  id: prerequi"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 193,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Homebridge Discord Community\n    url: https://discord.gg/homebridge"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 436,
    "preview": "name: Feature request\ndescription: Suggest an idea for this project\nlabels: ['enhancement']\nbody:\n  - type: textarea\n   "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/login_issue.yml",
    "chars": 1531,
    "preview": "name: Login issue\ndescription: Failed to login Tuya Cloud\nlabels: ['login issue']\nbody:\n  - type: checkboxes\n    id: pre"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 590,
    "preview": "name: Build and Lint\n\non: [push, pull_request]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast"
  },
  {
    "path": ".gitignore",
    "chars": 1902,
    "preview": "# Ignore compiled code\ndist\n\n# ------------- Defaults ------------- #\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\n"
  },
  {
    "path": ".npmignore",
    "chars": 1970,
    "preview": "# Ignore source code\nsrc\n\n# ------------- Defaults ------------- #\n\n# gitHub actions\n.github\n\n# eslint\n.eslintrc\n\n# type"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 59,
    "preview": "{\n  \"recommendations\": [\n    \"dbaeumer.vscode-eslint\"\n  ]\n}"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 152,
    "preview": "{\n  \"files.eol\": \"\\n\",\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\"\n  },\n  \"editor.rulers\": [ "
  },
  {
    "path": "ADVANCED_OPTIONS.md",
    "chars": 7326,
    "preview": "# Advanced Options\n\n**During the beta version, the options are unstable, may get changed during updates.**\n\nThe main fun"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 6154,
    "preview": "# Changelog\n\n## [1.7.0] - (unreleased)\n\n### Added\n- Add scene support. (#118)\n- Add Wireless Switch support (`wxkg`).\n- "
  },
  {
    "path": "LICENSE",
    "chars": 1089,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014-2021 Tuya Inc.\nPermission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "README.md",
    "chars": 12170,
    "preview": "# @0x5e/homebridge-tuya-platform\n\n[![npm](https://badgen.net/npm/v/@0x5e/homebridge-tuya-platform)](https://npmjs.com/pa"
  },
  {
    "path": "SUPPORTED_DEVICES.md",
    "chars": 19137,
    "preview": "# Supported Tuya Devices\n\nFirst-class category name, sedond-class category name, category code can be found here:\nhttps:"
  },
  {
    "path": "config.schema.json",
    "chars": 24841,
    "preview": "{\n    \"pluginAlias\": \"TuyaPlatform\",\n    \"pluginType\": \"platform\",\n    \"singular\": true,\n    \"headerDisplay\": \"\",\n    \"f"
  },
  {
    "path": "jest.config.js",
    "chars": 124,
    "preview": "/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n  preset: 'ts-jest',\n  testEnvironment: 'node',"
  },
  {
    "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": 1925,
    "preview": "{\n  \"name\": \"@0x5e/homebridge-tuya-platform\",\n  \"displayName\": \"Tuya\",\n  \"version\": \"1.7.0-beta.58\",\n  \"description\": \"F"
  },
  {
    "path": "src/accessory/AccessoryFactory.ts",
    "chars": 8464,
    "preview": "import { PlatformAccessory } from 'homebridge';\nimport TuyaDevice from '../device/TuyaDevice';\nimport { TuyaPlatform } f"
  },
  {
    "path": "src/accessory/AirConditionerAccessory.ts",
    "chars": 12867,
    "preview": "import { TuyaDeviceSchemaEnumProperty, TuyaDeviceSchemaIntegerProperty, TuyaDeviceStatus } from '../device/TuyaDevice';\n"
  },
  {
    "path": "src/accessory/AirPurifierAccessory.ts",
    "chars": 3164,
    "preview": "import { TuyaDeviceSchemaType } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessory';\nimport { confi"
  },
  {
    "path": "src/accessory/AirQualitySensorAccessory.ts",
    "chars": 1167,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureAirQuality } from './characteristic/AirQuality';\nimport {"
  },
  {
    "path": "src/accessory/BaseAccessory.ts",
    "chars": 11544,
    "preview": "/* eslint-disable @typescript-eslint/no-empty-function */\n/* eslint-disable @typescript-eslint/no-unused-vars */\nimport "
  },
  {
    "path": "src/accessory/CameraAccessory.ts",
    "chars": 4862,
    "preview": "import { TuyaDeviceStatus } from '../device/TuyaDevice';\nimport { TuyaStreamingDelegate } from '../util/TuyaStreamDelega"
  },
  {
    "path": "src/accessory/CarbonDioxideSensorAccessory.ts",
    "chars": 1693,
    "preview": "import { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDevice';\nimport { limit } from '../util/util';\nimport Bas"
  },
  {
    "path": "src/accessory/CarbonMonoxideSensorAccessory.ts",
    "chars": 1724,
    "preview": "import { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDevice';\nimport { limit } from '../util/util';\nimport Bas"
  },
  {
    "path": "src/accessory/CatToiletAccessory.ts",
    "chars": 4644,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureName } from './characteristic/Name';\nimport { configureOn"
  },
  {
    "path": "src/accessory/ContactSensorAccessory.ts",
    "chars": 840,
    "preview": "import BaseAccessory from './BaseAccessory';\n\nconst SCHEMA_CODE = {\n  CONTACT_STATE: ['doorcontact_state', 'switch'],\n};"
  },
  {
    "path": "src/accessory/DehumidifierAccessory.ts",
    "chars": 2983,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureActive } from './characteristic/Active';\nimport { configu"
  },
  {
    "path": "src/accessory/DiffuserAccessory.ts",
    "chars": 2259,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureActive } from './characteristic/Active';\nimport { configu"
  },
  {
    "path": "src/accessory/DimmerAccessory.ts",
    "chars": 3850,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchemaIntegerProperty, TuyaDeviceStatus } from '../device/TuyaD"
  },
  {
    "path": "src/accessory/DoorbellAccessory.ts",
    "chars": 3063,
    "preview": "import { TuyaDeviceStatus } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessory';\nimport { configure"
  },
  {
    "path": "src/accessory/ExtractionHoodAccessory.ts",
    "chars": 4428,
    "preview": "import { TuyaDeviceSchemaType } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessory';\nimport { confi"
  },
  {
    "path": "src/accessory/FanAccessory.ts",
    "chars": 5820,
    "preview": "import { TuyaDeviceSchemaType } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessory';\nimport { confi"
  },
  {
    "path": "src/accessory/GarageDoorAccessory.ts",
    "chars": 2225,
    "preview": "import BaseAccessory from './BaseAccessory';\n\nconst SCHEMA_CODE = {\n  CURRENT_DOOR_STATE: ['doorcontact_state'],\n  TARGE"
  },
  {
    "path": "src/accessory/HeaterAccessory.ts",
    "chars": 3640,
    "preview": "/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDe"
  },
  {
    "path": "src/accessory/HumanPresenceSensorAccessory.ts",
    "chars": 442,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureOccupancyDetected } from './characteristic/OccupancyDetec"
  },
  {
    "path": "src/accessory/HumidifierAccessory.ts",
    "chars": 5234,
    "preview": "import { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDevice';\nimport { limit, remap } from '../util/util';\nimp"
  },
  {
    "path": "src/accessory/IRAirConditionerAccessory.ts",
    "chars": 10717,
    "preview": "import debounce from 'debounce';\nimport BaseAccessory from './BaseAccessory';\n\nconst POWER_OFF = 0;\nconst POWER_ON = 1;\n"
  },
  {
    "path": "src/accessory/IRControlHubAccessory.ts",
    "chars": 1164,
    "preview": "import { TuyaDeviceStatus } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessory';\nimport { configure"
  },
  {
    "path": "src/accessory/IRGenericAccessory.ts",
    "chars": 1711,
    "preview": "import { TuyaIRRemoteKeyListItem } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessory';\nimport { co"
  },
  {
    "path": "src/accessory/LeakSensorAccessory.ts",
    "chars": 1098,
    "preview": "import BaseAccessory from './BaseAccessory';\n\nconst SCHEMA_CODE = {\n  LEAK: ['gas_sensor_status', 'gas_sensor_state', 'c"
  },
  {
    "path": "src/accessory/LightAccessory.ts",
    "chars": 1358,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureOn } from './characteristic/On';\nimport { configureMotion"
  },
  {
    "path": "src/accessory/LightSensorAccessory.ts",
    "chars": 991,
    "preview": "import { TuyaDeviceSchemaIntegerProperty } from '../device/TuyaDevice';\nimport { limit } from '../util/util';\nimport Bas"
  },
  {
    "path": "src/accessory/LockAccessory.ts",
    "chars": 1758,
    "preview": "import BaseAccessory from './BaseAccessory';\n\nconst SCHEMA_CODE = {\n  LOCK_CURRENT_STATE: ['open_close', 'closed_opened'"
  },
  {
    "path": "src/accessory/MotionSensorAccessory.ts",
    "chars": 400,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureMotionDetected } from './characteristic/MotionDetected';\n"
  },
  {
    "path": "src/accessory/OutletAccessory.ts",
    "chars": 169,
    "preview": "import SwitchAccessory from './SwitchAccessory';\n\nexport default class OutletAccessory extends SwitchAccessory {\n  mainS"
  },
  {
    "path": "src/accessory/PetFeederAccessory.ts",
    "chars": 4359,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureActive } from './characteristic/Active';\nimport { Charact"
  },
  {
    "path": "src/accessory/SaunaAccessory.ts",
    "chars": 4774,
    "preview": "import { TuyaDeviceSchemaIntegerProperty, TuyaDeviceStatus } from '../device/TuyaDevice';\nimport { limit } from '../util"
  },
  {
    "path": "src/accessory/SceneAccessory.ts",
    "chars": 1300,
    "preview": "import { PlatformAccessory } from 'homebridge';\nimport TuyaHomeDeviceManager from '../device/TuyaHomeDeviceManager';\nimp"
  },
  {
    "path": "src/accessory/SceneSwitchAccessory.ts",
    "chars": 1533,
    "preview": "import { TuyaDeviceSchema, TuyaDeviceSchemaType } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessor"
  },
  {
    "path": "src/accessory/SecuritySystemAccessory.ts",
    "chars": 1014,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureSecuritySystemCurrentState, configureSecuritySystemTarget"
  },
  {
    "path": "src/accessory/SmokeSensorAccessory.ts",
    "chars": 929,
    "preview": "import BaseAccessory from './BaseAccessory';\n\nconst SCHEMA_CODE = {\n  SENSOR_STATUS: ['smoke_sensor_status', 'smoke_sens"
  },
  {
    "path": "src/accessory/SwitchAccessory.ts",
    "chars": 3622,
    "preview": "import { TuyaDeviceSchema, TuyaDeviceSchemaType } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessor"
  },
  {
    "path": "src/accessory/TemperatureHumiditySensorAccessory.ts",
    "chars": 792,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureCurrentRelativeHumidity } from './characteristic/CurrentR"
  },
  {
    "path": "src/accessory/ThermostatAccessory.ts",
    "chars": 6692,
    "preview": "import { TuyaDeviceSchemaEnumProperty, TuyaDeviceSchemaIntegerProperty, TuyaDeviceStatus } from '../device/TuyaDevice';\n"
  },
  {
    "path": "src/accessory/ValveAccessory.ts",
    "chars": 1701,
    "preview": "import { TuyaDeviceSchema, TuyaDeviceSchemaType } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessor"
  },
  {
    "path": "src/accessory/VibrationSensorAccessory.ts",
    "chars": 1413,
    "preview": "import { TuyaDeviceStatus } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessory';\n\nconst SCHEMA_CODE"
  },
  {
    "path": "src/accessory/WeatherStationAccessory.ts",
    "chars": 2272,
    "preview": "import BaseAccessory from './BaseAccessory';\n\nexport default class TemperatureHumiditySensorAccessory extends BaseAccess"
  },
  {
    "path": "src/accessory/WhiteNoiseLightAccessory.ts",
    "chars": 1972,
    "preview": "import BaseAccessory from './BaseAccessory';\nimport { configureOn } from './characteristic/On';\nimport { configureLight "
  },
  {
    "path": "src/accessory/WindowAccessory.ts",
    "chars": 276,
    "preview": "import WindowCoveringAccessory from './WindowCoveringAccessory';\n\nexport default class WindowAccessory extends WindowCov"
  },
  {
    "path": "src/accessory/WindowCoveringAccessory.ts",
    "chars": 5695,
    "preview": "import { TuyaDeviceSchemaEnumProperty } from '../device/TuyaDevice';\nimport { limit } from '../util/util';\nimport BaseAc"
  },
  {
    "path": "src/accessory/WirelessSwitchAccessory.ts",
    "chars": 1560,
    "preview": "import { TuyaDeviceSchema, TuyaDeviceStatus } from '../device/TuyaDevice';\nimport BaseAccessory from './BaseAccessory';\n"
  },
  {
    "path": "src/accessory/characteristic/Active.ts",
    "chars": 756,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema } from '../../device/TuyaDevice';\nimport BaseAccessory f"
  },
  {
    "path": "src/accessory/characteristic/AirQuality.ts",
    "chars": 2814,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema, TuyaDeviceSchemaIntegerProperty, TuyaDeviceSchemaType }"
  },
  {
    "path": "src/accessory/characteristic/CurrentRelativeHumidity.ts",
    "chars": 915,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema, TuyaDeviceSchemaIntegerProperty } from '../../device/Tu"
  },
  {
    "path": "src/accessory/characteristic/CurrentTemperature.ts",
    "chars": 1210,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema, TuyaDeviceSchemaIntegerProperty } from '../../device/Tu"
  },
  {
    "path": "src/accessory/characteristic/EnergyUsage.ts",
    "chars": 3632,
    "preview": "import BaseAccessory from '../BaseAccessory';\nimport { API, Service } from 'homebridge';\nimport { TuyaDeviceSchema, Tuya"
  },
  {
    "path": "src/accessory/characteristic/Light.ts",
    "chars": 13743,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema, TuyaDeviceSchemaEnumProperty, TuyaDeviceSchemaIntegerPr"
  },
  {
    "path": "src/accessory/characteristic/LockPhysicalControls.ts",
    "chars": 831,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema } from '../../device/TuyaDevice';\nimport BaseAccessory f"
  },
  {
    "path": "src/accessory/characteristic/MotionDetected.ts",
    "chars": 773,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema, TuyaDeviceSchemaType } from '../../device/TuyaDevice';\n"
  },
  {
    "path": "src/accessory/characteristic/Name.ts",
    "chars": 521,
    "preview": "import { Service } from 'homebridge';\nimport BaseAccessory from '../BaseAccessory';\n\nexport function configureName(acces"
  },
  {
    "path": "src/accessory/characteristic/OccupancyDetected.ts",
    "chars": 823,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema } from '../../device/TuyaDevice';\nimport BaseAccessory f"
  },
  {
    "path": "src/accessory/characteristic/On.ts",
    "chars": 822,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema } from '../../device/TuyaDevice';\nimport BaseAccessory f"
  },
  {
    "path": "src/accessory/characteristic/ProgrammableSwitchEvent.ts",
    "chars": 3289,
    "preview": "import { CharacteristicProps, PartialAllowingNull, Service } from 'homebridge';\nimport { TuyaDeviceSchema, TuyaDeviceSch"
  },
  {
    "path": "src/accessory/characteristic/RelativeHumidityDehumidifierThreshold.ts",
    "chars": 1179,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema, TuyaDeviceSchemaIntegerProperty } from '../../device/Tu"
  },
  {
    "path": "src/accessory/characteristic/RotationSpeed.ts",
    "chars": 3091,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema, TuyaDeviceSchemaEnumProperty, TuyaDeviceSchemaIntegerPr"
  },
  {
    "path": "src/accessory/characteristic/SecuritySystemState.ts",
    "chars": 3734,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema } from '../../device/TuyaDevice';\nimport BaseAccessory f"
  },
  {
    "path": "src/accessory/characteristic/SwingMode.ts",
    "chars": 765,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema } from '../../device/TuyaDevice';\nimport BaseAccessory f"
  },
  {
    "path": "src/accessory/characteristic/TemperatureDisplayUnits.ts",
    "chars": 1017,
    "preview": "import { Service } from 'homebridge';\nimport { TuyaDeviceSchema } from '../../device/TuyaDevice';\nimport BaseAccessory f"
  },
  {
    "path": "src/config.ts",
    "chars": 2280,
    "preview": "import { PlatformConfig } from 'homebridge';\nimport { TuyaDeviceSchemaProperty, TuyaDeviceSchemaType } from './device/Tu"
  },
  {
    "path": "src/core/TuyaOpenAPI.ts",
    "chars": 12545,
    "preview": "/* eslint-disable max-len */\n/* eslint-disable @typescript-eslint/no-empty-function */\n/* eslint-disable @typescript-esl"
  },
  {
    "path": "src/core/TuyaOpenMQ.ts",
    "chars": 6849,
    "preview": "import mqtt from 'mqtt';\nimport { v4 as uuid_v4 } from 'uuid';\nimport Crypto from 'crypto';\nimport CryptoJS from 'crypto"
  },
  {
    "path": "src/device/TuyaCustomDeviceManager.ts",
    "chars": 2086,
    "preview": "import TuyaOpenAPI from '../core/TuyaOpenAPI';\nimport TuyaDevice from './TuyaDevice';\nimport TuyaDeviceManager from './T"
  },
  {
    "path": "src/device/TuyaDevice.ts",
    "chars": 2730,
    "preview": "\nexport enum TuyaDeviceSchemaMode {\n  UNKNOWN = '',\n  READ_WRITE = 'rw',\n  READ_ONLY = 'ro',\n  WRITE_ONLY = 'wo',\n}\n\nexp"
  },
  {
    "path": "src/device/TuyaDeviceManager.ts",
    "chars": 11251,
    "preview": "import EventEmitter from 'events';\nimport TuyaOpenAPI from '../core/TuyaOpenAPI';\nimport TuyaOpenMQ from '../core/TuyaOp"
  },
  {
    "path": "src/device/TuyaHomeDeviceManager.ts",
    "chars": 1904,
    "preview": "import TuyaDevice from './TuyaDevice';\nimport TuyaDeviceManager from './TuyaDeviceManager';\n\nexport default class TuyaHo"
  },
  {
    "path": "src/index.ts",
    "chars": 266,
    "preview": "import { API } from 'homebridge';\n\nimport { PLATFORM_NAME } from './settings';\nimport { TuyaPlatform } from './platform'"
  },
  {
    "path": "src/platform.ts",
    "chars": 17172,
    "preview": "import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebrid"
  },
  {
    "path": "src/settings.ts",
    "chars": 488,
    "preview": "// eslint-disable-next-line\n// @ts-ignore\nimport { pluginAlias as platformName } from '../config.schema.json';\n\n// eslin"
  },
  {
    "path": "src/util/FfmpegStreamingProcess.ts",
    "chars": 4626,
    "preview": "import {\n  ChildProcessWithoutNullStreams,\n  spawn,\n} from 'child_process';\nimport {\n  StreamRequestCallback,\n  StreamSe"
  },
  {
    "path": "src/util/Logger.ts",
    "chars": 1150,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport default interface Logger {\n  info(message?: any, ...args"
  },
  {
    "path": "src/util/TuyaRecordingDelegate.ts",
    "chars": 913,
    "preview": "/* eslint-disable @typescript-eslint/no-unused-vars */\nimport {\n  CameraRecordingConfiguration,\n  CameraRecordingDelegat"
  },
  {
    "path": "src/util/TuyaStreamDelegate.ts",
    "chars": 14199,
    "preview": "/* eslint-disable @typescript-eslint/no-unused-vars */\n/* eslint-disable max-len */\n\nimport {\n  AudioStreamingCodecType,"
  },
  {
    "path": "src/util/color.ts",
    "chars": 419,
    "preview": "import convert from 'color-convert';\nimport kelvinToRgb from 'kelvin-to-rgb';\n\nexport function kelvinToHSV(kevin: number"
  },
  {
    "path": "src/util/util.ts",
    "chars": 446,
    "preview": "export function remap(\n  value: number,\n  srcStart: number,\n  srcEnd: number,\n  dstStart: number,\n  dstEnd: number,\n) {\n"
  },
  {
    "path": "test/FanAccessory.test.ts",
    "chars": 15903,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { describe, expect, test, jest, beforeEach } from '@jest/"
  },
  {
    "path": "test/Light.test.ts",
    "chars": 5002,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { describe, expect, test, jest, beforeEach } from '@jest/"
  },
  {
    "path": "test/custom.test.ts",
    "chars": 2886,
    "preview": "/* eslint-disable no-console */\nimport { describe, expect, test } from '@jest/globals';\n\nimport TuyaOpenAPI from '../src"
  },
  {
    "path": "test/home.test.ts",
    "chars": 2284,
    "preview": "/* eslint-disable no-console */\nimport { describe, expect, test } from '@jest/globals';\n\nimport TuyaOpenAPI from '../src"
  },
  {
    "path": "test/util.ts",
    "chars": 1356,
    "preview": "import fs from 'fs';\nimport { PLATFORM_NAME } from '../src/settings';\nimport { TuyaPlatformConfig } from '../src/config'"
  },
  {
    "path": "tsconfig.json",
    "chars": 497,
    "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 0x5e/homebridge-tuya-platform GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 104 files (366.1 KB), approximately 91.1k tokens, and a symbol index with 554 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!