互联网账号共享器(DFRobot论坛首发)

在很久之前,共享一个账号是很简单的事情,只需要告诉对方账号和密码即可。但是随着时代的发展,除了账号密码之外,登陆还需要短信验证。比如目前国内功能最强,应用最广泛的某个网盘,很多资源都可以直接在上面找到免除了搜索之苦,另外和 BT 下载相比,只要保存了资源就不用担心内容不全(试想一下,历经千辛万苦淘到的种子,始终卡在99%远比找不到资源更加抑郁)。接下来的问题就是费用,每月25元的会员费用对于下载狂魔来说并不是问题,但是对于我这样一个月只有一两次下载需求的人来说就显得不那么划算,因此如果能和同学朋友共享一个账号是最好不过的了。唯一的问题是似乎网盘考虑到了这样的场景,经常在登陆时需要通过短信进行身份验证。如果能将验证短信一同分享给同学朋友,那就不存在这样的问题了。本文就介绍如何通过将身份验证短信发送到邮箱中来解决这个问题。

首先选择合适的开发板。这里我选择的是 LilyGo 推出的的T-Call开发板,它主控是 ESP32 板载了Sim800,因此可以用它实现收发短信,拨打电话和连接互联网的功能(ESP32本身可以实现WIFI上网,SIM800可以实现运营商的GPRS通讯功能)。

LilyGo T-Call

ESP32 是通过串口和SIM800 进行通讯的:

T-Call 串口
SIM800L 串口

ESP32 的一个特点是:具有OUTPUT功能的GPIOPin 可以任意定义为其他功能。例如,通过下面的代码指定了IO26/27作为串口使用:

#define MODEM_TX            27
#define MODEM_RX            26
// ESP32 对 SIM800串口
SerialAT.begin(115200,SERIAL_8N1, MODEM_RX, MODEM_TX);


SIIM800L 是通过串口用 AT 命令和 ESP32 打交道的,为了便于测试设计如下的测试代码:

#include <Wire.h>
 
  
 
// Set serial for AT commands (to the module)
 
#define SerialAT  Serial1
 
  
 
  
 
#define MODEM_RST             5
 
#define MODEM_PWRKEY          4
 
#define MODEM_POWER_ON       23
 
#define MODEM_TX             27
 
#define MODEM_RX             26
 
  
 
#define I2C_SDA              21
 
#define I2C_SCL              22
 
#define LED_GPIO             13
 
#define LED_ON               HIGH
 
#define LED_OFF              LOW
 
  
 
#define IP5306_ADDR          0x75
 
#define IP5306_REG_SYS_CTL0  0x00
 
  
 
void setup() {
 
    // put your setup code here, to run once:
 
    Serial.begin(115200);
 
  
 
    // Set GSM module baud rate and UART pins
 
    SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
 
    delay(1000);
 
  
 
    #ifdef MODEM_RST
 
    // Keep reset high
 
    pinMode(MODEM_RST, OUTPUT);
 
    digitalWrite(MODEM_RST, HIGH);
 
#endif
 
  
 
    pinMode(MODEM_PWRKEY, OUTPUT);
 
    pinMode(MODEM_POWER_ON, OUTPUT);
 
  
 
    // Turn on the Modem power first
 
    digitalWrite(MODEM_POWER_ON, HIGH);
 
  
 
    // Pull down PWRKEY for more than 1 second according to manual requirements
 
    digitalWrite(MODEM_PWRKEY, HIGH);
 
    delay(100);
 
    digitalWrite(MODEM_PWRKEY, LOW);
 
    delay(1000);
 
    digitalWrite(MODEM_PWRKEY, HIGH);
 
  
 
    // Initialize the indicator as an output
 
    pinMode(LED_GPIO, OUTPUT);
 
    digitalWrite(LED_GPIO, LED_OFF);
 
    
 
    Serial.println("Starting......");
 
}
 
  
 
void loop() {
 
  if (Serial.available()) {      // If anything comes in Serial (USB),
 
    SerialAT.write(Serial.read());   // read it and send it out Serial1
 
  }
 
  
 
  if (SerialAT.available()) {     // If anything comes in Serial1
 
    Serial.write(SerialAT.read());   // read it and send it out Serial (USB)
 
  }
 
}

这个代码前部分是 T-Call开发板提供的初始化代码,主要是对模块 Reset 之类的,然后在 Loop 中设置了一个USB串口和 SIM800L 互传的代码,就是USB串口的内容接收后转发到SerialAT上,同样的 SerialAT 上的内容也会转发给 USB串口。
烧写之后,可以在串口监视器中和SIM800L直接通讯,这样可以方便的进行命令的测试。需要注意的是在下面一定要选择“BOTHBL&CR”,这样每次发送的数据都是使用回车和换行结尾,这样才能保证 SIM800L 识别。

