## Summary
Unsupported or partially supported Tuya device report.
- Device nam…e: `Clim Salle d'attente`
- External ID: `tuya:bf5638fc7cc3f25e32i45q`
- Product ID: `f3goccgfj6qino4c`
- Product key: `keyquxnsj75xc8se`
- Model: `Air Conditioner`
- Category: `kt`
- Online: `true`
- Local override: `true`
- Local poll status: `Success`
- Local poll protocol: `3.3`
## Implementation Guide
Implement the Tuya support first, then finalize the tests:
1. Add or update the Tuya cloud mapping in `server/services/tuya/lib/mappings/cloud/`.
2. Add or update the Tuya local mapping in `server/services/tuya/lib/mappings/local/`.
3. Register or update the device definition in `server/services/tuya/lib/mappings/index.js`.
4. If the new feature needs a new Gladys `category/type`, add it in `server/utils/constants.js`.
5. If the new feature uses a new `category/type` not yet handled by Tuya reading, update `readValues` in `server/services/tuya/lib/device/tuya.deviceMapping.js`.
6. If the feature should be writable from Gladys, also update `writeValues` in `server/services/tuya/lib/device/tuya.deviceMapping.js`.
## Maintainer Notes
This issue contains:
- the complete structured Tuya report
- suggested fixture input files for onboarding tests
- raw cloud responses
- local poll DPS when available
This issue does not contain:
- `expected-device.json`
- `expected-events.json` or `expected-cloud-events.json` / `expected-local-events.json`
- `expected-local-mapping.json`
These expected files should still be reviewed and created manually.
`poll-device.json` is only a starting point based on the current support level in Gladys.
After adding or updating mappings, it should be completed with the final supported features and mapping metadata.
## Data Coverage
- `GET /v1.0/users/{sourceId}/devices` -> `Device List Entry` below
- `GET /v1.2/iot-03/devices/{deviceId}/specification` -> `input-device.json > specifications`
- `GET /v1.0/iot-03/devices/{deviceId}` -> `input-device.json` top-level metadata
- `GET /v2.0/cloud/thing/{deviceId}/shadow/properties` -> `input-device.json > properties` and `cloud-status.json`
- `GET /v2.0/cloud/thing/{deviceId}/model` -> `input-device.json > thing_model`
- local poll -> `local-dps.json` when available
## Suggested Fixture Folder
```text
air-conditioner-f3goccgfj6qino4c
```
Example:
```text
server/test/services/tuya/fixtures/devices/air-conditioner-f3goccgfj6qino4c
```
## Suggested File: manifest.js
```js
module.exports = {
name: 'Clim Salle d\'attente',
convertDevice: {
input: './input-device.json',
expected: './expected-device.json',
},
pollCloud: {
device: './poll-device.json',
response: './cloud-status.json',
expectedEvents: './expected-cloud-events.json',
},
pollLocal: {
device: './poll-device.json',
dps: './local-dps.json',
expectedEvents: './expected-local-events.json',
expectedCloudRequests: 0,
},
localMapping: {
device: './poll-device.json',
expected: './expected-local-mapping.json',
},
};
```
## Suggested File: input-device.json
Use this as the base input for `convertDevice`.
```json
{
"id": "bf5638fc7cc3f25e32i45q",
"name": "Clim Salle d'attente",
"product_name": "Air Conditioner",
"model": "Air Conditioner",
"product_id": "f3goccgfj6qino4c",
"product_key": "keyquxnsj75xc8se",
"local_key": "***",
"ip": "10.x.x.x",
"cloud_ip": "82.x.x.x",
"protocol_version": "3.3",
"local_override": true,
"online": true,
"specifications": {
"category": "kt",
"functions": [
{
"code": "switch",
"lang_config": {
"false": "",
"true": ""
},
"name": "开关",
"type": "Boolean",
"values": "{}"
},
{
"code": "eco",
"lang_config": {
"false": "",
"true": ""
},
"name": "ECO模式",
"type": "Boolean",
"values": "{}"
},
{
"code": "drying",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": "Dry",
"type": "Boolean",
"values": "{}"
},
{
"code": "cleaning",
"lang_config": {
"false": "",
"true": ""
},
"name": "自清洁",
"type": "Boolean",
"values": "{}"
},
{
"code": "temp_unit_convert",
"lang_config": {
"c": "℃",
"f": "℉"
},
"name": "Display Units",
"type": "Enum",
"values": "{\"range\":[\"c\",\"f\"]}"
},
{
"code": "temp_set",
"lang_config": {
"unit": "℃"
},
"name": "Set Temp",
"type": "Integer",
"values": "{\"unit\":\"℃\",\"min\":160,\"max\":880,\"scale\":1,\"step\":10}"
},
{
"code": "mode",
"lang_config": {
"auto": "Auto",
"cold": "Cool",
"hot": "Heat",
"wind": "Vent"
},
"name": "Mode",
"type": "Enum",
"values": "{\"range\":[\"auto\",\"cold\",\"wet\",\"heat\",\"fan\"]}"
},
{
"code": "heat",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": " Auxiliary Heat",
"type": "Boolean",
"values": "{}"
},
{
"code": "light",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": "Light",
"type": "Boolean",
"values": "{}"
},
{
"code": "sleep",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": "Sleep",
"type": "Boolean",
"values": "{}"
},
{
"code": "health",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": "Health",
"type": "Boolean",
"values": "{}"
},
{
"code": "fan_speed_enum",
"lang_config": {
"auto": "Auto",
"high": "High",
"level_2": "Low-Med",
"level_4": "Med-High",
"low": "Low",
"middle": "Medium",
"mute": "Quiet",
"strong": "Turbo"
},
"name": "WindSpeed",
"type": "Enum",
"values": "{\"range\":[\"auto\",\"low\",\"low_mid\",\"mid\",\"mid_high\",\"high\",\"mute\",\"turbo\"]}"
}
],
"status": [
{
"code": "switch",
"lang_config": {
"false": "",
"true": ""
},
"name": "开关",
"type": "Boolean",
"values": "{}"
},
{
"code": "fan_speed_enum",
"lang_config": {
"auto": "Auto",
"high": "High",
"level_2": "Low-Med",
"level_4": "Med-High",
"low": "Low",
"middle": "Medium",
"mute": "Quiet",
"strong": "Turbo"
},
"name": "WindSpeed",
"type": "Enum",
"values": "{\"range\":[\"auto\",\"low\",\"low_mid\",\"mid\",\"mid_high\",\"high\",\"mute\",\"turbo\"]}"
},
{
"code": "eco",
"lang_config": {
"false": "",
"true": ""
},
"name": "ECO模式",
"type": "Boolean",
"values": "{}"
},
{
"code": "drying",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": "Dry",
"type": "Boolean",
"values": "{}"
},
{
"code": "cleaning",
"lang_config": {
"false": "",
"true": ""
},
"name": "自清洁",
"type": "Boolean",
"values": "{}"
},
{
"code": "temp_unit_convert",
"lang_config": {
"c": "℃",
"f": "℉"
},
"name": "Display Units",
"type": "Enum",
"values": "{\"range\":[\"c\",\"f\"]}"
},
{
"code": "temp_set",
"lang_config": {
"unit": "℃"
},
"name": "Set Temp",
"type": "Integer",
"values": "{\"unit\":\"℃\",\"min\":160,\"max\":880,\"scale\":1,\"step\":10}"
},
{
"code": "temp_current",
"lang_config": {
"unit": "℃"
},
"name": "Temp Current",
"type": "Integer",
"values": "{\"unit\":\"℃\",\"min\":0,\"max\":600,\"scale\":1,\"step\":1}"
},
{
"code": "mode",
"lang_config": {
"auto": "Auto",
"cold": "Cool",
"hot": "Heat",
"wind": "Vent"
},
"name": "Mode",
"type": "Enum",
"values": "{\"range\":[\"auto\",\"cold\",\"wet\",\"heat\",\"fan\"]}"
},
{
"code": "heat",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": " Auxiliary Heat",
"type": "Boolean",
"values": "{}"
},
{
"code": "light",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": "Light",
"type": "Boolean",
"values": "{}"
},
{
"code": "countdown_left",
"lang_config": {
"unit": "Min"
},
"name": "Left Time",
"type": "Integer",
"values": "{\"unit\":\"分钟\",\"min\":0,\"max\":1440,\"scale\":0,\"step\":1}"
},
{
"code": "sleep",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": "Sleep",
"type": "Boolean",
"values": "{}"
},
{
"code": "health",
"lang_config": {
"false": "OFF",
"true": "ON"
},
"name": "Health",
"type": "Boolean",
"values": "{}"
}
]
},
"properties": {
"properties": [
{
"code": "Power",
"custom_name": "",
"dp_id": 1,
"time": 1772521207887,
"type": "bool",
"value": true
},
{
"code": "temp_set",
"custom_name": "",
"dp_id": 2,
"time": 1772522965366,
"type": "value",
"value": 200
},
{
"code": "temp_current",
"custom_name": "",
"dp_id": 3,
"time": 1772553646892,
"type": "value",
"value": 230
},
{
"code": "mode",
"custom_name": "",
"dp_id": 4,
"time": 1772521207672,
"type": "enum",
"value": "heat"
},
{
"code": "windspeed",
"custom_name": "",
"dp_id": 5,
"time": 1770803041733,
"type": "enum",
"value": "auto"
},
{
"code": "mode_ECO",
"custom_name": "",
"dp_id": 8,
"time": 1770474510177,
"type": "bool",
"value": false
},
{
"code": "mode_dry",
"custom_name": "",
"dp_id": 9,
"time": 1770474510177,
"type": "bool",
"value": false
},
{
"code": "heat",
"custom_name": "",
"dp_id": 12,
"time": 1772522965366,
"type": "bool",
"value": true
},
{
"code": "light",
"custom_name": "",
"dp_id": 13,
"time": 1770642835259,
"type": "bool",
"value": true
},
{
"code": "windshake",
"custom_name": "",
"dp_id": 15,
"time": 1770642484099,
"type": "enum",
"value": "off"
},
{
"code": "Fault",
"custom_name": "",
"dp_id": 20,
"time": 1770474510259,
"type": "bitmap",
"value": 0
},
{
"code": "Countdown",
"custom_name": "",
"dp_id": 21,
"time": 1770474506674,
"type": "enum",
"value": "0"
},
{
"code": "countdown_left",
"custom_name": "",
"dp_id": 22,
"time": 1770474508806,
"type": "value",
"value": 0
},
{
"code": "use_number",
"custom_name": "",
"dp_id": 101,
"time": 1770474510421,
"type": "value",
"value": 0
},
{
"code": "total_time",
"custom_name": "",
"dp_id": 102,
"time": 1772553334896,
"type": "value",
"value": 151
},
{
"code": "electricity",
"custom_name": "",
"dp_id": 103,
"time": 1772552853000,
"type": "value",
"value": 1
},
{
"code": "electricity_number",
"custom_name": "",
"dp_id": 104,
"time": 1770474506674,
"type": "value",
"value": 0
},
{
"code": "unit",
"custom_name": "",
"dp_id": 105,
"time": 1771803944077,
"type": "enum",
"value": "c"
},
{
"code": "horizontal",
"custom_name": "",
"dp_id": 106,
"time": 1770642446631,
"type": "enum",
"value": "off"
},
{
"code": "vertical",
"custom_name": "",
"dp_id": 107,
"time": 1770642484099,
"type": "enum",
"value": "off"
},
{
"code": "swing3d",
"custom_name": "",
"dp_id": 108,
"time": 1770474510177,
"type": "bool",
"value": false
},
{
"code": "sleep",
"custom_name": "",
"dp_id": 109,
"time": 1770641883241,
"type": "bool",
"value": false
},
{
"code": "health",
"custom_name": "",
"dp_id": 110,
"time": 1770642739109,
"type": "bool",
"value": true
},
{
"code": "clean",
"custom_name": "",
"dp_id": 111,
"time": 1770474510177,
"type": "bool",
"value": false
},
{
"code": "type",
"custom_name": "",
"dp_id": 112,
"time": 1770474510177,
"type": "enum",
"value": "cold"
},
{
"code": "fault2",
"custom_name": "",
"dp_id": 113,
"time": 1770474510259,
"type": "bitmap",
"value": 0
},
{
"code": "current_mode",
"custom_name": "",
"dp_id": 114,
"time": 1770474506674,
"type": "enum",
"value": "cold"
},
{
"code": "heat8",
"custom_name": "",
"dp_id": 115,
"time": 1770642896790,
"type": "bool",
"value": false
}
]
},
"thing_model": {
"modelId": "0000003fy5",
"services": [
{
"actions": [],
"code": "",
"description": "",
"events": [],
"name": "默认服务",
"properties": [
{
"abilityId": 1,
"accessMode": "rw",
"code": "Power",
"description": "",
"extensions": {
"iconName": "icon-dp_power",
"attribute": "1"
},
"name": "开关",
"typeSpec": {
"type": "bool"
}
},
{
"abilityId": 2,
"accessMode": "rw",
"code": "temp_set",
"description": "摄氏度:16-31\n华氏度:61-88",
"extensions": {
"iconName": "icon-dp_temp"
},
"name": "设置温度",
"typeSpec": {
"type": "value",
"max": 880,
"min": 160,
"scale": 1,
"step": 10,
"unit": "℃"
}
},
{
"abilityId": 3,
"accessMode": "ro",
"code": "temp_current",
"description": "",
"extensions": {
"iconName": "icon-dp_sun"
},
"name": "当前温度",
"typeSpec": {
"type": "value",
"max": 600,
"min": 0,
"scale": 1,
"step": 1,
"unit": "℃"
}
},
{
"abilityId": 4,
"accessMode": "rw",
"code": "mode",
"description": "自动/制冷/除湿/制热/送风",
"extensions": {
"iconName": "icon-dp_mode"
},
"name": "工作模式",
"typeSpec": {
"type": "enum",
"range": [
"auto",
"cold",
"wet",
"heat",
"fan"
]
}
},
{
"abilityId": 5,
"accessMode": "rw",
"code": "windspeed",
"description": "自动/低风/中低风/中风/中高风/高风/静音/超强",
"extensions": {
"iconName": "icon-dp_wind"
},
"name": "风速",
"typeSpec": {
"type": "enum",
"range": [
"auto",
"low",
"low_mid",
"mid",
"mid_high",
"high",
"mute",
"turbo"
]
}
},
{
"abilityId": 8,
"accessMode": "rw",
"code": "mode_ECO",
"description": "",
"extensions": {
"iconName": "icon-eco"
},
"name": "ECO模式",
"typeSpec": {
"type": "bool"
}
},
{
"abilityId": 9,
"accessMode": "rw",
"code": "mode_dry",
"description": "",
"extensions": {
"iconName": "icon-dp_flower"
},
"name": "干燥模式",
"typeSpec": {
"type": "bool"
}
},
{
"abilityId": 12,
"accessMode": "rw",
"code": "heat",
"description": "",
"extensions": {
"iconName": "icon-dp_light2"
},
"name": "辅热",
"typeSpec": {
"type": "bool"
}
},
{
"abilityId": 13,
"accessMode": "rw",
"code": "light",
"description": "",
"extensions": {
"iconName": "icon-dp_light"
},
"name": "灯光",
"typeSpec": {
"type": "bool"
}
},
{
"abilityId": 15,
"accessMode": "rw",
"code": "windshake",
"description": "上下/左右/全部/关闭",
"extensions": {
"iconName": "icon-dp_shake"
},
"name": "风摆",
"typeSpec": {
"type": "enum",
"range": [
"un_down",
"left_right",
"all",
"off"
]
}
},
{
"abilityId": 20,
"accessMode": "ro",
"code": "Fault",
"description": "",
"extensions": {
"iconName": "icon-dp_warming",
"scope": "fault"
},
"name": "故障告警",
"typeSpec": {
"type": "bitmap",
"label": [
"CL",
"E4",
"E5",
"H6",
"H9",
"HE",
"L0",
"L1",
"L2",
"L3",
"L6",
"L7",
"L8",
"L9",
"LA",
"Ld",
"P0",
"P1",
"P6",
"P8",
"PA",
"PC",
"Pd",
"PE"
],
"maxlen": 24
}
},
{
"abilityId": 21,
"accessMode": "rw",
"code": "Countdown",
"description": "0:取消,1-6小时。可根据产品实际功能进行设定。",
"extensions": {
"iconName": "icon-dp_time2"
},
"name": "倒计时",
"typeSpec": {
"type": "enum",
"range": [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24"
]
}
},
{
"abilityId": 22,
"accessMode": "ro",
"code": "countdown_left",
"description": "",
"extensions": {
"iconName": "icon-dp_time"
},
"name": "倒计时剩余时间",
"typeSpec": {
"type": "value",
"max": 1440,
"min": 0,
"scale": 0,
"step": 1,
"unit": "分钟"
}
},
{
"abilityId": 101,
"accessMode": "ro",
"code": "use_number",
"description": "",
"name": "使用时间上报次数",
"typeSpec": {
"type": "value",
"max": 100000,
"min": 0,
"scale": 0,
"step": 1,
"unit": ""
}
},
{
"abilityId": 102,
"accessMode": "ro",
"code": "total_time",
"description": "",
"name": "使用时间",
"typeSpec": {
"type": "value",
"max": 70000,
"min": 0,
"scale": 0,
"step": 1,
"unit": ""
}
},
{
"abilityId": 103,
"accessMode": "ro",
"code": "electricity",
"description": "",
"extensions": {
"trigger": "direct"
},
"name": "耗电量",
"typeSpec": {
"type": "value",
"max": 70000,
"min": 0,
"scale": 1,
"step": 1,
"unit": ""
}
},
{
"abilityId": 104,
"accessMode": "ro",
"code": "electricity_number",
"description": "",
"name": "耗电量上报次数",
"typeSpec": {
"type": "value",
"max": 10000,
"min": 0,
"scale": 0,
"step": 1,
"unit": ""
}
},
{
"abilityId": 105,
"accessMode": "rw",
"code": "unit",
"description": "",
"name": "温标切换",
"typeSpec": {
"type": "enum",
"range": [
"c",
"f"
]
}
},
{
"abilityId": 106,
"accessMode": "rw",
"code": "horizontal",
"description": "关闭/同相扫风/相向扫风",
"name": "左右扫风",
"typeSpec": {
"type": "enum",
"range": [
"off",
"same",
"opposite"
]
}
},
{
"abilityId": 107,
"accessMode": "rw",
"code": "vertical",
"description": "关闭/15扫风/位置1/位置2/位置3/位置4/位置5",
"name": "上下扫风",
"typeSpec": {
"type": "enum",
"range": [
"off",
"15",
"1",
"2",
"3",
"4",
"5"
]
}
},
{
"abilityId": 108,
"accessMode": "rw",
"code": "swing3d",
"description": "",
"name": "3d扫风",
"typeSpec": {
"type": "bool"
}
},
{
"abilityId": 109,
"accessMode": "rw",
"code": "sleep",
"description": "",
"name": "睡眠",
"typeSpec": {
"type": "bool"
}
},
{
"abilityId": 110,
"accessMode": "rw",
"code": "health",
"description": "",
"name": "健康",
"typeSpec": {
"type": "bool"
}
},
{
"abilityId": 111,
"accessMode": "rw",
"code": "clean",
"description": "",
"name": "清洁",
"typeSpec": {
"type": "bool"
}
},
{
"abilityId": 112,
"accessMode": "ro",
"code": "type",
"description": "单冷/冷暖",
"name": "机型",
"typeSpec": {
"type": "enum",
"range": [
"cold",
"cold_heat"
]
}
},
{
"abilityId": 113,
"accessMode": "ro",
"code": "fault2",
"description": "",
"extensions": {
"scope": "fault"
},
"name": "故障告警2",
"typeSpec": {
"type": "bitmap",
"label": [
"PF",
"SC",
"U0",
"U1",
"U2",
"U3",
"U4",
"U5",
"U6",
"U7",
"U8",
"U9",
"UC",
"Ud",
"E1",
"E2"
],
"maxlen": 16
}
},
{
"abilityId": 114,
"accessMode": "ro",
"code": "current_mode",
"description": "",
"name": "当前模式",
"typeSpec": {
"type": "enum",
"range": [
"cold",
"wet",
"heat",
"fan"
]
}
},
{
"abilityId": 115,
"accessMode": "rw",
"code": "heat8",
"description": "",
"name": "八度制热",
"typeSpec": {
"type": "bool"
}
}
]
}
]
}
}
```
## Suggested File: poll-device.json
Use this as a base Gladys device for cloud/local poll tests.
Important:
- this file reflects the support level available when the issue was created
- after implementing support in mappings, update `device_type`, `features`, and `tuya_mapping` to match the final Gladys support
- if the final support changes significantly, prefer rebuilding this file from the resulting converted device
```json
{
"external_id": "tuya:bf5638fc7cc3f25e32i45q",
"params": [
{
"name": "DEVICE_ID",
"value": "bf5638fc7cc3f25e32i45q"
},
{
"name": "LOCAL_KEY",
"value": "***"
},
{
"name": "CLOUD_IP",
"value": "82.x.x.x"
},
{
"name": "LOCAL_OVERRIDE",
"value": true
},
{
"name": "PRODUCT_ID",
"value": "f3goccgfj6qino4c"
},
{
"name": "CLOUD_STRATEGY",
"value": "legacy"
},
{
"name": "IP_ADDRESS",
"value": "10.x.x.x"
},
{
"name": "PROTOCOL_VERSION",
"value": "3.3"
},
{
"name": "PRODUCT_KEY",
"value": "keyquxnsj75xc8se"
}
],
"features": [
{
"name": "switch",
"external_id": "tuya:bf5638fc7cc3f25e32i45q:switch",
"selector": "tuya:bf5638fc7cc3f25e32i45q:switch",
"read_only": false,
"has_feedback": true,
"min": 0,
"max": 1,
"category": "switch",
"type": "binary"
},
{
"name": "Power",
"external_id": "tuya:bf5638fc7cc3f25e32i45q:Power",
"selector": "tuya:bf5638fc7cc3f25e32i45q:Power",
"read_only": false,
"has_feedback": true,
"min": 0,
"max": 1,
"category": "switch",
"type": "binary"
}
],
"tuya_mapping": {
"ignored_local_dps": [],
"ignored_cloud_codes": []
}
}
```
## Suggested File: cloud-status.json
Use this as the cloud poll response fixture.
Derived from `cloud.assembled.properties`.
```json
{
"result": [
{
"code": "Power",
"value": true
},
{
"code": "temp_set",
"value": 200
},
{
"code": "temp_current",
"value": 230
},
{
"code": "mode",
"value": "heat"
},
{
"code": "windspeed",
"value": "auto"
},
{
"code": "mode_ECO",
"value": false
},
{
"code": "mode_dry",
"value": false
},
{
"code": "heat",
"value": true
},
{
"code": "light",
"value": true
},
{
"code": "windshake",
"value": "off"
},
{
"code": "Fault",
"value": 0
},
{
"code": "Countdown",
"value": "0"
},
{
"code": "countdown_left",
"value": 0
},
{
"code": "use_number",
"value": 0
},
{
"code": "total_time",
"value": 151
},
{
"code": "electricity",
"value": 1
},
{
"code": "electricity_number",
"value": 0
},
{
"code": "unit",
"value": "c"
},
{
"code": "horizontal",
"value": "off"
},
{
"code": "vertical",
"value": "off"
},
{
"code": "swing3d",
"value": false
},
{
"code": "sleep",
"value": false
},
{
"code": "health",
"value": true
},
{
"code": "clean",
"value": false
},
{
"code": "type",
"value": "cold"
},
{
"code": "fault2",
"value": 0
},
{
"code": "current_mode",
"value": "cold"
},
{
"code": "heat8",
"value": false
}
]
}
```
## Suggested File: local-dps.json
Use this as the local poll DPS fixture.
```json
{
"1": true,
"2": 200,
"3": 230,
"4": "heat",
"5": "auto",
"8": false,
"9": false,
"12": true,
"13": true,
"15": "off",
"20": 0,
"22": 0,
"101": 0,
"102": 151,
"103": 1,
"105": "c",
"106": "off",
"107": "off",
"108": false,
"109": false,
"110": true,
"111": false,
"112": "cold",
"113": 0,
"115": false
}
```
## Suggested File: local-scan.json
Use this only if a local scan payload exists.
```json
{
"ip": "10.x.x.x",
"version": "3.3",
"productKey": "keyquxnsj75xc8se"
}
```
## Manual Files Guide
Build the remaining files manually after the Tuya mappings are in place:
1. Paste `input-device.json` from this issue, then add or update the Tuya mappings in `server/services/tuya/lib/mappings/cloud/`, `server/services/tuya/lib/mappings/local/`, and `server/services/tuya/lib/mappings/index.js`.
2. Create `expected-device.json` from the final Gladys device returned by `convertDevice`. This corresponds to the Tuya device as it should appear in the Discover page once support is implemented.
3. Build or complete `poll-device.json` from the final supported Gladys features. It should contain the final `device_type`, `features`, params, and `tuya_mapping` metadata.
4. Copy `cloud-status.json` from the cloud property values and `local-dps.json` from the local poll DPS payload when available.
5. Create `expected-events.json` if cloud and local polling should emit the same states. If they differ, keep separate `expected-cloud-events.json` and `expected-local-events.json` files instead.
6. Create `expected-local-mapping.json` from the final supported features present in `poll-device.json`, using the resolved DPS returned by the local mapping.
## Device List Entry
Source: `GET /v1.0/users/{sourceId}/devices`.
```json
{
"active_time": 1770474506,
"biz_type": 18,
"category": "kt",
"create_time": 1770474506,
"icon": "smart/program_category_icon/kt.png",
"id": "bf5638fc7cc3f25e32i45q",
"ip": "82.x.x.x",
"lat": "49.6",
"local_key": "***",
"lon": "0.33",
"model": "",
"name": "Clim Salle d'attente",
"online": true,
"owner_id": "20747383",
"product_id": "f3goccgfj6qino4c",
"product_name": "Air Conditioner",
"status": [
{
"code": "switch",
"value": true
},
{
"code": "temp_set",
"value": 200
},
{
"code": "temp_current",
"value": 230
},
{
"code": "mode",
"value": "hot"
},
{
"code": "fan_speed_enum",
"value": "auto"
},
{
"code": "eco",
"value": false
},
{
"code": "drying",
"value": false
},
{
"code": "heat",
"value": true
},
{
"code": "light",
"value": true
},
{
"code": "countdown_left",
"value": 0
},
{
"code": "temp_unit_convert",
"value": "c"
},
{
"code": "sleep",
"value": false
},
{
"code": "health",
"value": true
},
{
"code": "cleaning",
"value": false
}
],
"sub": false,
"time_zone": "+01:00",
"uid": "eu16072678921349PYdM",
"update_time": 1771984329,
"uuid": "3a0a51d0530c85aa"
}
```
## Supplemental Diagnostics
Additional metadata that does not naturally belong to the suggested fixture files.
```json
{
"selector": "tuya:bf5638fc7cc3f25e32i45q",
"service_id": "f631c437-9b06-4108-9151-4fb27f001e59",
"device_type": "unknown",
"protocol_version": "3.3",
"poll_frequency": 30000,
"should_poll": true,
"local_poll_status": "Success",
"unknown_specification_codes": [
"eco",
"drying",
"cleaning",
"temp_unit_convert",
"fan_speed_enum"
],
"unknown_local_dps": [
"2",
"3",
"4",
"5",
"8",
"9",
"12",
"13",
"15",
"20",
"22",
"101",
"102",
"103",
"105",
"106",
"107",
"108",
"109",
"110",
"111",
"112",
"113",
"115"
]
}
```
Note:
- The raw `thing_model.response.result.model` string is intentionally omitted here because it is already represented in a readable object form in `input-device.json > thing_model`.
## Remaining Files To Create Manually
```text
mapping file(s) in `server/services/tuya/lib/mappings/cloud/` and/or `server/services/tuya/lib/mappings/local/`
`server/services/tuya/lib/mappings/index.js`
`server/utils/constants.js` when a new Gladys category/type is required
`server/services/tuya/lib/device/tuya.deviceMapping.js` when new read/write handlers are required
poll-device.json (completed with final supported features)
expected-device.json
expected-events.json or expected-cloud-events.json / expected-local-events.json
expected-local-mapping.json
```
## Validation Checklist
- Confirm the inferred `device_type`
- Create or update the mapping files in `server/services/tuya/lib/mappings/cloud/` and/or `server/services/tuya/lib/mappings/local/`
- Register the mapping in `server/services/tuya/lib/mappings/index.js`
- Update `server/utils/constants.js` if the feature introduces a new Gladys `category/type`
- Update `server/services/tuya/lib/device/tuya.deviceMapping.js` if new `readValues` or `writeValues` handlers are needed
- Rebuild `poll-device.json` so it matches the final supported Gladys features
- Build `expected-device.json` from the final converted Gladys device shown in Discover
- Confirm the expected Gladys features
- Use a shared `expected-events.json` only when cloud and local polling should emit the same states
- Confirm cloud polling behavior
- Confirm local polling behavior
- Confirm local mapping
- Add or update mapping files if needed