Skip to content

Commit

Permalink
Rewrite Custom project start process. (#11)
Browse files Browse the repository at this point in the history
* Rewrite the `Custom` project start process. User will be created and authorized automatically.

* Update README.md
  • Loading branch information
0x5e committed Oct 25, 2022
1 parent 685991c commit 78f6e80
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Remove `lang` option.
- Update unit test.
- For `Custom` project type, some API has switched to the same as `Smart Home`.
- Rewrite the `Custom` project start process. User will be created and authorized automatically.

### Fixed
- 1004 signature error when url query has more than 2 elements.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ If beta version works fine for a while, it will be merged into the upstream repo
- Smoke Sensor
- Contact Sensor
- Leak Sensor
- For `Custom` project, `username` and `password` options are no longer need. The plugin will create a default user and authorize to all assets automatically.


## Supported Tuya Devices

Expand Down
8 changes: 6 additions & 2 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,16 @@
"username": {
"title": "Username",
"type": "string",
"required": true
"condition": {
"functionBody": "return model.options.projectType === '2';"
}
},
"password": {
"title": "Password",
"type": "string",
"required": true
"condition": {
"functionBody": "return model.options.projectType === '2';"
}
},
"appSchema": {
"title": "App",
Expand Down
2 changes: 0 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ export const customOptionsSchema = {
endpoint: { type: 'string', format: 'url', required: true },
accessId: { type: 'string', required: true },
accessKey: { type: 'string', required: true },
username: { type: 'string', required: true },
password: { type: 'string', required: true },
},
};

Expand Down
59 changes: 59 additions & 0 deletions src/core/TuyaOpenAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,34 @@ export default class TuyaOpenAPI {
return;
}

/**
* In 'Custom' project, get a token directly. (Login with admin)
* Have permission on asset management, user management.
* But lost some permission on device management.
* @returns
*/
async getToken() {
const res = await this.get('/v1.0/token', { grant_type: 1 });
if (res.success) {
const { access_token, refresh_token, uid, expire_time } = res.result;
this.tokenInfo = {
access_token: access_token,
refresh_token: refresh_token,
uid: uid,
expire: expire_time * 1000 + new Date().getTime(),
};
}
return res;
}

/**
* In 'Smart Home' project, login with App's user.
* @param countryCode 2-digit Country Code
* @param username Username
* @param password Password
* @param appSchema App Schema: 'tuyaSmart', 'smartlife'
* @returns
*/
async homeLogin(countryCode: number, username: string, password: string, appSchema: string) {

for (const _endpoint of Object.keys(DEFAULT_ENDPOINTS)) {
Expand Down Expand Up @@ -158,6 +185,38 @@ export default class TuyaOpenAPI {
return res;
}

/**
* In 'Custom' project, Search user by username.
* @param username Username
* @returns
*/
async customGetUserInfo(username: string) {
const res = await this.get(`/v1.2/iot-02/users/${username}`);
return res;
}

/**
* In 'Custom' project, create a user.
* @param username Username
* @param password Password
* @param country_code Country Code (Useless)
* @returns
*/
async customCreateUser(username: string, password: string, country_code = 1) {
const res = await this.post('/v1.0/iot-02/users', {
username,
password: Crypto.SHA256(password).toString().toLowerCase(),
country_code,
});
return res;
}

/**
* In 'Custom' project, login with user.
* @param username Username
* @param password Password
* @returns
*/
async customLogin(username: string, password: string) {
const res = await this.post('/v1.0/iot-03/users/login', {
'username': username,
Expand Down
13 changes: 11 additions & 2 deletions src/device/TuyaCustomDeviceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,23 @@ import TuyaDeviceManager from './TuyaDeviceManager';

export default class TuyaCustomDeviceManager extends TuyaDeviceManager {

async getAssetList() {
const res = await this.api.get('/v1.0/iot-03/users/assets', {
async getAssetList(parent_asset_id = -1) {
// const res = await this.api.get('/v1.0/iot-03/users/assets', {
const res = await this.api.get(`/v1.0/iot-02/assets/${parent_asset_id}/sub-assets`, {
'page_no': 0,
'page_size': 100,
});
return res;
}

async authorizeAssetList(uid: string, asset_ids: string[] = [], authorized_children = false) {
const res = await this.api.post(`/v1.0/iot-03/users/${uid}/actions/batch-assets-authorized`, {
asset_ids: asset_ids.join(','),
authorized_children,
});
return res;
}

async getAssetDeviceIDList(assetID: string) {
let deviceIDs: string[] = [];
const params = {
Expand Down
64 changes: 54 additions & 10 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export class TuyaPlatform implements DynamicPlatformPlugin {
}

// add accessories
this.log.info(`Got ${devices.length} device(s).`);
for (const device of devices) {
this.addAccessory(device);
}
Expand All @@ -126,33 +127,54 @@ export class TuyaPlatform implements DynamicPlatformPlugin {
return null;
}

const DEFAULT_USER = 'homebridge';
const DEFAULT_PASS = 'homebridge';

let res;
const { endpoint, accessId, accessKey, username, password } = this.options;
const { endpoint, accessId, accessKey } = this.options;
const api = new TuyaOpenAPI(endpoint, accessId, accessKey, this.log);
const mq = new TuyaOpenMQ(api, '2.0', this.log);
const deviceManager = new TuyaCustomDeviceManager(api, mq);

this.log.info('Log in to Tuya Cloud.');
res = await api.customLogin(username, password);
this.log.info('Get token.');
res = await api.getToken();
if (res.success === false) {
this.log.error(`Login failed. code=${res.code}, msg=${res.msg}`);
if (LOGIN_ERROR_MESSAGES[res.code]) {
this.log.error(LOGIN_ERROR_MESSAGES[res.code]);
}
this.log.error(`Get token failed. code=${res.code}, msg=${res.msg}`);
return null;
}

this.log.info('Start MQTT connection.');
mq.start();

this.log.info(`Search default user "${DEFAULT_USER}"`);
res = await api.customGetUserInfo(DEFAULT_USER);
if (res.success === false) {
this.log.error(`Search user failed. code=${res.code}, msg=${res.msg}`);
return null;
}


if (!res.result.user_name) {
this.log.info(`Default user "${DEFAULT_USER}" not exist.`);
this.log.info(`Creating default user "${DEFAULT_USER}".`);
res = await api.customCreateUser(DEFAULT_USER, DEFAULT_PASS);
if (res.success === false) {
this.log.error(`Create default user failed. code=${res.code}, msg=${res.msg}`);
return null;
}
} else {
this.log.info(`Default user "${DEFAULT_USER}" exists.`);
}
const uid = res.result.user_id;


this.log.info('Fetching asset list.');
res = await deviceManager.getAssetList();
if (res.success === false) {
this.log.error(`Fetching asset list failed. code=${res.code}, msg=${res.msg}`);
return null;
}

const assetIDList: string[] = [];
for (const { asset_id, asset_name } of res.result.assets) {
for (const { asset_id, asset_name } of res.result.list) {
this.log.info(`Got asset_id=${asset_id}, asset_name=${asset_name}`);
assetIDList.push(asset_id);
}
Expand All @@ -162,6 +184,28 @@ export class TuyaPlatform implements DynamicPlatformPlugin {
return null;
}


this.log.info('Authorize asset list.');
res = await deviceManager.authorizeAssetList(uid, assetIDList, true);
if (res.success === false) {
this.log.error(`Authorize asset list failed. code=${res.code}, msg=${res.msg}`);
return null;
}


this.log.info(`Log in with user "${DEFAULT_USER}".`);
res = await api.customLogin(DEFAULT_USER, DEFAULT_USER);
if (res.success === false) {
this.log.error(`Login failed. code=${res.code}, msg=${res.msg}`);
if (LOGIN_ERROR_MESSAGES[res.code]) {
this.log.error(LOGIN_ERROR_MESSAGES[res.code]);
}
return null;
}

this.log.info('Start MQTT connection.');
mq.start();

this.log.info('Fetching device list.');
const devices = await deviceManager.updateDevices(assetIDList);

Expand Down
8 changes: 7 additions & 1 deletion test/custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ if (options.projectType === '1') {
const customDeviceManager = new TuyaCustomDeviceManager(api, mq);

describe('TuyaOpenAPI', () => {
test('getToken()', async () => {
const res = await api.getToken();
expectSuccessResponse(res);
});

test('customLogin()', async () => {
await api.customLogin(options.username, options.password);
expect(api.isLogin()).toBeTruthy();
Expand All @@ -33,7 +38,8 @@ if (options.projectType === '1') {
test('getAssetList()', async () => {
const res = await customDeviceManager.getAssetList();
expectSuccessResponse(res);
for (const { asset_id } of res.result.assets) {
const assets = res.result.list || res.result.assets;
for (const { asset_id } of assets) {
assetIDList.push(asset_id);
}
});
Expand Down

0 comments on commit 78f6e80

Please sign in to comment.