带 ESP8266 客户端的 Flask REST API 服务器

Flask REST API Server with ESP8266 Client

我使用 Arduino Nano+ESP8266(前端)和 Flask REST API 服务器后端开发了一个密码验证系统。

以下是我的 ESP8266 代码,它将 HTTP POST 请求传递给 REST API 服务器,

#include <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <ESP8266HTTPClient.h>
#include<SoftwareSerial.h>

SoftwareSerial link(4, 0);
byte greenLED = 13;
byte statusLED = 14;
String hexstring = "";

// Replace with your network credentials
const char *ssid     = "***********";
const char *password = "***********";

const long utcOffsetInSeconds = 240;

// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "asia.pool.ntp.org", utcOffsetInSeconds);

//Week Days
String weekDays[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

//Month names
String months[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};

void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);
  Serial.print("Initializing System");
  for (int i = 10; i > 0; i--) {
    delay(500);
    Serial.print(i); Serial.println(' ');
  }
  pinMode(statusLED, OUTPUT);

  // Connect to Wi-Fi
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    link.begin(9600);
    link.setTimeout(100);
    // clear any pending stuff
    link.readString();
    pinMode(greenLED, OUTPUT);
  }

  // Initialize a NTPClient to get time
  timeClient.begin();

  timeClient.setTimeOffset(19770);
}

void loop() {
  timeClient.update();

  unsigned long epochTime = timeClient.getEpochTime();
  String formattedTime = timeClient.getFormattedTime();
  int currentHour = timeClient.getHours();
  int currentMinute = timeClient.getMinutes();
  int currentSecond = timeClient.getSeconds();
  String weekDay = weekDays[timeClient.getDay()];

  //Get a time structure
  struct tm *ptm = gmtime ((time_t *)&epochTime);
  int monthDay = ptm->tm_mday;
  int currentMonth = ptm->tm_mon + 1;
  String currentMonthName = months[currentMonth - 1];
  int currentYear = ptm->tm_year + 1900;

  //Print complete date:
  String currentDate = String(currentYear) + String(currentMonth) + String(monthDay) + String(currentHour) + String(currentMinute)  + String(currentSecond);


  delay(1000);

  if (link.available()) {
    String rec = link.readString();
    Serial.print(F("rec:")); Serial.println(rec);
    if (rec.length() > 0) {    // print it out
      Serial.println(F("Received data"));
      for (size_t i = 0; i < rec.length(); i++) {
        digitalWrite(greenLED, HIGH); //flash led to show data is arriving
        delay(20);
        digitalWrite(greenLED, LOW);
        if (i % 8 == 0) {
          Serial.println();
        }
        Serial.print(" 0x");
        if (rec[i] < 10) {
          Serial.print("0");
          hexstring += '0';
        }
        Serial.print(rec[i], HEX);
        hexstring += String(rec[i], HEX);

      }
      Serial.println();
      Serial.print("Current Date: ");
      Serial.println(currentDate);
      Serial.println("Your String Sir:");
      Serial.print(hexstring);
      Serial.println();
      //confirmation();
      
      String sub1 = hexstring.substring(0, 2);
      String sub2 = hexstring.substring(16, 32);
      String sub3 = hexstring.substring(32, 34);

      if (sub3 == "31") { //New User Registartion
        String data =  "{\"user_id\" :\"" + sub1 + "\",\"encrypted_password\" :\"" + sub2 + "\",\"option\" :\"" + sub3 + "\" ,\"request_datetime\" :\"" + currentDate + "\"}";
        HTTPClient http; //Declare object of class HTTPClient
        http.begin("http://192.168.8.228:5000/users/");      //Specify request destination
        http.addHeader("Content-Type", "application/json");
        int httpCode = http.POST(data.c_str());

        if (httpCode == 200) {
          String payload = http.getString();   //Get the response payload
          Serial.println(httpCode);   //Print HTTP return code
          Serial.println(payload);    //Print request response payload
          if (payload == "Code1") {
            link.read();
            Serial.println(F("Prompt other side Confirmation"));
            link.print("Success_Code1");
            digitalWrite(statusLED, HIGH); //flash led to show data is arriving
            delay(20);
            digitalWrite(statusLED, LOW);
          }
          else {
            link.read();
            Serial.println(F("Prompt other side Error1"));
            link.print("Error 1");
          }
        }
        http.end();
        rec = ""; // clear for next receive
        hexstring = ""; //Clear hexstring
      }

      else if (sub3 == "32") { //Confirm Password
        String data ="{\"user_id\":\"" + sub1 + "\",\"encrypted_password\":\"" + sub2 + "\",\"option\":\"" + sub3 + "\" ,\"request_datetime\":\"" + currentDate + "\"}";
        
        HTTPClient http; //Declare object of class HTTPClient
        String link2 = "http://192.168.8.228:5000/users/" + sub1;
        http.begin(link2);      //Specify request destination
        http.addHeader("Content - Type", "application / json");
        int httpCode = http.POST(data.c_str());
        delay(1000);
        Serial.println(data.c_str());
        Serial.println(httpCode);
        
        if (httpCode == 200) {
          String payload = http.getString();   //Get the response payload
          Serial.println(httpCode);   //Print HTTP return code
          Serial.println(payload);    //Print request response payload
          if (payload == "Code2") {
            link.read();
            Serial.println(F("Prompt other side Confirmation"));
            link.print("Success_Code1");
            digitalWrite(statusLED, HIGH); //flash led to show data is arriving
            delay(20);
            digitalWrite(statusLED, LOW);
          }
        }
        else {
          link.read();
          Serial.println(F("Prompt other side Error1"));
          link.print("Error 1");
        }
        http.end();
        rec = ""; // clear for next receive
        hexstring = ""; //Clear hexstring
      }

      else if (sub3 == "33") { //Change Password
        String data =  "{\"user_id\" :\"" + sub1 + "\",\"encrypted_password\" :\"" + sub2 + "\",\"option\" :\"" + sub3 + "\" ,\"request_datetime\" :\"" + currentDate + "\"}";
        HTTPClient http; //Declare object of class HTTPClient
        http.begin("http://192.168.8.228:5000/users/");      //Specify request destination
        http.addHeader("Content-Type", "application/json");
        int httpCode = http.POST(data.c_str());

        if (httpCode == 200) {
          String payload = http.getString();   //Get the response payload
          Serial.println(httpCode);   //Print HTTP return code
          Serial.println(payload);    //Print request response payload
          if (payload == "Code3") {
            link.read();
            Serial.println(F("Prompt other side Confirmation"));
            link.print("Success_Code1");
            digitalWrite(statusLED, HIGH); //flash led to show data is arriving
            delay(20);
            digitalWrite(statusLED, LOW);
          }
        }
        else {
          link.read();
          Serial.println(F("Prompt other side Error1"));
          link.print("Error 1");
        }
        http.end();
        rec = ""; // clear for next receive
        hexstring = ""; //Clear hexstring
      }

    }
  }
}

