Сервер API REST Flask с клиентом ESP8266

#python-3.x #rest #flask #pycharm #esp8266

Вопрос:

Я разработал систему проверки пароля с использованием Arduino Nano ESP8266 (интерфейс) и серверной части с сервером API Flask REST.

Ниже приведен мой код 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 *)amp;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
      } 
 

Verify password code part:

 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
      }
 

Below is my Flask/Python API server backend code:

 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:UsersVinod AmarathungaPycharmProjectTest1server1venvlibsite-packagesflaskapp.py", line 2070, in wsgi_app
    response = self.full_dispatch_request()
  File "C:UsersVinod AmarathungaPycharmProjectTest1server1venvlibsite-packagesflaskapp.py", line 1515, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:UsersVinod AmarathungaPycharmProjectTest1server1venvlibsite-packagesflaskapp.py", line 1513, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:UsersVinod AmarathungaPycharmProjectTest1server1venvlibsite-packagesflaskapp.py", line 1499, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "C:UsersVinod AmarathungaPycharmProjectTest1server1app2_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 он работает,
ниже приведен ответ почтальона на проверку:

введите описание изображения здесь

Этот запрос был таким же, отправленным с ESP8266, который был захвачен с тестового сервера Mockon:

введите описание изображения здесь

Ожидаемый ответ бэкенда [Работа с почтальоном]

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

Примечание: Зашифрованные данные вместе с пользовательскими вводами передаются в ESP с помощью Arduino Nano, как показано ниже:

введите описание изображения здесь

Ответ №1:

Ошибка TypeError: argument of type 'NoneType' is not iterable означает, что ваш data = request.get_json() возврат a None . Причину, по которой он возвращает a None , можно найти в документации API falsk.Request.get_json. Там было написано:

По умолчанию эта функция не вернет ничего, если тип mimetype не является приложением/json

Взгляните на свой код Arduino, вы добавляете дополнительные пробелы в http-заголовке:

        http.addHeader("Content - Type", "application / json");
 

Заголовок HTTP должен быть:

 Content-Type: application/json