Skip to content

Commit 7b6f13a

Browse files
committed
Merge branch 'release/1.0.4'
2 parents 6f0d191 + 4e75f7c commit 7b6f13a

File tree

9 files changed

+349
-94
lines changed

9 files changed

+349
-94
lines changed

bin/src/api.js

Lines changed: 224 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ if (typeof(URL) === 'undefined') {
88
}
99
}
1010

11-
const request = require('request-promise-native')
1211
const util = require('util')
13-
const uuid4 = require('uuid4');
14-
const md5 = require('md5');
12+
const uuid4 = require('uuid4')
13+
const md5 = require('md5')
14+
const term = require( 'terminal-kit' ).terminal
15+
const axios = require('axios')
1516

1617
const cleanServerUrl = (server) => {
1718
server = /mqtts?:\/\//.test(server) ? server : 'mqtt://' + server // add protocol
@@ -22,54 +23,190 @@ const cleanServerUrl = (server) => {
2223

2324
const serverRegex = /((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])|(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])):(?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])$/
2425

26+
const base64Encode = str => Buffer.from(str).toString('base64')
27+
28+
const tableOptions = {
29+
hasBorder: true,
30+
borderChars: 'light',
31+
contentHasMarkup: true,
32+
fit: true,
33+
width: 95,
34+
firstColumnTextAttr: { color: 'yellow' }
35+
}
36+
37+
function logRequest(request) {
38+
let url = new URL(request.url);
39+
console.log(`> ${request.method.toUpperCase()} ${url.path}`)
40+
console.log(`> Host: ${url.host}`)
41+
42+
let headers = {}
43+
headers = Object.assign(headers, request.headers.common);
44+
headers = Object.assign(headers, request.headers[request.method]);
45+
headers = Object.assign(headers, Object.fromEntries(
46+
Object.entries(request.headers).filter(
47+
([header]) => !['common', 'delete', 'get', 'head', 'post', 'put', 'patch'].includes(header)
48+
)
49+
));
50+
for (let [header, value] of Object.entries(headers)) {
51+
console.log(`> ${header}: ${value}`)
52+
}
53+
54+
console.log('>')
55+
console.log(util.inspect(request.data, {showHidden: false, depth: null}))
56+
}
57+
58+
function logResponse(response) {
59+
console.log(`< ${response.status} ${response.statusText}`)
60+
for (const [header, value] of Object.entries(response.headers)) {
61+
console.log(`< ${header}: ${value}`)
62+
}
63+
console.log('<')
64+
console.log(util.inspect(response.data, {showHidden: false, depth: null}))
65+
}
66+
67+
function handleRequestError(error, verbose)
68+
{
69+
if (verbose) {
70+
if (error.response) {
71+
logResponse(error.response)
72+
} else if (error.request) {
73+
logRequest(error.request)
74+
} else {
75+
console.error('Error', error.message);
76+
}
77+
} else {
78+
console.error('Error', 'Unable to connect to device');
79+
}
80+
}
2581