如你所见,有3种方法,

新用户注册码部分:

if (sub3 == "31") { //New User Registartion
        String data =  "{\"user_id\" :\"" + sub1 + "\",\"encrypted_password\" :\"" + sub2 + "\",\"option\" :\"" + sub3 + "\" ,\"request_datetime\" :\"" + currentDate + "\"}";
        HTTPClient http; //Declare object of class HTTPClient
        http.begin("http://192.168.8.228:5000/users/");      //Specify request destination
        http.addHeader("Content-Type", "application/json");
        int httpCode = http.POST(data.c_str());

        if (httpCode == 200) {
          String payload = http.getString();   //Get the response payload
          Serial.println(httpCode);   //Print HTTP return code
          Serial.println(payload);    //Print request response payload
          if (payload == "Code1") {
            link.read();
            Serial.println(F("Prompt other side Confirmation"));
            link.print("Success_Code1");
            digitalWrite(statusLED, HIGH); //flash led to show data is arriving
            delay(20);
            digitalWrite(statusLED, LOW);
          }
          else {
            link.read();
            Serial.println(F("Prompt other side Error1"));
            link.print("Error 1");
          }
        }
        http.end();
        rec = ""; // clear for next receive
        hexstring = ""; //Clear hexstring
      } 

验证密码代码部分:

else if (sub3 == "32") { //Confirm Password
        String data ="{\"user_id\":\"" + sub1 + "\",\"encrypted_password\":\"" + sub2 + "\",\"option\":\"" + sub3 + "\" ,\"request_datetime\":\"" + currentDate + "\"}";
        
        HTTPClient http; //Declare object of class HTTPClient
        String link2 = "http://192.168.8.228:5000/users/" + sub1;
        http.begin(link2);      //Specify request destination
        http.addHeader("Content - Type", "application / json");
        int httpCode = http.POST(data.c_str());
        delay(1000);
        Serial.println(data.c_str());
        Serial.println(httpCode);
        
        if (httpCode == 200) {
          String payload = http.getString();   //Get the response payload
          Serial.println(httpCode);   //Print HTTP return code
          Serial.println(payload);    //Print request response payload
          if (payload == "Code2") {
            link.read();
            Serial.println(F("Prompt other side Confirmation"));
            link.print("Success_Code1");
            digitalWrite(statusLED, HIGH); //flash led to show data is arriving
            delay(20);
            digitalWrite(statusLED, LOW);
          }
        }
        else {
          link.read();
          Serial.println(F("Prompt other side Error1"));
          link.print("Error 1");
        }
        http.end();
        rec = ""; // clear for next receive
        hexstring = ""; //Clear hexstring
      }

下面是我的Flask/PythonAPI服务器后端代码:

from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
import uuid


app = Flask(__name__)
db = SQLAlchemy(app)
DEBUG = True

# app.config['SECRET_KEY'] = 'secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root@localhost:3306/app'
print("App2 Change2")


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True, index=True)
    user_id = db.Column(db.VARCHAR(10), nullable=False, unique=True)
    encrypted_password = db.Column(db.VARCHAR(20), nullable=False)
    option = db.Column(db.VARCHAR(3), nullable=False)
    request_datetime = db.Column(db.VARCHAR(20), nullable=False)
    public_id = db.Column(db.VARCHAR(100), nullable=False)

    def __repr__(self):
        return f'User <{self.public_id}>'