AT+CSQ 命令查询当前信号强度结果

因为国内的验证码短信大多是包含中文的,所以需要使用 AT+CMGF=0命令设置为 PDU模式(与之对应的另外一种模式是 TEXT, 是纯 ASCII )。切换为 PDU 之后,收到的一个条短信示例如下:
+CMGL: 2,0,"",800891683110304105F22412A10156155931010536300008020130224352233A30104EAC4E1C30119A8C8BC178014E3A003100330038003200390035FF0C8BF757286CE8518C987597624E2D8F9351654EE55B8C62106CE8518C
有兴趣的朋友可以去研究一下 PDU 的格式,这里我们使用一个在线的工具进行解析【参考1】

在线解析 PDU

将收到的短信内容(数字部分),填写到中间,然后使用 Convert 即可完成解析。
了解了基本原理后就可以开始着手代码编写了。代码主要分成两部分:第一部分,短信的接收。通过一个 AT 告诉 SIM800L 之后每次模块收到短信后会自动将其发送到串口,这样简化了操作,也避免因为SIM卡短信满了的情况;第二部分,通过 EMAIL 转发短信内容。这里使用 ESP32_MailClient 这个库来实现。另外因为 PDU 短信格式比较复杂,还涉及到了 Unicode 的编码问题,想 ESP32 直接解析有困难,因此,这里采用将短信内容嵌入一个解析的 HTML文件,然后按照附件发送的方法。用户收到邮件后,下载附件,打开即可看到内容(因为HTML中有需要执行的 JS 代码,邮箱系统预览附件无法看到最终结果)。
完整的代码如下:

#include <Wire.h>
 
#include "ESP32_MailClient.h"
 
#include "parta.h"
 
#include "partc.h"
 
  
 
// WIFI 账号和密码
 
#define WIFI_SSID "WIFINAME"
 
#define WIFI_PASSWORD "WIFIPASSWORD"
 
  
 
// 给 Serial1 赋予别称
 
#define SerialAT  Serial1
 
  
 
// 声明发送用到的对象
 
SMTPData smtpData;
 
  
 
// 设置短信为PDU 模式
 
String PDUModeCMD="AT+CMGF=0\n\r";
 
// 设置收到短信后直接发送给 ESP32, 不存储在 SIM卡上
 
String DirectSMSCMD="AT+CNMI=1,2,0,0,0\n\r";
 
  
 
#define MODEM_RST             5
 
#define MODEM_PWRKEY          4
 
#define MODEM_POWER_ON       23
 
#define MODEM_TX             27
 
#define MODEM_RX             26
 
  
 
#define I2C_SDA              21
 
#define I2C_SCL              22
 
#define LED_GPIO             13
 
#define LED_ON               HIGH
 
#define LED_OFF              LOW
 
  
 
#define IP5306_ADDR          0x75
 
#define IP5306_REG_SYS_CTL0  0x00
 
  
 
  
 
String SMSReady="";
 
char   c;
 
long int Elsp=-1;
 
  
 
void SendMail() {
 
        Serial.println("Sending email...");
 
  
 
        //【登录】服务器(服务器地址,端口,账号,授权码)
 
        smtpData.setLogin("smtp.163.com", 465, "SenderAddress@163.com", "SenderPassword"); 
 
        //启用【TLS】协议支持,在587端口  
 
        //smtpData.setSTARTTLS(true);
 
        //设置【发件人】
 
        smtpData.setSender("YOURNAME", "SenderAddress@163.com");
 
        //设置【优先级】(High, Normal, Low or 1 to 5 (1 is highest))
 
        smtpData.setPriority("Low");
 
        //设置【主题】
 
        smtpData.setSubject("SMS data");
 
        //设置【消息文本】(普通的文本或html)
 
        smtpData.setMessage("<div style=\"color:#ff0000;font-size:20px;\">SMS in attachment</div>", true);
 
        //添加【收件人】,可以添加多个收件人
 
        smtpData.addRecipient("Receiver@qq.com");
 
        //设置【存储类型】读取附加文件
 
        
 
        // put your setup code here, to run once:
 
        char *p;
 
        if ((p = (char *) malloc (PartA_html_size+SMSReady.length()+PartC_html_size)) == 0)
 
          {
 
            //out of memory
 
            Serial.println("Can't allocate memeory");
 
            smtpData.empty();
 
            return ;
 
          }
 
        for (int i=0;i<PartA_html_size;i++) {p[i]=PartA_html[i];}
 
        for (int i=0;i<SMSReady.length();i++) {p[i+PartA_html_size]=SMSReady[i];}
 
        for (int i=0;i<PartC_html_size;i++) {p[i+PartA_html_size+SMSReady.length()]=PartC_html[i];}
 
        smtpData.addAttachData("SMS.html", "image/png", (uint8_t *)p, PartA_html_size+SMSReady.length()+PartC_html_size); //从内存中
 
        //【发送】
 
        if (!MailClient.sendMail(smtpData)) {
 
        Serial.println("Error sending Email, " + MailClient.smtpErrorReason());
 
        }
 
        //【清空】缓存
 
        smtpData.empty();
 
        free(p);
 
}
 
  
 
