SmartOS/Drivers/Esp8266/Esp8266.cpp

860 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Sys.h"
#include "Task.h"
#include "Time.h"
#include "Esp8266.h"
#include "EspTcp.h"
#include "EspUdp.h"
#include "WaitExpect.h"
#include "Config.h"
#define NET_DEBUG DEBUG
//#define NET_DEBUG 0
#if NET_DEBUG
#define net_printf debug_printf
#else
#define net_printf(format, ...)
#endif
/*
注意事项
1、设置模式AT+CWMODE需要重启后生效AT+RST
2、AP模式下查询本机IP无效可能会造成死机
3、开启server需要多连接作为基础AT+CIPMUX=1
4、单连接模式多连接模式 收发数据有参数个数区别
*/
/******************************** Esp8266 ********************************/
Esp8266::Esp8266(ITransport* port, Pin power, Pin rst)
{
Set(port);
if(power != P0) _power.Init(power, false);
if(rst != P0) _rst.Init(rst, true);
AutoConn = false;
Led = nullptr;
NetReady = nullptr;
_Expect = nullptr;
Buffer(_sockets, 5 * 4).Clear();
Mode = SocketMode::STA_AP;
SSID = new String();
Pass = new String();
}
Esp8266::~Esp8266()
{
delete SSID;
delete Pass;
}
void Esp8266::OpenAsync()
{
if(Opened) return;
Sys.AddTask([](void* param) { ((Esp8266*)param)->Open(); }, this, 0, -1, "Esp8266");
}
bool Esp8266::OnOpen()
{
if(!PackPort::OnOpen()) return false;
// 先关一会电,然后再上电,让它来一个完整的冷启动
if (!_power.Empty())
{
_power.Open(); // 使用前必须Open
_power.Down(20);
}
if (!_rst.Empty()) _rst.Open(); // 使用前必须Open
// 每两次启动会有一次打开失败,交替
if(!_rst.Empty())
Reset(false); // 硬重启
else
Reset(true); // 软件重启命令
// 如果首次加载,则说明现在处于出厂设置模式,需要对模块恢复出厂设置
auto cfg = Config::Current->Find("NET");
//if(!cfg) Restore();
for(int i=0; i<2; i++)
{
// 等待模块启动进入就绪状态
if(!WaitForCmd("ready", 3000))
{
if (!Test())
{
net_printf("Esp8266::Open 打开失败!");
return false;
}
}
if(cfg || i==1) break;
Restore();
}
// 开回显
Echo(true);
#if NET_DEBUG
// 获取版本
auto ver = GetVersion();
//net_printf("版本:");
//ver.Show(true);
#endif
// 默认Both
if(Mode == SocketMode::Wire) Mode = SocketMode::STA_AP;
if (GetMode() != Mode)
{
if(!SetMode(Mode))
{
net_printf("设置Station模式失败");
return false;
}
}
Config();
SetDHCP(Mode, true);
// 等待WiFi自动连接
if(!AutoConn || !WaitForCmd("WIFI CONNECTED", 3000))
{
if (!SSID || !*SSID || Mode == SocketMode::STA_AP)
{
net_printf("启动AP模式!\r\n");
String wifiName = "WsLink-";
wifiName += Buffer(Sys.ID, 3).ToHex();
int chn = (Sys.Ms() % 14) + 1;
SetAP(wifiName, "", chn, 0, 1, 1);
}
if (SSID && *SSID)
{
if (!JoinAP(*SSID, *Pass)) // Pass 可以为空
{
net_printf("连接WiFi失败\r\n");
return false;
}
}
}
// 拿到IP网络就绪
if(Mode == SocketMode::Station || Mode == SocketMode::STA_AP)
{
IP = GetIP(true);
SaveConfig();
ShowConfig();
}
if(NetReady) NetReady(*this);
return true;
}
void Esp8266::OnClose()
{
_power.Close();
_rst.Close();
PackPort::OnClose();
}
// 配置网络参数
void Esp8266::Config()
{
// 设置多连接
SetMux(true);
// 设置IPD
SetIPD(true);
auto mac = Mac;
SetMAC(true, mac);
mac[5]++;
SetMAC(false, mac);
SetAutoConn(AutoConn);
}
ISocket* Esp8266::CreateSocket(ProtocolType type)
{
auto es = (EspSocket**)_sockets;
int i = 0;
for(i = 0; i < 5; i++)
{
if(es[i] == nullptr) break;
}
if(i >= 5 )
{
net_printf("没有空余的Socket可用了 !\r\n");
return nullptr;
}
switch(type)
{
case ProtocolType::Tcp:
return es[i] = new EspTcp(*this, i);
case ProtocolType::Udp:
return es[i] = new EspUdp(*this, i);
default:
return nullptr;
}
}
// 启用DNS
bool Esp8266::EnableDNS() { return true; }
// 启用DHCP
bool Esp8266::EnableDHCP() { Mode = SocketMode::STA_AP; return true;/* return SetDHCP(SocketMode::Both, true); */}
// 发送指令,在超时时间内等待返回期望字符串,然后返回内容
String Esp8266::Send(const String& cmd, cstring expect, cstring expect2, uint msTimeout)
{
TS("Esp8266::Send");
String rs;
auto& task = Task::Current();
// 判断是否正在发送其它指令
if(_Expect)
{
#if NET_DEBUG
auto w = (WaitExpect*)_Expect;
net_printf("Esp8266::Send %d 正在发送 ", w->TaskID);
if(w->Command)
w->Command->Trim().Show(false);
else
net_printf("数据");
net_printf(" %d 无法发送 ", task.ID);
cmd.Trim().Show(true);
#endif
return rs;
}
// 在接收事件中拦截
WaitExpect we;
we.TaskID = task.ID;
//we.Command = &cmd;
we.Result = &rs;
we.Key1 = expect;
we.Key2 = expect2;
_Expect = &we;
#if NET_DEBUG
bool EnableLog = true;
#endif
if(cmd)
{
// 设定小灯快闪时间,单位毫秒
if(Led) Led->Write(50);
Port->Write(cmd);
#if NET_DEBUG
// 只有AT指令显示日志
if(!cmd.StartsWith("AT") || (expect && expect[0] == '>')) EnableLog = false;
if(EnableLog)
{
we.Command = &cmd;
net_printf("%d=> ", task.ID);
cmd.Trim().Show(true);
}
#endif
}
we.Wait(msTimeout);
if(_Expect == &we) _Expect = nullptr;
//if(rs.Length() > 4) rs.Trim();
#if NET_DEBUG
if(EnableLog && rs)
{
net_printf("%d<= ", task.ID);
rs.Trim().Show(true);
}
#endif
return rs;
}
// 发送命令,自动检测并加上\r\n等待响应OK
bool Esp8266::SendCmd(const String& cmd, uint msTimeout)
{
TS("Esp8266::SendCmd");
static const cstring ok = "OK";
static const cstring err = "ERROR";
String cmd2;
// 只有AT指令需要检查结尾其它指令不检查避免产生拷贝
auto p = &cmd;
if(cmd.StartsWith("AT") && !cmd.EndsWith("\r\n"))
{
cmd2 = cmd;
cmd2 += "\r\n";
p = &cmd2;
}
// 二级拦截。遇到错误也马上结束
auto rt = Send(*p, ok, err, msTimeout);
return rt.Contains(ok);
}
bool Esp8266::WaitForCmd(cstring expect, uint msTimeout)
{
String rs;
// 在接收事件中拦截
WaitExpect we;
we.Result = &rs;
we.Key1 = expect;
we.Capture = false;
_Expect = &we;
// 等待收到数据
bool rt = we.Wait(msTimeout);
_Expect = nullptr;
return rt;
}
void ParseFail(cstring name, const Buffer& bs)
{
#if NET_DEBUG
if(bs.Length() == 0) return;
int p = 0;
if(p < bs.Length() && bs[p] == ' ') p++;
if(p < bs.Length() && bs[p] == '\r') p++;
if(p < bs.Length() && bs[p] == '\n') p++;
// 无法识别的数据可能是空格前缀,需要特殊处理
auto str = bs.Sub(p, -1).AsString();
if(str)
{
net_printf("Esp8266:%s 无法识别[%d]", name, bs.Length());
//if(bs.Length() == 2) net_printf("%02X %02X ", bs[0], bs[1]);
str.Show(true);
}
#endif
}
// 引发数据到达事件
uint Esp8266::OnReceive(Buffer& bs, void* param)
{
if(bs.Length() == 0) return 0;
TS("Esp8266::OnReceive");
//!!! 分析+IPD数据和命令返回特别要注意粘包
int s = 0;
int p = 0;
auto str = bs.AsString();
while(p >= 0 && p < bs.Length())
{
s = p;
p = str.IndexOf("+IPD,", s);
// +IPD之前之后的数据留给命令分析
int size = p>=0 ? p-s : bs.Length()-s;
if(size > 0)
{
if(_Expect)
{
uint rs = ParseReply(bs.Sub(s, size));
#if NET_DEBUG
// 如果没有吃完,剩下部分报未识别
//if(rs < size) ParseFail("ParseReply", bs.Sub(s + rs, size - rs));
// 不要报未识别了,反正内部会全部吃掉
#endif
}
else
{
#if NET_DEBUG
ParseFail("NoExpect", bs.Sub(s, size));
#endif
}
}
// +IPD开头的数据作为收到数据
if(p >= 0)
{
if(p + 5 >= bs.Length())
{
#if NET_DEBUG
ParseFail("+IPD<=5", bs.Sub(p, -1));
#endif
break;
}
else
{
uint rs = ParseReceive(bs.Sub(p, -1));
if(rs <= 0)
{
#if NET_DEBUG
ParseFail("ParseReceive", bs.Sub(p + rs, -1));
#endif
break;
}
// 游标移到下一组数据
p += rs;
}
}
}
return 0;
}
/*
+IPD 接收网络数据
1) 单连接时:
(+CIPMUX=0)
+IPD,<len>[,<remote IP>,<remote
port>]:<data>
2) 多连接时:
(+CIPMUX=1)
+IPD,<link ID>,<len>[,<remote IP>,<remote port>]:<data>
*/
// 分析+IPD接收数据。返回被用掉的字节数
uint Esp8266::ParseReceive(const Buffer& bs)
{
TS("Esp8266::ParseReceive");
auto str = bs.AsString();
StringSplit sp(str, ",");
// 跳过第一段+IPD
auto rs = sp.Next();
if(!(rs = sp.Next())) return 0;
int idx = rs.ToInt();
if(idx < 0 || idx > 4) return 0;
if(!(rs = sp.Next())) return 0;
int len = rs.ToInt();
IPEndPoint ep;
if(!(rs = sp.Next())) return 0;
ep.Address = IPAddress::Parse(rs);
// 提前改变下一个分隔符
sp.Sep = ":";
if(!(rs = sp.Next())) return 0;
ep.Port = rs.ToInt();
// 后面是数据
sp.Next();
int s = sp.Position;
if(s <= 0) return 0;
// 分发给各个Socket
auto es = (EspSocket**)_sockets;
auto sk = es[idx];
if(sk)
{
// 校验数据长度
int len2 = len;
int remain = bs.Length() - s;
if(remain < len2)
{
len2 = remain;
net_printf("剩余数据长度 %d 小于标称长度 %d \r\n", remain, len);
}
sk->OnProcess(bs.Sub(s, len2), ep);
}
return sp.Position + len;
}
// 分析关键字。返回被用掉的字节数
uint Esp8266::ParseReply(const Buffer& bs)
{
if(!_Expect) return 0;
// 拦截给同步方法
auto we = (WaitExpect*)_Expect;
bool rs = we->Parse(bs);
// 如果内部已经适配,则清空
if(!we->Result) _Expect = nullptr;
return rs;
}
/******************************** 基础AT指令 ********************************/
// 基础AT指令
bool Esp8266::Test()
{
for(int i=0; i<10; i++)
{
if(SendCmd("AT", 500)) return true;
Reset(false);
}
return false;
}
bool Esp8266::Reset(bool soft)
{
if(soft)
return SendCmd("AT+RST");
else
{
_rst.Up(100);
return true;
}
}
/*
AT 版本信息
基于的SDK版本信息
编译生成时间
*/
String Esp8266::GetVersion()
{
return Send("AT+GMR\r\n", "OK");
}
bool Esp8266::Sleep(uint ms)
{
String cmd = "AT+GSLP=";
cmd += ms;
return SendCmd(cmd);
}
bool Esp8266::Echo(bool open)
{
String cmd = "ATE";
cmd = cmd + (open ? '1' : '0');
return SendCmd(cmd);
}
// 恢复出厂设置将擦写所有保存到Flash的参数恢复为默认参数。会导致模块重启
bool Esp8266::Restore()
{
return SendCmd("AT+RESTORE");
}
/******************************** Esp8266 ********************************/
/*
发送:
"AT+CWMODE=1
"
返回:
"AT+CWMODE=1
OK
"
*/
bool Esp8266::SetMode(SocketMode mode)
{
String cmd = "AT+CWMODE=";
cmd += (byte)mode;
if (!SendCmd(cmd)) return false;
Mode = mode;
return true;
}
/*
发送:
"AT+CWMODE?
"
返回:
"AT+CWMODE? +CWMODE:1
OK
"
*/
SocketMode Esp8266::GetMode()
{
TS("Esp8266::GetMode");
auto mod = SocketMode::Station;
auto rs = Send("AT+CWMODE?\r\n", "OK");
if (!rs) return mod;
int p = rs.IndexOf(':');
if(p < 0) return mod;
return (SocketMode)rs.Substring(p+1, 1).ToInt();
}
// +CWJAP:<ssid>,<bssid>,<channel>,<rssi>
// <bssid>目标AP的MAC地址
String Esp8266::GetJoinAP()
{
return Send("AT+CWJAP?\r\n", "OK");
}
/*
发送:
"AT+CWJAP="yws007','yws52718"
"
返回: ( 一般3秒钟能搞定 密码后面位置会停顿, WIFI GOT IP 前面也会停顿 )
"AT+CWJAP="yws007','yws52718"WIFI CONNECTED
WIFI GOT IP
OK
"
也可能 (已连接其他WIFI) 70bytes
"AT+CWJAP="yws007','yws52718" WIFI DISCONNECT
WIFI CONNECTED
WIFI GOT IP
OK
"
密码错误返回
"AT+CWJAP="yws007','7" +CWJAP:1
FAIL
"
*/
bool Esp8266::JoinAP(const String& ssid, const String& pass)
{
#if NET_DEBUG
TimeCost tc;
#endif
String cmd = "AT+CWJAP=";
cmd = cmd + "\"" + ssid + "\",\"" + pass + "\"";
bool rs = SendCmd(cmd, 15000);
#if NET_DEBUG
net_printf("Esp8266::JoinAP ");
if(rs)
net_printf("成功 ");
else
net_printf("失败 ");
tc.Show();
#endif
return rs;
}
/*
返回:
"AT+CWQAP WIFI DISCONNECT
OK
"
*/
bool Esp8266::UnJoinAP()
{
return SendCmd("AT+CWQAP", 2000);
}
/*
开机自动连接WIFI
*/
bool Esp8266::SetAutoConn(bool enable)
{
String cmd = "AT+CWAUTOCONN=";
return SendCmd(cmd + (enable ? '1' : '0'));
}
// +CWLAP:<enc>,<ssid>,<rssi>,<mac>,<ch>,<freq offset>,<freq calibration>
// freq offset, AP频偏单位kHz转为成ppm需要除以2.4
// freq calibration频偏校准值
String Esp8266::LoadAPs()
{
return Send("AT+CWLAP\r\n", "OK");
}
// +CWSAP:<ssid>,<pwd>,<chl>,<ecn>,<max conn>,<ssid hidden>
String Esp8266::GetAP()
{
return Send("AT+CWSAP\r\n", "OK");
}
/*
注意: 指令只有在 softAP 模式开启后有效
参数说明:
<ssid> 字符串参数,接入点名称
<pwd> 字符串参数密码强度范围8 ~ 64 字节 ASCII
<chl> 通道号
<ecn> 加密方式,不支持 WEP
0 OPEN
2 WPA_PSK
3 WPA2_PSK
4 WPA_WPA2_PSK
<max conn> 允许连接 ESP8266 soft-AP 的最多 station 数目
取值范围 [1, 4]
<ssid hidden> 默认为 0开启广播 ESP8266 soft-AP SSID
0 广播 SSID
1 不广播 SSID
*/
bool Esp8266::SetAP(const String& ssid, const String& pass, byte channel, byte ecn, byte maxConnect, bool hidden)
{
String cmd = "AT+CWSAP=";
cmd = cmd + "\"" + ssid + "\",\"" + pass + "\"," + channel + ',' + ecn /*+ ',' + maxConnect + ',' + (hidden ? '1' : '0')*/;
return SendCmd(cmd, 1600);
}
// <ip addr>,<mac>
// 查询连接到AP的Stations信息。无法查询DHCP接入
String Esp8266::LoadStations()
{
return Send("AT+CWLIF\r\n", "OK");
}
bool Esp8266::GetDHCP(bool* sta, bool* ap)
{
auto rs = Send("AT+CWDHCP?\r\n", "OK");
if(!rs) return false;
byte n = rs.ToInt();
*sta = n & 0x01;
*ap = n & 0x02;
return true;
}
bool Esp8266::SetDHCP(SocketMode mode, bool enable)
{
byte m = 0;
switch(mode)
{
case SocketMode::Station:
m = 1;
break;
case SocketMode::AP:
m = 0;
break;
case SocketMode::STA_AP:
m = 2;
break;
default:
return false;
}
String cmd = "AT+CWDHCP=";
return SendCmd(cmd + m + ',' + enable);
}
MacAddress Esp8266::GetMAC(bool sta)
{
auto rs = Send(sta ? "AT+CIPSTAMAC?\r\n" : "AT+CIPAPMAC?\r\n", "OK");
int p = rs.IndexOf(':');
if(p < 0) return MacAddress::Empty();
return MacAddress::Parse(rs.Substring(p + 1, -1));
}
// 设置MAC会导致WiFi连接断开
bool Esp8266::SetMAC(bool sta, const MacAddress& mac)
{
String cmd = sta ? "AT+CIPSTAMAC" : "AT+CIPAPMAC";
cmd = cmd + "=\"" + mac.ToString().Replace('-', ':') + '\"';
return SendCmd(cmd);
}
IPAddress Esp8266::GetIP(bool sta)
{
auto rs = Send(sta ? "AT+CIPSTA?\r\n" : "AT+CIPAP?\r\n", "OK");
int p = rs.IndexOf("ip:\"");
if(p < 0) return IPAddress::Any();
p += 4;
int e = rs.IndexOf("\"", p);
if(e < 0) return IPAddress::Any();
return IPAddress::Parse(rs.Substring(p, e - p));
}
/******************************** TCP/IP ********************************/
/*
STATUS:<stat>
+CIPSTATUS:<link ID>,<type>,<remote IP>,<remote port> ,<local port>,<tetype>
参数说明:
<stat>
2获得 IP
3已连接
4断开连接
5未连接到 WiFi
<link ID> ⺴˷ 络连接 ID (0~4),⽤ݒ于多连接的情况
<type> 字符串参数, “TCP” 或者 “UDP”
<remote IP> 字符串,远端 IP 地址
<remote port> 远端端⼝Ծ值
<local port> ESP8266 本地端⼝Ծ值
<tetype>
0: ESP8266 作为 client
1: ESP8266 作为 server
*/
String Esp8266::GetStatus()
{
return Send("AT+CIPSTATUS?\r\n", "OK");
}
bool Esp8266::GetMux()
{
auto rs = Send("AT+CIPMUX?\r\n", "OK");
int p = rs.IndexOf(':');
if(p < 0) return false;
return rs.Substring(p + 1, 1) != "0";
}
bool Esp8266::SetMux(bool enable)
{
String cmd = "AT+CIPMUX=";
return SendCmd(cmd + (enable ? '1' : '0'));
}
bool Esp8266::Update()
{
return SendCmd("AT+CIPUPDATE");
}
bool Esp8266::Ping(const IPAddress& ip)
{
String cmd = "AT+PING=";
return SendCmd(cmd + ip);
}
bool Esp8266::SetIPD(bool enable)
{
String cmd = "AT+CIPDINFO=";
return SendCmd(cmd + (enable ? '1' : '0'));
}