db.create_all()


@app.route('/')
def home():
    return {
        'message': 'Welcome to Vinod Test App'
    }


@app.route('/users/')
def get_users():
    return jsonify([
        {
            'user_id': user.user_id, 'encrypted_password': user.encrypted_password, 'option': user.option,
            'request_datetime': user.request_datetime
        } for user in User.query.all()
    ])


@app.route('/users/<user_id>/')
def get_user(user_id):
    print(user_id)
    user = User.query.filter_by(user_id=user_id).first_or_404()
    return {
        'user_id': user.user_id, 'encrypted_password': user.encrypted_password,
        'public_id': user.public_id, 'original_time': user.request_datetime
    }


# Add New User

@app.route('/users/', methods=['POST'])
def create_user():
    data = request.get_json()
    print(request.get_json())
    if not 'user_id' in data or not 'encrypted_password' in data or not 'option' in data:
        return jsonify({
            'error': 'Bad Request',
            'message': 'User Id and Encrypted Data is Missing'
        }), 400
    if len(data['encrypted_password']) < 16:
        return jsonify({
            'error': 'Bad Request',
            'message': 'Length Error in Encrypted Data'
        }), 400
    u = User(
        user_id=data['user_id'],
        encrypted_password=data['encrypted_password'],
        option=data.get('option'),
        request_datetime=data['request_datetime'],
        public_id=str(uuid.uuid4())
    )
    db.session.add(u)
    db.session.commit()
    return {
               'id': u.user_id, 'encrypted_data': u.encrypted_password,
               'public_id': u.public_id, 'original_time': u.request_datetime,
           }, 200


# Verify Password

@app.route('/users/<user_id>', methods=['POST'])
def check_user(user_id):
    data = request.get_json()
    if 'user_id' not in data:
        return {
                   'error': 'Bad Request',
                   'message': 'User_id field needs to be present'
               }, 400
    user = User.query.filter_by(user_id=user_id).first_or_404()
    user.user_id = data['user_id']
    if 'user_id' in data:
        if user.encrypted_password == data['encrypted_password']:

            return jsonify({
                'user_id': user.public_id,
                'encrypted_password': user.encrypted_password,
                'original_time': user.request_datetime
            })
        else:
            return {
                       'error': 'Bad Request',
                       'message': 'Password Do Not Match'
                   }, 400


@app.route('/users/<user_id>/', methods=['DELETE'])
def delete_user(user_id):
    user = User.query.filter_by(user_id=user_id).first_or_404()
    db.session.delete(user)
    db.session.commit()
    return {
        'success': 'Data deleted successfully'
    }


if __name__ == '__main__':
    app.run(debug=True)

我的问题是:当验证密码通过 ESP8266 传递到后端以验证密码时,不工作。但是新用户注册通过ESP8266是有效的。

下面是我在 PyCharm:

中遇到的错误
 * Running on http://192.168.8.100:5000/ (Press CTRL+C to quit)
[2021-09-26 14:57:55,237] ERROR in app: Exception on /users/32 [POST]
Traceback (most recent call last):
  File "C:\Users\Vinod Amarathunga\PycharmProject\Test1\server1\venv\lib\site-packages\flask\app.py", line 2070, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\Vinod Amarathunga\PycharmProject\Test1\server1\venv\lib\site-packages\flask\app.py", line 1515, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\Vinod Amarathunga\PycharmProject\Test1\server1\venv\lib\site-packages\flask\app.py", line 1513, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\Vinod Amarathunga\PycharmProject\Test1\server1\venv\lib\site-packages\flask\app.py", line 1499, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "C:\Users\Vinod Amarathunga\PycharmProject\Test1\server1\app2_Change2.py", line 108, in check_user
    if 'user_id' not in data:
TypeError: argument of type 'NoneType' is not iterable

192.168.8.174 - - [26/Sep/2021 15:52:20] "POST /users/32 HTTP/1.1" 500 -

但是当我从 Postman 发送相同的请求(验证密码)时,它起作用了, 以下是邮递员对验证的回复:

此请求与从 Mockon 测试服务器捕获的 ESP8266 发送的请求相同:

后端预期响应[使用 Postman]

192.168.8.103 - - [26/Sep/2021 15:52:59] "POST /users/32 HTTP/1.1" 400 -

注意: Arduino Nano 将加密数据连同用户输入传递给 ESP,如下所示:

错误TypeError: argument of type 'NoneType' is not iterable意味着你的data = request.get_json()return一个None。 return a None 的原因可以在 falsk.Request.get_json API 文档中找到。它说:

By default this function will return None if the mimetype is not application/json

看看你的 Arduino 代码,你在 http header:

上添加了额外的空格
       http.addHeader("Content - Type", "application / json");

HTTP header 应该是:

Content-Type: application/json