diff --git a/components/tuya/actions/list-devices/list-devices.mjs b/components/tuya/actions/list-devices/list-devices.mjs new file mode 100644 index 0000000000000..308586dadfc24 --- /dev/null +++ b/components/tuya/actions/list-devices/list-devices.mjs @@ -0,0 +1,43 @@ +import tuya from "../../tuya.app.mjs"; + +export default { + key: "tuya-list-devices", + name: "List Devices", + description: "Get a list of devices associated with a home. [See the documentation](https://developer.tuya.com/en/docs/cloud/d7ee73aadb?id=Kawfjer0wkt2a)", + version: "0.0.1", + type: "action", + props: { + tuya, + userId: { + propDefinition: [ + tuya, + "userId", + ], + }, + homeId: { + propDefinition: [ + tuya, + "homeId", + (c) => ({ + userId: c.userId, + }), + ], + optional: true, + }, + }, + async run({ $ }) { + const response = this.homeId + ? await this.tuya.listHomeDevices({ + homeId: this.homeId, + }) + : await this.tuya.listUserDevices({ + userId: this.userId, + }); + if (response?.result?.length) { + $.export("$summary", `Found ${response.result.length} device${response.result.length === 1 + ? "" + : "s"}`); + } + return response; + }, +}; diff --git a/components/tuya/actions/list-homes/list-homes.mjs b/components/tuya/actions/list-homes/list-homes.mjs new file mode 100644 index 0000000000000..8d6c4eec44bad --- /dev/null +++ b/components/tuya/actions/list-homes/list-homes.mjs @@ -0,0 +1,29 @@ +import tuya from "../../tuya.app.mjs"; + +export default { + key: "tuya-list-homes", + name: "List Homes", + description: "Based on the user ID, query the list of homes where the specified user belongs. [See the documentation](https://developer.tuya.com/en/docs/cloud/f5dd40ed14?id=Kawfjh9hpov1n)", + version: "0.0.1", + type: "action", + props: { + tuya, + userId: { + propDefinition: [ + tuya, + "userId", + ], + }, + }, + async run({ $ }) { + const response = await this.tuya.listHomes({ + userId: this.userId, + }); + if (response?.result?.length) { + $.export("$summary", `Found ${response.result.length} home${response.result.length === 1 + ? "" + : "s"}`); + } + return response; + }, +}; diff --git a/components/tuya/actions/send-instructions-to-device/send-instructions-to-device.mjs b/components/tuya/actions/send-instructions-to-device/send-instructions-to-device.mjs new file mode 100644 index 0000000000000..0f34200843212 --- /dev/null +++ b/components/tuya/actions/send-instructions-to-device/send-instructions-to-device.mjs @@ -0,0 +1,69 @@ +import tuya from "../../tuya.app.mjs"; + +export default { + key: "tuya-send-instructions-to-device", + name: "Send Instructions to Device", + description: "Send an instruction to the specified device. [See the documentation](https://developer.tuya.com/en/docs/cloud/device-control?id=K95zu01ksols7#title-35-Send%20instructions%20to%20the%20device)", + version: "0.0.1", + type: "action", + props: { + tuya, + userId: { + propDefinition: [ + tuya, + "userId", + ], + }, + homeId: { + propDefinition: [ + tuya, + "homeId", + (c) => ({ + userId: c.userId, + }), + ], + optional: true, + }, + deviceId: { + propDefinition: [ + tuya, + "deviceId", + (c) => ({ + userId: c.userId, + homeId: c.homeId, + }), + ], + }, + instructionCode: { + propDefinition: [ + tuya, + "instructionCode", + (c) => ({ + deviceId: c.deviceId, + }), + ], + }, + value: { + type: "string", + label: "Value", + description: "The value to set", + }, + }, + async run({ $ }) { + const response = await this.tuya.sendInstructionsToDevice({ + deviceId: this.deviceId, + data: { + commands: [ + { + code: this.instructionCode, + value: this.value, + }, + ], + }, + }); + if (response.success) { + $.export("$summary", "Successfully sent instructions to device"); + } + return response; + }, +}; diff --git a/components/tuya/package.json b/components/tuya/package.json index 6de3afdd66cc7..8674e909b2b55 100644 --- a/components/tuya/package.json +++ b/components/tuya/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/tuya", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Tuya Components", "main": "tuya.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream <support@pipedream.com> (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "@tuya/tuya-connector-nodejs": "^2.1.2" } -} \ No newline at end of file +} diff --git a/components/tuya/sources/new-device-parameter-updated/new-device-parameter-updated.mjs b/components/tuya/sources/new-device-parameter-updated/new-device-parameter-updated.mjs new file mode 100644 index 0000000000000..a230beaa3af84 --- /dev/null +++ b/components/tuya/sources/new-device-parameter-updated/new-device-parameter-updated.mjs @@ -0,0 +1,88 @@ +import tuya from "../../tuya.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import sampleEmit from "./test-event.mjs"; + +export default { + key: "tuya-new-device-parameter-updated", + name: "New Device Parameter Updated", + description: "Emit new event when the specified device parameter is updated. [See the documentation](https://developer.tuya.com/en/docs/cloud/device-management?id=K9g6rfntdz78a#title-10-Get%20a%20list%20of%20devices%20under%20a%20specified%20user)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + tuya, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + userId: { + propDefinition: [ + tuya, + "userId", + ], + }, + deviceParameter: { + type: "string", + label: "Device Parameter", + description: "The device parameter to watch for updates. E.g. `switch_1`", + }, + homeId: { + propDefinition: [ + tuya, + "homeId", + (c) => ({ + userId: c.userId, + }), + ], + optional: true, + }, + }, + methods: { + _getPreviousValues() { + return this.db.get("previousValues") || {}; + }, + _setPreviousValues(previousValues) { + this.db.set("previousValues", previousValues); + }, + getCurrentValue(device) { + const { status } = device; + const relevantStatus = status.find(({ code }) => code === this.deviceParameter); + return relevantStatus?.value; + }, + generateMeta(item) { + const ts = Date.now(); + return { + id: `${item.id}${ts}`, + summary: `Device Updated with ID: ${item.id}`, + ts, + }; + }, + }, + async run() { + const previousValues = this._getPreviousValues(); + const newValues = {}; + + const { result: devices } = this.homeId + ? await this.tuya.listHomeDevices({ + homeId: this.homeId, + }) + : await this.tuya.listUserDevices({ + userId: this.userId, + }); + + for (const device of devices) { + const currentValue = this.getCurrentValue(device); + if (previousValues[device.id] !== currentValue) { + const meta = this.generateMeta(device); + this.$emit(device, meta); + } + newValues[device.id] = currentValue; + } + + this._setPreviousValues(newValues); + }, + sampleEmit, +}; diff --git a/components/tuya/sources/new-device-parameter-updated/test-event.mjs b/components/tuya/sources/new-device-parameter-updated/test-event.mjs new file mode 100644 index 0000000000000..31d394ee1c983 --- /dev/null +++ b/components/tuya/sources/new-device-parameter-updated/test-event.mjs @@ -0,0 +1,77 @@ +export default { + "active_time": 1748632139, + "biz_type": 18, + "category": "cz", + "create_time": 1748632139, + "icon": "smart/icon/bay1599677305883cNM4/03aaf2fd77d712895eb7b8408a374ebe.png", + "id": "vdevo174863213925233", + "ip": "47.225.143.157", + "lat": "", + "local_key": ".'0k9<h@|2j7!OK9", + "lon": "", + "model": "TS-441", + "name": "Tramontina Plug-vdevo", + "online": true, + "owner_id": "250587566", + "product_id": "mjzxchzvqmxps3lf", + "product_name": "Plugue Tramontina", + "status": [ + { + "code": "switch_1", + "value": false + }, + { + "code": "countdown_1", + "value": 0 + }, + { + "code": "add_ele", + "value": 0 + }, + { + "code": "cur_current", + "value": 0 + }, + { + "code": "cur_power", + "value": 0 + }, + { + "code": "cur_voltage", + "value": 0 + }, + { + "code": "relay_status", + "value": "power_off" + }, + { + "code": "overcharge_switch", + "value": false + }, + { + "code": "light_mode", + "value": "relay" + }, + { + "code": "child_lock", + "value": false + }, + { + "code": "cycle_time", + "value": "" + }, + { + "code": "random_time", + "value": "" + }, + { + "code": "switch_inching", + "value": "" + } + ], + "sub": false, + "time_zone": "", + "uid": "az1748632127862aWriL", + "update_time": 1748632139, + "uuid": "vdevo174863213925233" + } \ No newline at end of file diff --git a/components/tuya/tuya.app.mjs b/components/tuya/tuya.app.mjs index 7cf9ecd689e92..c51da7b9190ca 100644 --- a/components/tuya/tuya.app.mjs +++ b/components/tuya/tuya.app.mjs @@ -1,11 +1,122 @@ +import { TuyaContext } from "@tuya/tuya-connector-nodejs"; +import { ConfigurationError } from "@pipedream/platform"; + export default { type: "app", app: "tuya", - propDefinitions: {}, + propDefinitions: { + userId: { + type: "string", + label: "User ID", + description: "The unique identifier of a user. E.g. `az1748632127862aWriL`", + }, + homeId: { + type: "string", + label: "Home ID", + description: "The identifier of a home", + async options({ userId }) { + const { result } = await this.listHomes({ + userId, + }); + return result?.map(({ + home_id: value, name: label, + }) => ({ + label, + value, + })) || []; + }, + }, + deviceId: { + type: "string", + label: "Device ID", + description: "The identifier of a device", + async options({ + userId, homeId, + }) { + const { result } = homeId + ? await this.listHomeDevices({ + homeId, + }) + : await this.listUserDevices({ + userId, + }); + return result?.map(({ + id: value, name: label, + }) => ({ + label, + value, + })) || []; + }, + }, + instructionCode: { + type: "string", + label: "Instruction Code", + description: "The code of the command to use. E.g. `switch_1`", + async options({ deviceId }) { + const { result } = await this.listDeviceFunctions({ + deviceId, + }); + return result?.functions?.map(({ + code: value, name: label, + }) => ({ + label, + value, + })) || []; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _getClient() { + return new TuyaContext({ + baseUrl: this.$auth.base_url, + accessKey: this.$auth.client_id, + secretKey: this.$auth.client_secret, + }); + }, + async _makeRequest({ + method = "GET", + path, + data = {}, + }) { + const response = await this._getClient().request({ + method, + path, + body: data, + }); + if (!response.success) { + console.log(response); + throw new ConfigurationError(`${response.msg}`); + } + return response; + }, + listHomes({ userId }) { + return this._makeRequest({ + path: `/v1.0/users/${userId}/homes`, + }); + }, + listUserDevices({ userId }) { + return this._makeRequest({ + path: `/v1.0/users/${userId}/devices`, + }); + }, + listHomeDevices({ homeId }) { + return this._makeRequest({ + path: `/v1.0/homes/${homeId}/devices`, + }); + }, + listDeviceFunctions({ deviceId }) { + return this._makeRequest({ + path: `/v1.0/devices/${deviceId}/functions`, + }); + }, + sendInstructionsToDevice({ + deviceId, data, + }) { + return this._makeRequest({ + method: "POST", + path: `/v1.0/devices/${deviceId}/commands`, + data, + }); }, }, -}; \ No newline at end of file +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 795127a8cf399..5b11895ca85bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13491,7 +13491,14 @@ importers: components/tutor_lms: {} - components/tuya: {} + components/tuya: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 + '@tuya/tuya-connector-nodejs': + specifier: ^2.1.2 + version: 2.1.2 components/twelve_data: {} @@ -19800,6 +19807,9 @@ packages: '@tsconfig/node14@1.0.3': resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + '@tuya/tuya-connector-nodejs@2.1.2': + resolution: {integrity: sha512-8tM7QlOF1QQrT3iQgcHp4JDNRUdOyi06h8F5ZL7antQZYP67TRQ2/puisoo2uhdXo+n+GT0B605pWaDqr9nPrA==} + '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} @@ -36373,6 +36383,13 @@ snapshots: '@tsconfig/node14@1.0.3': {} + '@tuya/tuya-connector-nodejs@2.1.2': + dependencies: + axios: 0.21.4 + qs: 6.13.1 + transitivePeerDependencies: + - debug + '@types/acorn@4.0.6': dependencies: '@types/estree': 1.0.6