/
ESP8266WiFi.js
413 lines (389 loc) · 14.4 KB
/
ESP8266WiFi.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
/* Copyright (c) 2015 Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. */
/*
Library for interfacing to the EspressIF ESP8266. Uses the 'NetworkJS'
library to provide a JS endpoint for HTTP.
For EspressIF ESP8266 firmware 0.25
```
Serial2.setup(115200, { rx: A3, tx : A2 });
console.log("Connecting to ESP8266");
var wifi = require("ESP8266WiFi").connect(Serial2, function() {
console.log("Connecting to WiFi");
wifi.connect("SSID","key", function() {
console.log("Connected");
require("http").get("http://www.espruino.com", function(res) {
console.log("Response: ",res);
res.on('data', function(d) {
console.log("--->"+d);
});
});
});
});
```
// TODO: AT+UART_CUR=115200,8,1,0,2 can enable flow control - add as a function with WIFI_SERIAL.setup(115200, { rx: .., tx : .., cts : WIFI_CTS });
*/
var ENCR_FLAGS = ["open","wep","wpa_psk","wpa2_psk","wpa_wpa2_psk"];
var at;
var socks = [];
var sockUDP = [];
var sockData = ["","","","",""];
var MAXSOCKETS = 5;
function udpToIPAndPort(data) {
return {
ip : data.charCodeAt(0)+"."+data.charCodeAt(1)+"."+data.charCodeAt(2)+"."+data.charCodeAt(3),
port : data.charCodeAt(5)<<8 | data.charCodeAt(4),
len : data.charCodeAt(7)<<8 | data.charCodeAt(6) // length of data
};
}
/*
`socks` can have the following states:
undefined : unused
true : connected and ready
"DataClose" : closed on esp8266, but with data still in sockData
"Wait" : waiting for connection (client), or for data to be sent
"WaitClose" : We asked to close it, but it hasn't been opened yet
"Accept" : opened by server, waiting for 'accept' to be called
"UDP" : reserved for UDP, but not yet opened
*/
// -----------------------------------------------------------------------------------
var netCallbacks = {
create : function(host, port, type, options) {
//console.log("CREATE ",arguments);
if (!at) return -1; // disconnected
/* Create a socket and return its index, host is a string, port is an integer.
If host isn't defined, create a server socket */
if (host===undefined && type!=2) {
var sckt = MAXSOCKETS;
socks[sckt] = "Wait";
sockData[sckt] = "";
at.cmd(`AT+CIPSERVER=1,${port}\r\n`, 10000, function(d) {
if (d=="OK") {
socks[sckt] = true;
} else {
socks[sckt] = undefined;
setTimeout(function() {
throw new Error("CIPSERVER failed ("+(d?d:"Timeout")+")");
}, 0);
}
});
return MAXSOCKETS;
} else {
var sckt = 0;
while (socks[sckt]!==undefined) sckt++; // find free socket
if (sckt>=MAXSOCKETS) return -7; // SOCKET_ERR_MAX_SOCK
sockData[sckt] = "";
socks[sckt] = "Wait";
var cmd;
if (type&3==2) {
// If there's a port specified, make a server now - otherwise reserve the socket and do it later
if (port) cmd = `AT+CIPSTART=${sckt},"UDP","255.255.255.255",${port},${port},2\r\n`;
else socks[sckt] = "UDP";
sockUDP[sckt] = true;
} else {
let socktype = "TCP";
if (options&&options.protocol=="https:") {
// This is a hack until https://github.com/espruino/Espruino/issues/2410 is fixed
socktype = "SSL";
if (port==80) port = 443;
}
cmd = `AT+CIPSTART=${sckt},"${socktype}",${JSON.stringify(host)},${port}\r\n`;
//console.log(cmd);
delete sockUDP[sckt];
}
if (cmd) at.cmd(cmd,10000,function cb(d) {
//console.log("CIPSTART "+JSON.stringify(d));
if (d=="ALREADY CONNECTED") return cb; // we're expecting an ERROR too
// x,CONNECT should have been received between times. If it hasn't appeared, it's an error.
if (d!="OK" || socks[sckt]!==true) socks[sckt] = -6; // SOCKET_ERR_NOT_FOUND
});
}
return sckt;
},
/* Close the socket. returns nothing */
close : function(sckt) {
//console.log("CLOSE ",arguments);
if (socks[sckt]=="Wait")
socks[sckt]="WaitClose";
else if (socks[sckt]!==undefined) {
// socket may already have been closed (eg. received 0,CLOSE)
if (socks[sckt]<0 || socks[sckt]=="UDP")
socks[sckt] = undefined;
else
// we need to a different command if we're closing a server
at.cmd(((sckt==MAXSOCKETS) ? 'AT+CIPSERVER=0' : ('AT+CIPCLOSE='+sckt))+'\r\n',1000, function() {
socks[sckt] = undefined;
});
}
},
/* Accept the connection on the server socket. Returns socket number or -1 if no connection */
accept : function() {
// console.log("Accept",sckt);
for (var i=0;i<MAXSOCKETS;i++)
if (socks[i]=="Accept") {
//console.log("Socket accept "+i,JSON.stringify(sockData),JSON.stringify(socks));
socks[i] = true;
return i;
}
return -1;
},
/* Receive data. Returns a string (even if empty).
If non-string returned, socket is then closed */
recv : function(sckt, maxLen) {
if (sockData[sckt]) {
var r;
if (sockData[sckt].length > maxLen) {
r = sockData[sckt].substr(0,maxLen);
sockData[sckt] = sockData[sckt].substr(maxLen);
} else {
r = sockData[sckt];
sockData[sckt] = "";
if (socks[sckt]=="DataClose")
socks[sckt] = undefined;
}
return r;
}
if (socks[sckt]<0) return socks[sckt]; // report an error
if (!socks[sckt]) return -1; // close it
return "";
},
/* Send data. Returns the number of bytes sent - 0 is ok.
Less than 0 */
send : function(sckt, data, type) {
if (!at) return -1; // disconnected
if (at.isBusy() || socks[sckt]=="Wait") return 0;
if (socks[sckt]<0) return socks[sckt]; // report an error
if (!socks[sckt]) return -1; // close it
//console.log("SEND ",arguments);
var d;
if (socks[sckt]=="UDP") {
d = udpToIPAndPort(data);
socks[sckt]="Wait";
at.cmd('AT+CIPSTART='+sckt+',"UDP","'+d.ip+'",'+d.port+','+d.port+',2\r\n',10000,function(d) {
if (d!="OK") socks[sckt] = -6; // SOCKET_ERR_NOT_FOUND
});
return 0;
}
//console.log("Send",sckt,data);
var returnVal = data.length;
var extra = "";
if (type==2) { // UDP
d = udpToIPAndPort(data);
extra = ',"'+d.ip+'",'+d.port;
data = data.substr(8,d.len);
returnVal = 8+d.len;
}
at.cmd(`AT+CIPSEND=${sckt},${data.length+extra}\r\n`, 2000, function cb(d) {
//console.log("SEND "+JSON.stringify(d));
if (d=="OK") {
at.register('> ', function(l) {
at.unregister('> ');
at.write(data);
return l.substr(2);
});
} else if (d=="Recv "+data.length+" bytes" || d=="busy s...") {
// all good, we expect this
// Not sure why we get "busy s..." in this case (2 sends one after the other) but it all seems ok.
} else if (d=="SEND OK") {
// we're ready for more data now
if (socks[sckt]=="WaitClose") netCallbacks.close(sckt);
socks[sckt]=true;
return;
} else {
socks[sckt]=undefined; // uh-oh. Error. If undefined it was probably a timeout
at.unregister('> ');
return;
}
return cb;
});
// if we obey the above, we shouldn't get the 'busy p...' prompt
socks[sckt]="Wait"; // wait for data to be sent
return returnVal;
}
};
//Handle +IPD input data from ESP8266
function ipdHandler(line) {
var colon = line.indexOf(":");
if (colon<0) return line; // not enough data here at the moment
var parms = line.substring(5,colon).split(",");
parms[1] = 0|parms[1];
var len = line.length-(colon+1);
var sckt = parms[0];
if (sockUDP[sckt]) {
var ip = (parms[2]||"0.0.0.0").split(".").map(function(x){return 0|x;});
var p = 0|parms[3]; // port
sockData[sckt] += String.fromCharCode(ip[0],ip[1],ip[2],ip[3],p&255,p>>8,len&255,len>>8);
}
if (len>=parms[1]) {
// we have everything
sockData[sckt] += line.substr(colon+1,parms[1]);
return line.substr(colon+parms[1]+1); // return anything else
} else {
// still some to get - use getData to request a callback
sockData[sckt] += line.substr(colon+1,len);
at.getData(parms[1]-len, function(data) { sockData[sckt] += data; });
return "";
}
}
// -----------------------------------------------------------------------------------
function sckOpen(ln) {
var sckt = ln[0];
//console.log("SCKOPEN", JSON.stringify(ln),"current",JSON.stringify(socks[sckt]));
if (socks[sckt]===undefined && socks[MAXSOCKETS]) {
// if we have a server and the socket randomly opens, it's a new connection
socks[sckt] = "Accept";
} else if (socks[sckt]=="Wait") {
// everything's good - we're connected
socks[sckt] = true;
} else {
// Otherwise we had an error - timeout? but it's now open. Close it.
at.cmd('AT+CIPCLOSE='+sckt+'\r\n',1000, function() {
socks[sckt] = undefined;
});
}
}
function sckClosed(ln) {
//console.log("CLOSED", JSON.stringify(ln));
socks[ln[0]] = sockData[ln[0]]!="" ? "DataClose" : undefined;
}
var wifiFuncs = {
ipdHandler:ipdHandler,
"debug" : function() {
return {
socks:socks,
sockData:sockData
};
},
// initialise the ESP8266
"init" : function(callback) {
at.cmd("ATE0\r\n",1000,function cb(d) { // turn off echo
//console.log(">>>>>"+JSON.stringify(d));
if (d=="ATE0" || d=="") return cb;
if (d=="OK") {
at.cmd("AT+CIPMUX=1\r\n",1000,function(d) { // turn on multiple sockets
if (d!="OK") callback("CIPMUX failed: "+(d?d:"Timeout"));
else at.cmd("AT+CIPDINFO=1\r\n",1000, function() { // enable IP Info in +IPD
at.cmd("AT+CIPSSLSIZE=4096\r\n",1000, function() { // set SSL buffer size
callback(null); // we don't care if this succeeds or not
});
});
});
} else callback("ATE0 failed: "+(d?d:"Timeout"));
});
},
"reset" : function(callback) {
//console.log("AT+RST");
at.cmd("\r\nAT+RST\r\n", 10000, function cb(d) {
//console.log(">>>>>"+JSON.stringify(d));
// 'ready' for 0.25, 'Ready.' for 0.50
if (d===undefined) callback("No 'ready' after AT+RST");
else if (d=="ready" || d=="Ready.") setTimeout(function() { wifiFuncs.init(callback); }, 1000);
else return cb;
});
},
"getVersion" : function(callback) {
at.cmd("AT+GMR\r\n", 1000, function(d) {
// works ok, but we could get more data
callback(null,d);
});
},
"connect" : function(ssid, key, callback) {
at.cmd("AT+CWMODE=1\r\n", 1000, function(cwm) {
if (cwm!="no change" && cwm!="OK") callback("CWMODE failed: "+(cwm?cwm:"Timeout"));
else at.cmd("AT+CWJAP="+JSON.stringify(ssid)+","+JSON.stringify(key)+"\r\n", 20000, function cb(d) {
if (["WIFI DISCONNECT","WIFI CONNECTED","WIFI GOT IP","+CWJAP:1"].indexOf(d)>=0) return cb;
if (d!="OK") setTimeout(callback,0,"WiFi connect failed: "+(d?d:"Timeout"));
else setTimeout(callback,0,null);
});
});
},
"getAPs" : function (callback) {
var aps = [];
at.cmdReg("AT+CWLAP\r\n", 5000, "+CWLAP:",
function(d) {
var ap = d.slice(8,-1).split(",");
aps.push({ ssid : JSON.parse(ap[1]),
enc: ENCR_FLAGS[ap[0]],
signal: parseInt(ap[2]),
mac : JSON.parse(ap[3]) });
},
function() { callback(null, aps); });
},
"getConnectedAP" : function(callback) {
var con;
at.cmdReg("AT+CWJAP?\r\n", 1000, "+CWJAP:",
function(d) { con=JSON.parse(d.slice(7)); },
function() { callback(null, con); });
},
"createAP" : function(ssid, key, channel, enc, callback) {
at.cmd("AT+CWMODE=2\r\n", 1000, function(cwm) {
if (cwm!="no change" && cwm!="OK" && cwm!="WIFI DISCONNECT") callback("CWMODE failed: "+(cwm?cwm:"Timeout"));
var encn = enc ? ENCR_FLAGS.indexOf(enc) : 0;
if (encn<0) callback("Encryption type "+enc+" not known - "+ENCR_FLAGS);
else at.cmd("AT+CWSAP="+JSON.stringify(ssid)+","+JSON.stringify(key)+","+channel+","+encn+"\r\n", 5000, function(cwm) {
if (cwm!="OK") callback("CWSAP failed: "+(cwm?cwm:"Timeout"));
else callback(null);
});
});
},
"getConnectedDevices" : function(callback) {
var devs = [];
this.at.cmd("AT+CWLIF\r\n",1000,function r(d) {
if (d=="OK") callback(null, devs);
else if (d===undefined || d=="ERROR") callback("Error");
else {
var e = d.split(",");
devs.push({ip:e[0], mac:e[1]});
return r;
}
});
},
"getIP" : function(callback) {
var ip;
at.cmdReg("AT+CIFSR\r\n", 1000, "+CIFSR", function(d) {
if (!ip && d.indexOf(',')>=0) ip=JSON.parse(d.slice(d.indexOf(',')+1));
}, function(d) {
if (d!="OK") callback("CIFSR failed: "+d);
else callback(null, ip);
});
},
/* Set the name of the access point */
"setHostname" : function(hostname, callback) {
at.cmd("AT+CWHOSTNAME="+JSON.stringify(hostname)+"\r\n",500,callback);
},
/* Ping the given address. Callback is called with the ping time
in milliseconds, or undefined if there is an error */
"ping" : function(addr, callback) {
var time;
at.cmd('AT+PING="'+addr+'"\r\n',1000,function cb(d) {
if (d && d[0]=="+") {
time=d.substr(1);
return cb;
} else if (d=="OK") callback(time); else callback();
});
}
};
exports.connect = function(usart, connectedCallback) {
wifiFuncs.at = at = require("AT").connect(usart);
require("NetworkJS").create(netCallbacks);
at.register("+IPD", ipdHandler);
at.register("+CW", line => { // handle +CWJAP/etc
var end = line.indexOf("\n");
if (end<0) return line;
print("+CW", line);
return line.substr(end+1);
});
at.registerLine("0,CONNECT", sckOpen);
at.registerLine("1,CONNECT", sckOpen);
at.registerLine("2,CONNECT", sckOpen);
at.registerLine("3,CONNECT", sckOpen);
at.registerLine("4,CONNECT", sckOpen);
at.registerLine("0,CLOSED", sckClosed);
at.registerLine("1,CLOSED", sckClosed);
at.registerLine("2,CLOSED", sckClosed);
at.registerLine("3,CLOSED", sckClosed);
at.registerLine("4,CLOSED", sckClosed);
at.registerLine("WIFI CONNECTED", function() { exports.emit("associated"); });
at.registerLine("WIFI GOT IP", function() { exports.emit("connected"); });
at.registerLine("WIFI DISCONNECTED", function() { exports.emit("disconnected"); });
wifiFuncs.reset(connectedCallback);
return wifiFuncs;
};