void setup() {
 
    // USB 串口
 
    Serial.begin(115200);
 
  
 
    // ESP32 对 SIM800 串口
 
    SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
 
  
 
    Serial.print("Connecting to AP");
 
  
 
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
 
    while (WiFi.status() != WL_CONNECTED)
 
    {
 
      Serial.print(".");
 
      delay(200);
 
    }
 
  
 
    Serial.println("");
 
    Serial.println("WiFi connected.");
 
    Serial.println("IP address: ");
 
    Serial.println(WiFi.localIP());
 
    Serial.println();
 
  
 
    #ifdef MODEM_RST
 
      // Reset 模块
 
      pinMode(MODEM_RST, OUTPUT);
 
      digitalWrite(MODEM_RST, HIGH);
 
    #endif
 
  
 
    pinMode(MODEM_PWRKEY, OUTPUT);
 
    pinMode(MODEM_POWER_ON, OUTPUT);
 
  
 
    // Turn on the Modem power first
 
    digitalWrite(MODEM_POWER_ON, HIGH);
 
  
 
    // Pull down PWRKEY for more than 1 second according to manual requirements
 
    digitalWrite(MODEM_PWRKEY, HIGH);
 
    delay(100);
 
    digitalWrite(MODEM_PWRKEY, LOW);
 
    delay(1000);
 
    digitalWrite(MODEM_PWRKEY, HIGH);
 
  
 
    // Initialize the indicator as an output
 
    pinMode(LED_GPIO, OUTPUT);
 
    digitalWrite(LED_GPIO, LED_OFF);
 
    
 
    Serial.println("Power on");
 
    delay(20000);
 
    Serial.println("Start");
 
  
 
    Serial.println("Set to PDU Mode");
 
    SerialAT.print(PDUModeCMD);
 
    delay(200);
 
    Serial.println("Direct show SMS to uart");
 
    SerialAT.print(DirectSMSCMD);
 
    delay(200);   
 
}
 
  
 
  
 
void loop() {
 
  if (Serial.available()) {      // If anything comes in Serial (USB),
 
    SerialAT.write(Serial.read());   // read it and send it out Serial1
 
  }
 
  
 
  if (SerialAT.available()) {     // If anything comes in Serial1
 
    Elsp=millis();
 
    c=SerialAT.read();
 
    SMSReady=SMSReady+c;
 
    Serial.write(c);   // read it and send it out Serial (USB)
 
  }
 
  
 
  if ((Elsp!=-1)&&(millis()-Elsp>500)) {
 
      Serial.print("Buffer [");
 
      Serial.print(SMSReady);
 
      Serial.println("]");
 
      if (SMSReady.indexOf("+CMT:")!=-1) {
 
            // 删除 "+CMT:" 以及他之前的字符(主要是 \n \r)
 
            SMSReady=SMSReady.substring(SMSReady.indexOf("+CMT:")+5);
 
            SMSReady=SMSReady.substring(SMSReady.indexOf('\n')+1);
 
      Serial.print("<");
 
      Serial.print(SMSReady);
 
      Serial.println(">");
 
  
 
            SendMail();
 
            SMSReady="";
 
            Elsp=-1;
 
        }
 
      else {SMSReady="";}
 
      Elsp=-1;
 
    }
 
}

运行结果:

收到的邮件
打下载附件后打开即可看到结果


最后特别注意的事情还有:

1. SIM卡的选择。切勿选择物联网卡,这种卡没有短信收发和电话拨打的功能。另外,我测试了上电的联通和电信卡,前者工作正常,后者虽然在手机上显示有2G网络,但是无法正常工作;

2. 这个开发板背后的 SIM插槽比较松,我拿到第一天就干掉卡片了,好在装上去还能正常工作,所以在使用时无比小心;

3. 如果发现无法接收到短信,请检查信号强度,或者用其他手机拨打号码查看能否接通;

4. SIM800L 提供上网功能的,但是同ESP32_MailClient不兼容,所以最终还是使用 ESP32 的 WIFI 功能进行上网发送邮件;

本文首发在 DFRobot 论坛

https://mc.dfrobot.com.cn/thread-307727-1-1.html


参考:

1. http://www.sendsms.cn/pdu/

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注