diff --git a/config.schema.json b/config.schema.json index c5f65a3f..5889896f 100644 --- a/config.schema.json +++ b/config.schema.json @@ -76,37 +76,6 @@ ], "required": true }, - "endPoint": { - "title": "API Region", - "type": "string", - "default": "form", - "oneOf": [{ - "title": "America", - "enum": ["https://openapi.tuyaus.com"] - }, - { - "title": "China", - "enum": ["https://openapi.tuyacn.com"] - }, - { - "title": "Europe", - "enum": ["https://openapi.tuyaeu.com"] - }, - { - "title": "India", - "enum": ["https://openapi.tuyain.com"] - }, - { - "title": "EasternAmerica", - "enum": ["https://openapi-ueaz.tuyaus.com"] - }, - { - "title": "WesternEurope", - "enum": ["https://openapi-weaz.tuyaeu.com"] - } - ], - "required": true - }, "countryCode": { "title": "Country Code", "type": "number", diff --git a/config/config.json b/config/config.json index 960dba58..f2891814 100644 --- a/config/config.json +++ b/config/config.json @@ -19,7 +19,6 @@ "accessId": "", "accessKey": "", "lang": "en", - "endPoint": "", "projectType": "2", "appSchema": "tuyaSmart", "countryCode": 86, diff --git a/index.js b/index.js index 8a0e441b..04b40a63 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,8 @@ const Fanv2Accessory = require('./lib/fanv2_accessory'); const HeaterAccessory = require('./lib/heater_accessory'); const GarageDoorAccessory = require('./lib/garagedoor_accessory'); const AirPurifierAccessory = require('./lib/air_purifier_accessory') +const WindowCoveringAccessory = require('./lib/window_covering_accessory') + const LogUtil = require('./util/logutil') const DataUtil = require('./util/datautil') @@ -72,7 +74,6 @@ class TuyaPlatform { } } else { api = new TuyaSHOpenAPI( - config.options.endPoint, config.options.accessId, config.options.accessKey, config.options.username, @@ -123,6 +124,7 @@ class TuyaPlatform { case 'fwd': case 'tgq': case 'xdd': + case 'dc': deviceAccessory = new LightAccessory(this, homebridgeAccessory, device); this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); this.deviceAccessories.set(uuid, deviceAccessory); @@ -162,6 +164,11 @@ class TuyaPlatform { this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); this.deviceAccessories.set(uuid, deviceAccessory); break; + case 'cl': + deviceAccessory = new WindowCoveringAccessory(this, homebridgeAccessory, device); + this.accessories.set(uuid, deviceAccessory.homebridgeAccessory); + this.deviceAccessories.set(uuid, deviceAccessory); + break; default: break; } diff --git a/lib/light_accessory.js b/lib/light_accessory.js index 88f3d970..34d2cf19 100644 --- a/lib/light_accessory.js +++ b/lib/light_accessory.js @@ -189,7 +189,7 @@ getAccessoryCharacteristic(name) { case 'bright_value': if (this.deviceCategorie == 'dj') { defaultBrightRange = { 'min': 25, 'max': 255 } - }else if (this.deviceCategorie == 'xdd' || this.deviceCategorie == 'fwd' || this.deviceCategorie == 'tgq') { + }else if (this.deviceCategorie == 'xdd' || this.deviceCategorie == 'fwd' || this.deviceCategorie == 'tgq' || this.deviceCategorie == 'dd' || this.deviceCategorie == 'dc') { defaultBrightRange = { 'min': 10, 'max': 1000 } } break; @@ -200,7 +200,7 @@ getAccessoryCharacteristic(name) { case 'temp_value': if (this.deviceCategorie == 'dj') { defaultTempRange = { 'min': 0, 'max': 255 } - }else if (this.deviceCategorie == 'xdd' || this.deviceCategorie == 'fwd') { + }else if (this.deviceCategorie == 'xdd' || this.deviceCategorie == 'fwd' || this.deviceCategorie == 'dd' || this.deviceCategorie == 'dc') { defaultTempRange = { 'min': 0, 'max': 1000 } } break; @@ -210,7 +210,7 @@ getAccessoryCharacteristic(name) { case 'colour_data': if (this.deviceCategorie == 'dj') { defaultSaturationRange = { 'min': 0, 'max': 255 } - }else if (this.deviceCategorie == 'xdd' || this.deviceCategorie == 'fwd') { + }else if (this.deviceCategorie == 'xdd' || this.deviceCategorie == 'fwd' || this.deviceCategorie == 'dd' || this.deviceCategorie == 'dc') { defaultSaturationRange = { 'min': 0, 'max': 1000 } } break; diff --git a/lib/tuyashopenapi.js b/lib/tuyashopenapi.js index 808fe945..5689dc15 100644 --- a/lib/tuyashopenapi.js +++ b/lib/tuyashopenapi.js @@ -2,11 +2,11 @@ const axios = require('axios').default; const Crypto = require('crypto-js'); const uuid = require('uuid'); const nonce = uuid.v1(); - +const DEFAULT_ENDPOINT = 'https://openapi.tuyacn.com'; class TuyaSHOpenAPI { - constructor(endpoint, accessId, accessKey, username, password, countryCode, appSchema, log, lang = 'en') { - this.endpoint = endpoint; + constructor(accessId, accessKey, username, password, countryCode, appSchema, log, lang = 'en') { + this.endpoint = DEFAULT_ENDPOINT; this.access_id = accessId; this.access_key = accessKey; this.lang = lang; @@ -45,7 +45,8 @@ class TuyaSHOpenAPI { 'password': md5pwd, 'schema': this.appSchema }); - let { access_token, refresh_token, uid, expire_time } = res.result; + let { access_token, refresh_token, uid, expire_time , platform_url} = res.result; + this.endpoint = platform_url ? platform_url : DEFAULT_ENDPOINT; this.tokenInfo = { access_token: access_token, refresh_token: refresh_token, diff --git a/lib/window_covering_accessory.js b/lib/window_covering_accessory.js new file mode 100644 index 00000000..0ada9976 --- /dev/null +++ b/lib/window_covering_accessory.js @@ -0,0 +1,143 @@ +const BaseAccessory = require('./base_accessory') + +let Accessory; +let Service; +let Characteristic; + +class WindowCoveringAccessory extends BaseAccessory { + + constructor(platform, homebridgeAccessory, deviceConfig) { + ({ Accessory, Characteristic, Service } = platform.api.hap); + super( + platform, + homebridgeAccessory, + deviceConfig, + Accessory.Categories.WINDOW_COVERING, + Service.WindowCovering + ); + this.isRefesh = false; + this.statusArr = deviceConfig.status; + + this.refreshAccessoryServiceIfNeed(this.statusArr, false); + }; + + /** + * init Or refresh AccessoryService + */ + refreshAccessoryServiceIfNeed(stateArr, isRefesh) { + this.isRefesh = isRefesh; + for (const statusMap of stateArr) { + // Characteristic.TargetPosition + if ( statusMap.code === 'percent_control') { + this.percentControlMap = statusMap + this.normalAsync(Characteristic.TargetPosition, 0, isRefesh); + } + + if ( statusMap.code === 'percent_state') { + // Characteristic.CurrentPosition + this.positionMap = statusMap + this.normalAsync(Characteristic.CurrentPosition, this.positionMap.value, isRefesh); + + // Characteristic.PositionState + let hbValue = this.getHomeBridgeParam(Characteristic.PositionState, this.positionMap); + this.normalAsync(Characteristic.PositionState, hbValue, isRefesh); + } + } + } + + /** + * add get/set Accessory service Characteristic Listner + */ + getAccessoryCharacteristic(name) { + //set Accessory service Characteristic + this.service.getCharacteristic(name) + .on('get', callback => { + if (this.hasValidCache()) { + callback(null, this.getCachedState(name)); + } + }) + .on('set', (hbValue, callback) => { + let tuyaParam = this.getTuyaParam(name, hbValue); + this.platform.tuyaOpenApi.sendCommand(this.deviceId, tuyaParam).then(() => { + //store homebridge value + this.setCachedState(name, hbValue); + //store targetPosition value + this.targetPosition = hbValue; + callback(); + }).catch((error) => { + this.log.error('[SET][%s] Characteristic Error: %s', this.homebridgeAccessory.displayName, error); + this.invalidateCache(); + callback(error); + }); + }); + } + + + + /** + * get Tuya param from HomeBridge param + */ + getTuyaParam(name, hbParam) { + let code; + let value; + if (Characteristic.TargetPosition === name) { + code = this.percentControlMap.code; + value = hbParam; + } + return { + "commands": [ + { + "code": code, + "value": value + } + ] + }; + } + + /** + * get HomeBridge param from tuya param + */ + getHomeBridgeParam(name, tuyaParam) { + if (Characteristic.PositionState === name) { + if (this.targetPosition) { + if (this.targetPosition > tuyaParam.value) { + return Characteristic.PositionState.INCREASING; + }else if (this.targetPosition < tuyaParam.value) { + return Characteristic.PositionState.DECREASING; + }else{ + return Characteristic.PositionState.STOPPED; + } + }else{ + return Characteristic.PositionState.STOPPED; + } + } + } + + /** + * update HomeBridge state + * @param {*} name HomeBridge Name + * @param {*} hbValue HomeBridge Value + * @param {*} isRefesh + */ + normalAsync(name, hbValue, isRefresh) { + //store homebridge value + this.setCachedState(name, hbValue); + if (isRefresh) { + this.service + .getCharacteristic(name) + .updateValue(hbValue); + } else { + this.getAccessoryCharacteristic(name); + } + } + + /** + * Tuya MQTT update device status + */ + updateState(data) { + this.refreshAccessoryServiceIfNeed(data.status, true); + } + +} + +module.exports = WindowCoveringAccessory; \ No newline at end of file