homebridge-meross
Version:
Homebridge plugin to control Meross devices.
271 lines (244 loc) • 8 kB
JavaScript
/* jshint node: true,esversion: 9, -W014, -W033 */
/* eslint-disable new-cap */
'use strict'
const axios = require('axios')
const crypto = require('crypto')
module.exports = class connectionHTTP {
constructor (platform) {
this.consts = platform.consts
this.debug = platform.config.debug
this.funcs = platform.funcs
this.ignoredDevices = platform.ignoredDevices
this.localUUIDs = platform.localUUIDs
this.log = platform.log
this.password = platform.config.password
this.username = platform.config.username
this.userkey = platform.config.userkey
}
async login () {
try {
const nonce = this.funcs.generateRandomString(16)
const timestampMillis = Date.now()
const loginParams = this.funcs.encodeParams({
email: this.username,
password: this.password
})
// Generate the md5-hash (called signature)
const datatosign = '23x17ahWarFH6w29' + timestampMillis + nonce + loginParams
const md5hash = crypto
.createHash('md5')
.update(datatosign)
.digest('hex')
const res = await axios({
url: 'https://iot.meross.com/v1/Auth/Login',
method: 'post',
headers: {
Authorization: 'Basic ',
vender: 'Meross',
AppVersion: '1.3.0',
AppLanguage: 'EN',
'User-Agent': 'okhttp/3.6.0'
},
data: {
params: loginParams,
sign: md5hash,
timestamp: timestampMillis,
nonce: nonce
}
})
// Check to see we got a response
if (!res.data) {
throw new Error(this.lang.noResponse)
}
if (!res.data.info || res.data.info !== 'Success' || !res.data.data) {
if (res.data.info === 'Username is not exist or password is wrong') {
if (this.base64Tried) {
throw new Error(res.data.info || 'login failed - ' + JSON.stringify(res.data))
} else {
this.base64Tried = true
this.password = Buffer.from(this.password, 'base64')
.toString('utf8')
.replace(/(\r\n|\n|\r)/gm, '')
.trim()
return await this.login()
}
}
throw new Error(res.data.info || 'login failed - ' + JSON.stringify(res.data))
}
this.key = res.data.data.key
this.token = res.data.data.token
this.userid = res.data.data.userid
if (this.debug && !this.userkey) {
this.log.warn('Meross Key: %s', this.key)
}
return {
key: this.key,
token: this.token,
userid: this.userid
}
} catch (err) {
if (err.code && this.consts.httpRetryCodes.includes(err.code)) {
// Retry if another attempt could be successful
this.log.warn('%s [login() - %s].', this.lang.httpRetry, err.code)
await this.funcs.sleep(30000)
return await this.login()
} else {
throw err
}
}
}
async getDevices () {
try {
if (!this.key || !this.token || !this.userid) {
throw new Error(this.lang.notAuth)
}
const nonce = this.funcs.generateRandomString(16)
const timestampMillis = Date.now()
const loginParams = this.funcs.encodeParams({})
// Generate the md5-hash (called signature)
const datatosign = '23x17ahWarFH6w29' + timestampMillis + nonce + loginParams
const md5hash = crypto
.createHash('md5')
.update(datatosign)
.digest('hex')
const res = await axios({
url: 'https://iot.meross.com/v1/Device/devList',
method: 'post',
headers: {
Authorization: 'Basic ' + this.token,
vender: 'Meross',
AppVersion: '1.3.0',
AppLanguage: 'EN',
'User-Agent': 'okhttp/3.6.0'
},
data: {
params: loginParams,
sign: md5hash,
timestamp: timestampMillis,
nonce: nonce
}
})
// Check to see we got a response
if (!res.data) {
throw new Error(this.lang.noResponse)
}
if (
res.data.info !== 'Success' ||
!this.funcs.hasProperty(res.data, 'data') ||
!Array.isArray(res.data.data)
) {
throw new Error('invalid device list received - ' + JSON.stringify(res.data))
}
// Don't return ignored devices or those that have been configured for local control
const toReturn = []
res.data.data.forEach(device => {
if (!this.ignoredDevices.includes(device.uuid) && !this.localUUIDs.includes(device.uuid)) {
toReturn.push(device)
}
})
// Return the amended device list
return toReturn
} catch (err) {
if (err.code && this.consts.httpRetryCodes.includes(err.code)) {
// Retry if another attempt could be successful
this.log.warn('%s [getDevices() - %s].', this.lang.httpRetry, err.code)
await this.funcs.sleep(30000)
return await this.login()
} else {
throw err
}
}
}
async getSubDevices (device) {
try {
if (!this.key || !this.token || !this.userid) {
throw new Error(this.lang.notAuth)
}
const nonce = this.funcs.generateRandomString(16)
const timestampMillis = Date.now()
const loginParams = this.funcs.encodeParams({
uuid: device.uuid
})
// Generate the md5-hash (called signature)
const datatosign = '23x17ahWarFH6w29' + timestampMillis + nonce + loginParams
const md5hash = crypto
.createHash('md5')
.update(datatosign)
.digest('hex')
const res = await axios({
url: 'https://iot.meross.com/v1/Hub/getSubDevices',
method: 'post',
headers: {
Authorization: 'Basic ' + this.token,
vender: 'Meross',
AppVersion: '1.3.0',
AppLanguage: 'EN',
'User-Agent': 'okhttp/3.6.0'
},
data: {
params: loginParams,
sign: md5hash,
timestamp: timestampMillis,
nonce: nonce
}
})
// Check to see we got a response
if (!res.data) {
throw new Error(this.lang.noResponse)
}
if (
res.data.info !== 'Success' ||
!this.funcs.hasProperty(res.data, 'data') ||
!Array.isArray(res.data.data)
) {
throw new Error('invalid subdevice list received - ' + JSON.stringify(res.data))
}
// Return the subdevice list to the platform
return res.data.data
} catch (err) {
if (err.code && this.consts.httpRetryCodes.includes(err.code)) {
// Retry if another attempt could be successful
this.log.warn('%s [getDevices() - %s].', this.lang.httpRetry, err.code)
await this.funcs.sleep(30000)
return await this.login()
} else {
throw err
}
}
}
async logout () {
try {
if (!this.key || !this.token || !this.userid) {
throw new Error(this.lang.notAuth)
}
const nonce = this.funcs.generateRandomString(16)
const timestampMillis = Date.now()
const loginParams = this.funcs.encodeParams({})
// Generate the md5-hash (called signature)
const datatosign = '23x17ahWarFH6w29' + timestampMillis + nonce + loginParams
const md5hash = crypto
.createHash('md5')
.update(datatosign)
.digest('hex')
await axios({
url: 'https://iot.meross.com/v1/Profile/logout',
method: 'post',
headers: {
Authorization: 'Basic ' + this.token,
vender: 'Meross',
AppVersion: '1.3.0',
AppLanguage: 'EN',
'User-Agent': 'okhttp/3.6.0'
},
data: {
params: loginParams,
sign: md5hash,
timestamp: timestampMillis,
nonce: nonce
}
})
} catch (err) {
// No need to show errors as this is only called on plugin shutdown
}
}
}