2682
module.exports = class API {
27-
constructor(host, key, userId) {
83+
constructor(host, key, userId, verbose = false) {
2884
this.host = host
2985
this.key = key
3086
this.userId = userId
87+
88+
axios.interceptors.request.use(request => {
89+
if (verbose) {
90+
logRequest(request)
91+
}
92+
return request
93+
})
94+
95+
axios.interceptors.response.use(response => {
96+
if (verbose) {
97+
logResponse(response)
98+
}
99+
return response
100+
})
31101
}
32102

33-
signPacket(payload) {
34-
let messageId = md5(uuid4())
35-
let timestamp = Math.floor(Date.now() / 1000)
36-
let signature = md5(messageId + this.key + timestamp)
103+
signPacket(packet) {
104+
const messageId = md5(uuid4())
105+
const timestamp = Math.floor(Date.now() / 1000)
106+
const signature = md5(messageId + this.key + timestamp)
37107

38-
payload.header.messageId = messageId
39-
payload.header.timestamp = timestamp
40-
payload.header.sign = signature
108+
packet.header.messageId = messageId
109+
packet.header.timestamp = timestamp
110+
packet.header.sign = signature
41111

42-
return payload
112+
return packet
43113
}
44114

45-
deviceInformation() {
46-
let payload = {
115+
async deviceInformation() {
116+
const packet = this.signPacket({
47117
'header': {
48118
'method': 'GET',
49119
'namespace': 'Appliance.System.All'
50120
},
51121
'payload': {}
52-
}
53-
54-
payload = this.signPacket(payload)
55-
56-
console.log('sending payload', util.inspect(payload, {showHidden: false, depth: null}))
122+
})
57123

58-
return request.post({
59-
url: `http://${this.host}/config`,
60-
headers: {
61-
'Content-Type': 'application/json'
62-
},
63-
json: payload
64-
}).catch(err => console.error(err))
124+
try {
125+
const response = await axios.post(
126+
`http://${this.host}/config`,
127+
packet,
128+
{
129+
headers: {
130+
'Content-Type': 'application/json'
131+
},
132+
}
133+
)
134+
135+
const data = response.data;
136+
137+
if ('error' in data.payload) {
138+
let {code, message} = data.payload.error;
139+
140+
switch (code) {
141+
case 5001:
142+
console.error('Incorrect shared key provided.')
143+
break;
144+
}
145+
146+
return
147+
}
148+
149+
const system = data.payload.all.system
150+
const digest = data.payload.all.digest
151+
const hw = system.hardware
152+
const fw = system.firmware
153+
154+
let rows = [
155+
['Device', `${hw.type} ${hw.subType} ${hw.chipType} (hardware:${hw.version} firmware:${fw.version})`],
156+
['UUID', hw.uuid],
157+
['Mac address', hw.macAddress],
158+
['WIFI', `${fw.innerIp} (${fw.wifiMac})`],
159+
];
160+
161+
if (fw.server) {
162+
rows.push(
163+
['MQTT broker', `${fw.server}:${fw.port}`],
164+
['Status', `${system.online.status == 0 ? '^ROffline' : '^GOnline'}`]
165+
)
166+
} else {
167+
rows.push(
168+
['Status', `^BConfiguration`]
169+
)
170+
}
171+
172+
rows.push(
173+
['Credentials', `User: ^C${fw.userId}\nPassword: ^C${this.calculateDevicePassword(hw.macAddress, fw.userId)}`]
174+
)
175+
176+
if (digest.togglex) {
177+
let row = ['Switch state']
178+
let col = []
179+
for (let sw of digest.togglex) {
180+
let ch = [];
181+
if (digest.togglex.length > 1) {
182+
ch.push(`Channel ${sw.channel}:`);
183+
}
184+
ch.push(sw.onoff == 0 ? '^ROff' : '^GOn')
185+
186+
const switchUpdateDate = new Date();
187+
switchUpdateDate.setTime(sw.lmTime * 1000) // put time into ms not seconds
188+
ch.push(`(${new Intl.DateTimeFormat(process.env.LC_TIME, { dateStyle: 'full', timeStyle: 'long' }).format(switchUpdateDate)})`)
189+
190+
col.push(ch.join('^ '))
191+
}
192+
row.push(col.join("\n"));
193+
rows.push(row)
194+
}
195+
196+
term.table(
197+
rows,
198+
tableOptions
199+
)
200+
} catch (error) {
201+
handleRequestError(error)
202+
}
65203
}
66204

67-
configureMqttServers(mqtt) {
68-
let servers = mqtt/*.filter((server) => serverRegex.test(server))*/
69-
servers = mqtt.map((server) => {
205+
async configureMqttServers(mqtt) {
206+
const servers = mqtt.map((server) => {
70207
server = cleanServerUrl(server)
71208

72-
let url = new URL(server)
209+
const url = new URL(server)
73210
return {
74211
host: url.hostname,
75212
port: url.port
@@ -80,18 +217,24 @@ module.exports = class API {
80217
if (servers.length == 1) {
81218
servers.push(servers[0]);
82219
}
220+
221+
term.table(
222+
[
223+
['Primary MQTT broker', `${servers[0].host}:${servers[0].port}`],
224+
['Secondary MQTT broker', `${servers[1].host}:${servers[1].port}`]
225+
],
226+
tableOptions
227+
)
83228

84-
console.log('Setting MQTT servers', servers)
85-
86-
let payload = {
229+
const packet = this.signPacket({
87230
'header': {
88231
'method': 'SET',
89232
'namespace': 'Appliance.Config.Key'
90233
},
91234
'payload': {
92235
'key': {
93236
'gateway': ((servers) => {
94-
let gateway = servers[0]
237+
const gateway = servers[0]
95238

96239
if (servers.length > 1) {
97240
gateway.secondHost = servers[1].host
@@ -104,45 +247,64 @@ module.exports = class API {
104247
'userId': this.userId
105248
}
106249
}
107-
}
108-
109-
payload = this.signPacket(payload)
110-
111-
console.log('sending payload', util.inspect(payload, {showHidden: false, depth: null}))
250+
})
112251

113-
return request.post({
114-
url: `http://${this.host}/config`,
115-
headers: {
116-
'Content-Type': 'application/json'
117-
},
118-
json: payload
119-
}).catch(err => console.error(err))
252+
try {
253+
const response = await axios.post(
254+
`http://${this.host}/config`,
255+
packet,
256+
{
257+
headers: {
258+
'Content-Type': 'application/json'
259+
},
260+
}
261+
)
262+
} catch (error) {
263+
handleRequestError(error)
264+
}
120265
}
121266

122-
configureWifiCredentials(credentials) {
123-
let payload = {
267+
async configureWifiCredentials(credentials) {
268+
const ssid = base64Encode(credentials.ssid)
269+
const password = base64Encode(credentials.password)
270+
271+
const packet = this.signPacket({
124272
'header': {
125273
'method': 'SET',
126274
'namespace': 'Appliance.Config.Wifi'
127275
},
128276
'payload': {
129277
'wifi': {
130-
"ssid": Buffer.from(credentials.ssid).toString('base64'),
131-
"password": Buffer.from(credentials.password).toString('base64')
278+
"ssid": ssid,
279+
"password": password
132280
}
133281
}
134-
}
135-
136-
payload = this.signPacket(payload)
137-
138-
console.log('sending payload', util.inspect(payload, {showHidden: false, depth: null}))
282+
})
283+
284+
term.table(
285+
[
286+
['Encoded WIFI SSID', `${ssid}`],
287+
['Encoded WIFI password', `${password}`]
288+
],
289+
tableOptions
290+
)
139291

140-
return request.post({
141-
url: `http://${this.host}/config`,
142-
headers: {
143-
'Content-Type': 'application/json'
144-
},
145-
json: payload
146-
}).catch(err => console.error(err))
292+
try {
293+
const response = await axios.post(
294+
`http://${this.host}/config`,
295+
packet,
296+
{
297+
headers: {
298+
'Content-Type': 'application/json'
299+
},
300+
}
301+
)
302+
} catch (error) {
303+
handleRequestError(error)
304+
}
305+
}
306+
307+
calculateDevicePassword(macAddress, user = null) {
308+
return `${user}_${md5(macAddress + this.key)}`
147309
}
148310
}

bin/src/meross

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
'use strict'
44

5-
var program = require('commander');
5+
const {version} = require('./package.json')
6+
const program = require('commander')
7+
68
program
7-
.version('0.2.0');
9+
.version(version)
810

911
program
1012
.command('info [options]', 'get information about compatable Meross smart device')
1113
.command('setup [options]', 'setup compatable Meross smart device')
1214

13-
program.parse(process.argv);
15+
program.parse(process.argv)

0 commit comments

Comments
 (0)