MKS WIFI per Makerbase Robin: aggiornamento del firmware e nuova funzionalità Web Socket – 4

Spread the love

Ho acquistato un FlyingBear Ghost 5 con modulo WiFi integrato, ma scopro che non c’è l’interfaccia web. Non so perché non possano aggiungere un’interfaccia utente di base. Poi sono andato a vedere l’interfaccia web delle schede di fascia alta della Makerbase e ho capito che era meglio che non l’avessero sviluppata.

La mia soluzione è stata modificare il firmware per supportare il Web Socket e sviluppare l’interfaccia Web. Questa funzione è compatibile con tutte le schede Makerbase che dispongono di un modulo WiFi MKS.

Makerbase MKS wifi firmware upgrade mischianti
Makerbase MKS wifi firmware upgrade mischianti

Il risultato dell’interfaccia Web di BeePrint è in questa schermata.

MKS WiFi BeePrint interface of my FlyingBear Ghost 5
MKS WiFi BeePrint interface of my FlyingBear Ghost 5

Ho anche deciso di spiegare tutte le fasi dello sviluppo del progetto ed in quest’articolo qui vorrei esporre l’infrastruttura hardware della scheda MKS WiFi che si interfaccia con la Makerbase Robin Nano.

Ora analizzeremo le modifiche che ho apportato al modulo WiFi MKS originale, con l’introduzione del Web Socket e alcune piccole funzionalità per sviluppare l’interfaccia Web ed eseguire il debug.

Aggiornamento WiFi MKS

Ho apportato alcune modifiche al codice originale, le vecchie funzioni sono rimaste le stesse, ma devo aggiungere un server WebSocket e alcuni endpoint per funzionalità aggiuntive.

Ma queste modifiche utilizzano un po’ di memoria aggiuntiva, quindi abbiamo perso l’aggiornamento OTA per il filesystem, quando dobbiamo caricare nuovamente il filesystem, dobbiamo eseguire il downgrade del firmware, caricare il filesystem e caricare di nuovo il firmware.

Codice WebSocket

La sezione che gestisce il messaggio è questa

bool manageMessage(char* readStr, int readSize){
  //transfer file
  #if 0
  if(strstr((const char *)readStr, "M29") != 0)
  {
    if(!verification_flag)
    {
      break;
    }
    if(transfer_state != TRANSFER_IDLE)
    {
      rcv_end_flag = true;
      net_print((const uint8_t *) "ok\n", strlen((const char *)"ok\n"));
      break;
    }
  }
  #endif

  DEBUG_PRINT(F("transfer_file_flag "));
  DEBUG_PRINTLN(transfer_file_flag);

  DEBUG_PRINT(F("verification_flag "));
  DEBUG_PRINTLN(verification_flag);

  DEBUG_PRINT(F("readStr "));
  DEBUG_PRINTLN(readStr);

  DEBUG_PRINT(F("readSize "));
  DEBUG_PRINTLN(readSize);


  if(transfer_file_flag)
  {

    if(!verification_flag)
    {
      return false;
    }
    if(gFileFifo.left() >= readSize)
    {

      gFileFifo.push((char *)readStr, readSize);
      transfer_frags += readSize;


    }

  }
  else
  {


    if(verification_flag)
    {
      int j = 0;
      char cmd_line[100] = {0};
      String gcodeM3 = "";

    #if 0
      if(transfer_state == TRANSFER_BEGIN)
      {
        if(strstr((const char *)readStr, "M110") != 0)
        {

          file_fragment = 0;
          rcv_end_flag = false;
          transfer_file_flag = true;

          if(package_file_first(filePath) == 0)
          {
            /*transfer_state = TRANSFER_READY;
            digitalWrite(EspReqTransferPin, LOW);*/
          }
          else
          {
            transfer_file_flag = false;
            transfer_state = TRANSFER_IDLE;
          }
          net_print((const uint8_t *) "ok\n", strlen((const char *)"ok\n"));
          break;
        }
      }

    #endif

      init_queue(&cmd_queue);

      cmd_index = 0;
      memset(cmd_fifo, 0, sizeof(cmd_fifo));
      while(j < readSize)
      {
        if((readStr[j] == '\r') || (readStr[j] == '\n'))
        {
          if((cmd_index) > 1)
          {
            cmd_fifo[cmd_index] = '\n';
            cmd_index++;


            push_queue(&cmd_queue, cmd_fifo, cmd_index);
          }
          memset(cmd_fifo, 0, sizeof(cmd_fifo));
          cmd_index = 0;
        }
        else if(readStr[j] == '\0')
          break;
        else
        {
          if(cmd_index >= sizeof(cmd_fifo))
          {
            memset(cmd_fifo, 0, sizeof(cmd_fifo));
            cmd_index = 0;
          }
          cmd_fifo[cmd_index] = readStr[j];
          cmd_index++;
        }

        j++;

        do_transfer();
        yield();

      }
      while(pop_queue(&cmd_queue, cmd_line, sizeof(cmd_line)) >= 0)
      {
      #if 0
        point = strstr((const char *)cmd_line, "M28 ");
        if(point != 0)
        {
          if((strstr((const char *)cmd_line, ".g") || strstr((const char *)cmd_line, ".G")))
          {
            int index = 0;
            char *fileName;

            point += 3;
            while(*(point + index) == ' ')
              index++;

            memcpy((char *)filePath, (const char *)(point + index), readSize - (int)(point + index - (int)(&cmd_line[0])));

            gFileFifo.reset();

            transfer_frags = 0;


            transfer_state = TRANSFER_BEGIN;

            sprintf((char *)dbgStr, "Writing to file:%s\n", (char *)filePath);

            net_print((const uint8_t *)dbgStr, strlen((const char *)dbgStr));
          }

        }
        else
      #endif
        {
          /*transfer gcode*/
          //Serial.write(cmd_line, readNum);
          if((strchr((const char *)cmd_line, 'G') != 0)
            || (strchr((const char *)cmd_line, 'M') != 0)
            || (strchr((const char *)cmd_line, 'T') != 0))
          {
            if(strchr((const char *)cmd_line, '\n') != 0 )
            {
              String gcode((const char *)cmd_line);

            //  sprintf((char *)dbgStr, "read %d: %s\n", readNum, cmd_line);

          //    net_print((const uint8_t *)dbgStr, strlen((char *)dbgStr));

              if(gcode.startsWith("M998") && (M3_TYPE == ROBIN))
              {
                net_print((const uint8_t *) "ok\r\n", strlen((const char *)"ok\r\n"));
              }
              else if(gcode.startsWith("M997"))
              {
                if(gPrinterInf.print_state == PRINTER_IDLE)
                //  net_print((const uint8_t *) "M997 IDLE\r\n", strlen((const char *)"M997 IDLE\r\n"));
                  strcpy((char *)dbgStr, "M997 IDLE\r\n");
                else if(gPrinterInf.print_state == PRINTER_PRINTING)
                //  net_print((const uint8_t *) "M997 PRINTING\r\n", strlen((const char *)"M997 PRINTING\r\n"));
                  strcpy((char *)dbgStr, "M997 PRINTING\r\n");
                else if(gPrinterInf.print_state == PRINTER_PAUSE)
                  //net_print((const uint8_t *) "M997 PAUSE\r\n", strlen((const char *)"M997 PAUSE\r\n"));
                  strcpy((char *)dbgStr, "M997 PAUSE\r\n");
                else
                  strcpy((char *)dbgStr, "M997 NOT CONNECTED\r\n");
              //  net_print((const uint8_t *) "ok\r\n", strlen((const char *)"ok\r\n"));
              }
              else if(gcode.startsWith("M27"))
              {
                memset(dbgStr, 0, sizeof(dbgStr));
                sprintf((char *)dbgStr, "M27 %d\r\n", gPrinterInf.print_file_inf.print_rate);
              //  net_print((const uint8_t *) dbgStr, strlen((const char *)dbgStr));
              //  net_print((const uint8_t *) "ok\r\n", strlen((const char *)"ok\r\n"));
              }
              else if(gcode.startsWith("M992"))
              {
                memset(dbgStr, 0, sizeof(dbgStr));
                sprintf((char *)dbgStr, "M992 %02d:%02d:%02d\r\n",
                  gPrinterInf.print_file_inf.print_hours, gPrinterInf.print_file_inf.print_mins, gPrinterInf.print_file_inf.print_seconds);
              //  net_print((const uint8_t *) dbgStr, strlen((const char *)dbgStr));
              //  net_print((const uint8_t *) "ok\r\n", strlen((const char *)"ok\r\n"));
              }
              else if(gcode.startsWith("M994"))
              {
                memset(dbgStr, 0, sizeof(dbgStr));
                sprintf((char *)dbgStr, "M994 %s;%d\r\n",
                  gPrinterInf.print_file_inf.file_name.c_str(), gPrinterInf.print_file_inf.file_size);
              //  net_print((const uint8_t *) dbgStr, strlen((const char *)dbgStr));
              //  net_print((const uint8_t *) "ok\r\n", strlen((const char *)"ok\r\n"));
              }
              else  if(gcode.startsWith("M115"))
              {
                memset(dbgStr, 0, sizeof(dbgStr));
                if(M3_TYPE == ROBIN)
                  strcpy((char *)dbgStr, "FIRMWARE_NAME:Robin\r\n");
                else if(M3_TYPE == TFT28)
                  strcpy((char *)dbgStr, "FIRMWARE_NAME:TFT28/32\r\n");
                else if(M3_TYPE == TFT24)
                  strcpy((char *)dbgStr, "FIRMWARE_NAME:TFT24\r\n");


              //  net_print((const uint8_t *) dbgStr, strlen((const char *)dbgStr));
              //  net_print((const uint8_t *) "ok\r\n", strlen((const char *)"ok\r\n"));
              }
              /*else if(gcode.startsWith("M105"))
              {
                memset(dbgStr, 0, sizeof(dbgStr));
                sprintf((char *)dbgStr, "T:%d /%d B:%d /%d T0:%d /%d T1:%d /%d @:0 B@:0\r\n",
                  (int)gPrinterInf.curSprayerTemp[0], (int)gPrinterInf.desireSprayerTemp[0], (int)gPrinterInf.curBedTemp, (int)gPrinterInf.desireBedTemp,
                  (int)gPrinterInf.curSprayerTemp[0], (int)gPrinterInf.desireSprayerTemp[0], (int)gPrinterInf.curSprayerTemp[1], (int)gPrinterInf.desireSprayerTemp[1]);

              //  net_print((const uint8_t *) dbgStr, strlen((const char *)dbgStr));
              //  net_print((const uint8_t *) "ok\r\n", strlen((const char *)"ok\r\n"));
              }*/
              else
              {
                DEBUG_PRINT(F("GCODE COMMANDS "));
                DEBUG_PRINTLN(gcode);

                if(gPrinterInf.print_state == PRINTER_IDLE)
                {
                  if(gcode.startsWith("M23") || gcode.startsWith("M24"))
                   {
                    gPrinterInf.print_state = PRINTER_PRINTING;
                    gPrinterInf.print_file_inf.file_name = "";
                    gPrinterInf.print_file_inf.file_size = 0;
                    gPrinterInf.print_file_inf.print_rate = 0;
                    gPrinterInf.print_file_inf.print_hours = 0;
                    gPrinterInf.print_file_inf.print_mins = 0;
                    gPrinterInf.print_file_inf.print_seconds = 0;

                    printFinishFlag = false;
                   }
                }
                gcodeM3.concat(gcode);

              }

            }
          }
        }
        if(strlen((const char *)dbgStr) > 0)
        {
          net_print((const uint8_t *) "ok\r\n", strlen((const char *)"ok\r\n"));

          net_print((const uint8_t *) dbgStr, strlen((const char *)dbgStr));
          memset(dbgStr, 0, sizeof(dbgStr));

        }



        do_transfer();
        yield();



      }

      if(gcodeM3.length() > 2)
      {
        package_gcode(gcodeM3, true);
        //Serial.write(uart_send_package, sizeof(uart_send_package));
        /*transfer_state = TRANSFER_READY;
        digitalWrite(EspReqTransferPin, LOW);*/
        do_transfer();

        socket_busy_stamp = millis();
      }


    }
  }
  return true;

}

Ho isolato il codice in modo da poter chiamare questa funzione nella zona di gestione precedente, qui la gestione dei messaggi TCP.

    {
      if (tcp.hasClient()){
        for(i = 0; i < MAX_SRV_CLIENTS; i++){
          //find free/disconnected spot
        #if 0
          if (!serverClients[i] || !serverClients[i].connected()){
          if(serverClients[i]) serverClients[i].stop();
          serverClients[i] = tcp.available();
          continue;
          }
          #else
          if(serverClients[i].connected())
          {
          serverClients[i].stop();
          }
          #endif
          serverClients[i] = tcp.available();
        }
        if (tcp.hasClient())
        {
          //no free/disconnected spot so reject
          WiFiClient serverClient = tcp.available();
          serverClient.stop();

        }
      }
      memset(dbgStr, 0, sizeof(dbgStr));
      for(i = 0; i < MAX_SRV_CLIENTS; i++)
      {
        if (serverClients[i] && serverClients[i].connected())
        {
          uint32_t readNum = serverClients[i].available();

          if(readNum > FILE_FIFO_SIZE)
          {
          //  net_print((const uint8_t *) "readNum > FILE_FIFO_SIZE\n");
            serverClients[i].flush();
          //  Serial.println("flush");
            continue;
          }


          if(readNum > 0)
          {
            char * point;

              uint8_t readStr[readNum + 1] ;

            uint32_t readSize;

            readSize = serverClients[i].read(readStr, readNum);

            readStr[readSize] = 0;

            if (!manageMessage((char*)readStr, readSize)) break;
          }
        }
      }

e qui la gestione nel listener WS.

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
  DynamicJsonDocument docwsT(512);
  DeserializationError error;

    switch(type) {
        case WStype_DISCONNECTED:
      webSocket.sendTXT(num, "{\"connection\": false}");

            DEBUG_PRINT(F(" Disconnected "));
            DEBUG_PRINTLN(num, DEC);

            isConnectedWebSocket = false;
//            isConnectedWebSocketAck = false;
//            DEBUG_PRINTF_AI(F("[%u] Disconnected!\n"), num);
            break;
        case WStype_CONNECTED:
            {
                IPAddress ip = webSocket.remoteIP(num);
//                DEBUG_PRINTF_AI(F("[%u] Connected from %d.%d.%d.%d url: %s\n"), num, ip[0], ip[1], ip[2], ip[3], payload);

                DEBUG_PRINT(num);
                DEBUG_PRINT(F("Connected from: "));
                DEBUG_PRINT(ip.toString());
                DEBUG_PRINT(F(" "));
                DEBUG_PRINTLN((char*)payload);

        // send message to client
//        webSocket.sendTXT(num, "{\"type\":\"connection\", \"connection\": true, \"simpleMessage\": "+String(readSingleMessage?"true":"false")+"}");

        isConnectedWebSocket = true;

        memset(dbgStr, 0, sizeof(dbgStr));
        sprintf((char *)dbgStr, "FWV:%s\r\n", firmwareVersion);
        net_print((const uint8_t*)dbgStr, strlen((const char *)dbgStr));

            }
            break;
        case WStype_TEXT:
          DEBUG_PRINT("NUM -> ");DEBUG_PRINT(num);
          DEBUG_PRINT("payload -> ");DEBUG_PRINTLN((char*)payload);

          manageMessage((char*)payload, length);
//          manageMessage(String((char*)payload));
//          error = deserializeJson(docwsT, (char*)payload);
//
//          if (error) {
//            // if the file didn't open, print an error:
//            DEBUG_PRINT(F("Error parsing JSON "));
//            webSocket.broadcastTXT("Error on WS");
//          }else{
//            JsonObject postObjws = docwsT.as<JsonObject>();
//
//            bool startReceiveDevMsg = postObjws[F("startReceiveDevMsg")];
//            if (startReceiveDevMsg==true){
////          readSingleMessage = postObjws[F("singleMessage")];
//          isConnectedWebSocketAck  = true;
//          DEBUG_PRINT(F("Start listening messages SM -> "));
//          DEBUG_PRINTLN(isConnectedWebSocketAck);
//
//              webSocket.broadcastTXT("{\"type\": \"device_msg\", \"receiving\": true}");
//            }else if (startReceiveDevMsg==false){
//          isConnectedWebSocketAck  = false;
//          DEBUG_PRINT(F("Start listening messages SM -> "));
//          DEBUG_PRINTLN(isConnectedWebSocketAck);
//
//          webSocket.broadcastTXT("{\"type\": \"device_msg\", \"receiving\": false}");
//            }
//
//        bool singleMessage = postObjws[F("singleMessage")];
//        DEBUG_PRINT(F("Single message -> "));
//        DEBUG_PRINTLN(singleMessage);
//            if (singleMessage){
//          readSingleMessage = singleMessage;
//            }
//          }

//          DEBUG_PRINTF_AI(F("[%u] get Text: %s\n"), num, payload);

            // send message to client
            // webSocket.sendTXT(num, "message here");

            // send data to all connected clients
            // webSocket.broadcastTXT("message here");
            break;
        case WStype_BIN:
////          DEBUG_PRINTF_AI(F("[%u] get binary length: %u\n"), num, length);
//            hexdump(payload, length);
//
//            // send message to client
//            // webSocket.sendBIN(num, payload, length);
//            break;
        case WStype_ERROR:
        case WStype_FRAGMENT_TEXT_START:
        case WStype_FRAGMENT_BIN_START:
        case WStype_FRAGMENT:
        case WStype_FRAGMENT_FIN:
        case WStype_PING:
        case WStype_PONG:

//          DEBUG_PRINTF_AI(F("[%u] get binary length: %u\n"), num, length);
          DEBUG_PRINT(F("WS : "))
          DEBUG_PRINT(type)
          DEBUG_PRINT(F(" - "))
      DEBUG_PRINTLN((char*)payload);

            // send message to client
            // webSocket.sendBIN(num, payload, length);
            break;
    }

}

Test WebSocket

E’ possibile connettersi con il semplice client websocket che puoi trovare in questo articolo.

WebSocket client connected to MKS WiFi
WebSocket client connected to MKS WiFi

Quando la connessione è iniziata, ricevi le informazioni sulla temperatura e sul WiFi, puoi provare ad aggiungere un comando come M20 1:, fai attenzione al ritorno alla fine (usa shift + invio), e riceverai una serie di messaggi come

End point REST

Per impostazione predefinita, viene esposto solo un end-point, il caricamento del file.

Per sviluppare un’interfaccia Web prima abiliterò le richieste CORS, per maggiori informazioni fare riferimento a questo tutorial “Server REST con esp8266 e esp32: richieste CORS, OPTION e POST“.

Dove sendCrossOriginHeader è gestito dalla define ENABLE_CORS.

void setCrossOrigin(){
#ifdef ENABLE_CORS
    server.sendHeader(F("Access-Control-Allow-Origin"), F("*"));
    server.sendHeader(F("Access-Control-Max-Age"), F("600"));
    server.sendHeader(F("Access-Control-Allow-Methods"), F("PUT,POST,GET,OPTIONS"));
    server.sendHeader(F("Access-Control-Allow-Headers"), F("*"));
#endif
};

void sendCrossOriginHeader(){
    DEBUG_PRINTLN(F("sendCORSHeader"));
    server.sendHeader(F("access-control-allow-credentials"), F("false"));
    setCrossOrigin();
    server.send(204);
}

Come puoi vedere, non è un multipart form data, ma vuole un flusso del file in formato raw.

void handleUpload()
{
  DEBUG_PRINTLN(F("Upload request!"));

  setCrossOrigin();

  uint32_t now;
  uint8_t readBuf[1024];

  uint32_t postLength = server.getPostLength();
  String uri = server.uri();


  if(uri != NULL)
  {
    if((transfer_file_flag) || (transfer_state != TRANSFER_IDLE) || (gPrinterInf.print_state != PRINTER_IDLE))
  {
    server.send(409, FPSTR(STR_MIME_TEXT_PLAIN), FPSTR("409 Conflict"));
    return;
  }

    if(server.hasArg((const char *) "X-Filename"))
    {
      if((transfer_file_flag) || (transfer_state != TRANSFER_IDLE))
      {
        server.send(500, FPSTR(STR_MIME_APPLICATION_JSON), FPSTR(STR_JSON_ERR_500_IS_BUSY));
      return;
      }

    file_fragment = 0;
    rcv_end_flag = false;
    transfer_file_flag = true;
    gFileFifo.reset();
    upload_error = false;
    upload_success = false;

    String FileName = server.arg((const char *) "X-Filename");
    //package_gcode(FileName, true);
    //transfer_state = TRANSFER_READY;
    //digitalWrite(EspReqTransferPin, LOW);
    //String fileNameAfterDecode = urlDecode(FileName);
    //package_gcode(fileNameAfterDecode, true);
    //transfer_state = TRANSFER_READY;
    //digitalWrite(EspReqTransferPin, LOW);
    if(package_file_first((char *)FileName.c_str(), (int)postLength) == 0)
    {
      /*transfer_state = TRANSFER_READY;
      digitalWrite(EspReqTransferPin, LOW);*/
    }
    else
    {
      transfer_file_flag = false;
    }
    /*wait m3 reply for first frame*/
    int wait_tick = 0;
    while(1)
    {
      do_transfer();

      delay(100);

      wait_tick++;

      if(wait_tick > 20) // 2s
      {
        if(digitalRead(McuTfrReadyPin) == HIGH) // STM32 READY SIGNAL
        {
          upload_error = true;
        //  Serial.println("upload_error");
        }
        else
        {
      //    Serial.println("upload_sucess");
        }
        break;
      }

      int len_get = get_printer_reply();
      if(len_get > 0)
      {
        esp_data_parser((char *)uart_rcv_package, len_get);

        uart_rcv_index = 0;
      }

      if(upload_error)
      {
        break;
      }
    }

    if(!upload_error)
    {

       now = millis();
      do
      {
        do_transfer();

        int len = get_printer_reply();

        if(len > 0)
        {
        //  Serial.println("rcv");
          esp_data_parser((char *)uart_rcv_package, len);

          uart_rcv_index = 0;
        }

        if(upload_error || upload_success)
        {
          break;
        }

        if (postLength != 0)
        {
          uint32_t len = gFileFifo.left();



          if (len > postLength)
          {
             len = postLength;
          }
          if(len > sizeof(readBuf))
          {
             len = sizeof(readBuf);
          }
          if(len > 0)
          {

            size_t len2 = server.readPostdata(server.client(), readBuf, len);

            if (len2 > 0)
            {
              postLength -= len2;

              gFileFifo.push((char *)readBuf, len2);


              now = millis();
            }
          }

        }
        else
        {
          rcv_end_flag = true;
          break;
        }
        yield();


      }while (millis() - now < 10000);
    }

    if(upload_success || rcv_end_flag )
    {
      server.send(200, FPSTR(STR_MIME_APPLICATION_JSON), FPSTR(STR_JSON_ERR_0));
    }
    //if((millis() - now >= 10000) || upload_error)
    else
    {

      //Serial.print("len:");
      //Serial.println(gFileFifo.left() );

      //Serial.print("postLength:");
      //Serial.println(postLength );
      if(Serial.baudRate() != 115200)
      {
        Serial.flush();
        Serial.begin(115200);
        // Serial.begin(4500000);
      }

      //Serial.println("timeout" );


      transfer_file_flag = false;
      rcv_end_flag = false;
      transfer_state = TRANSFER_IDLE;
      server.send(500, FPSTR(STR_MIME_APPLICATION_JSON), FPSTR(STR_JSON_ERR_500_NO_DATA_RECEIVED));
    }

    }
  else
  {

    server.send(500, FPSTR(STR_MIME_APPLICATION_JSON), FPSTR(STR_JSON_ERR_500_NO_FILENAME_PROVIDED));
    return;
  }
  }
}

Aggiungo anche un nuovo end point in GET e POST per gestire la configurazione di BeePrint:

  server.on("/configApp", HTTP_OPTIONS, sendCrossOriginHeader);
  server.on("/configApp", HTTP_POST, postConfigFile, NULL);
  server.on("/configApp", HTTP_GET, getConfigFile, NULL);

Memorizza un file su SPIFFS che può essere utilizzato per preservare la configurazione dell’interfaccia utente di BeeTree.

void postConfigFile() {
  DEBUG_PRINTLN(F("postConfigFile"));

  setCrossOrigin();


  uint32_t postLength = server.getPostLength();

  if(postLength > maxPostSize || (postLength <= 0))
  {
    server.send(400, FPSTR(STR_MIME_TEXT_PLAIN), FPSTR("Bad Request, wrong post size"));
    return;
  }



  uint8_t readBuf[postLength];
  DynamicJsonDocument doc(postLength);

  if((postLength > sizeof(readBuf)) || (postLength <= 0))
  {
    server.send(400, FPSTR(STR_MIME_TEXT_PLAIN), FPSTR("Bad Request"));
    return;
  }

  server.readPostdata(server.client(), readBuf, postLength);
//    serializeJson(doc, buf);
//    JsonObject& root = jsonBuffer.parseObject((char *)readBuf);
//
//    root = jsonBuffer.parseObject((char *)readBuf);

  DeserializationError error = deserializeJson(doc, readBuf);


    String postBody = server.arg("plain");
    DEBUG_PRINTLN(postBody);

//  DynamicJsonDocument doc(CONFIG_FILE_HEAP);
//  DeserializationError error = deserializeJson(doc, postBody);
  if (error) {
    // if the file didn't open, print an error:
    DEBUG_PRINT(F("Error parsing JSON "));
    DEBUG_PRINTLN(error.c_str());

    String msg = error.c_str();

    server.send(400, F("text/html"), "Error in parsin json body! <br>"+msg);

  }else{
    JsonObject postObj = doc.as<JsonObject>();

    DEBUG_PRINT(F("HTTP Method: "));
    DEBUG_PRINTLN(server.method());

        if (server.method() == HTTP_POST) {
            if (( postObj.containsKey("server") ||
                postObj.containsKey("preferences") ||
          postObj.containsKey("serverSMTP") ||
          postObj.containsKey("camera")
          )) {

              DEBUG_PRINT(F("Open config file..."));
              File configFile = SPIFFS.open(F("/mc/config.txt"), "w");
              if (!configFile) {
                  DEBUG_PRINTLN(F("fail."));
                  server.send(304, F("text/html"), F("Fail to store data, can't open file!"));
              }else{
                DEBUG_PRINTLN(F("done."));
                serializeJson(doc, configFile);
//                httpRestServer.sendHeader("Content-Length", String(postBody.length()));
                server.send(201, F("application/json"), postBody);

//                DEBUG_PRINTLN(F("Sent reset page"));
//            delay(5000);
//            ESP.restart();
//            delay(2000);
              }
            }
            else {
              server.send(204, F("text/html"), F("No data found, or incorrect!"));
            }
        }
    }
}

Caricamento di prova

Il processo di caricamento può essere gestito dal un comando curl come questo:

curl -i -X POST "http://192.168.1.164/upload?X-Filename=prova.stl" -H "Content-Type: text/xml" --data-binary "@FBG5_prova.gcode"

E qui la risposta:

*   Trying 192.168.1.164...
* TCP_NODELAY set
* Connected to 192.168.1.164 (192.168.1.164) port 80 (#0)
> POST /upload?X-Filename=prova.stl×tamp=1635695248382 HTTP/1.1
> Host: 192.168.1.164
> User-Agent: curl/7.55.1
> Accept: */*
> Content-Type: text/xml
> Content-Length: 299092
> Expect: 100-continue
>
* Done waiting for 100-continue
* We are completely uploaded and fine
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Cache-Control: no-cache, no-store, must-revalidate
< Pragma: no-cache
< Expires: 0
< Content-Type: application/json
< Content-Length: 9
< Connection: close
<
{"err":0}* Closing connection 0

Compilare la versione mischianti del firmware MKS WiFi

Ora dobbiamo compilare la versione Mischianti MKS WiFi del firmware, puoi caricarla direttamente sulla scheda WiFi MKS o sul dispositivo NodeMCU come descritto nell’articolo precedente.

MKS WiFi esp12 programming module connection on BreadBoard
MKS WiFi esp12 programming module connection on BreadBoard

Prima di tutto devi usare la versione 2.7.4 del core esp8266, perché continuo a usare il filesystem SPIFFS come la versione originale.

Quindi devi impostare questo valore sul tuo IDE Arduino:

  • Consiglio :  NodeMCU 1.0 (ESP-12E Module)
  • Led integrato :  2
  • Velocità di caricamento :  921600  (115200 è più sicuro)
  • Frequenza CPU : “160 MHz” (80 MHz è più sicuro)
  • Dimensione flash :  4 MB
  • Spiffs : 3Mb

E la mia ultima build usa questo set di librerie

  • ArduinoJson@6.18.4
  • ESP8266WiFi@1.0
  • ESP8266mDNS@1.2
  • EEPROM@1.0
  • ESP8266HTTPClient@1.2
  • WebSocket@2.3.5
Test env for MKS WiFi with NodeMCU esp8266 on Robin Nano
Test env for MKS WiFi with NodeMCU esp8266 on Robin Nano

Attiva CORS e DEBUG

Per semplificare lo sviluppo del firmware aggiungo 2 define:

#define ENABLE_CORS

che consentono di utilizzare un’interfaccia web esterna (non installata sul dispositivo), puoi ottenere maggiori informazioni leggendo il tutorial REST .

#define MKSWIFI_DEBUG

Che abilitano alcune informazioni di debug su una seriale specifica

// Define where debug output will be printed.
#define DEBUG_PRINTER Serial1

con questa configurazione puoi leggere il messaggio di debug sul pin D4, e puoi ottenere maggiori informazioni su questo articolo “WeMos D1 mini (esp8266): eseguire un debug sulla seriale secondaria“.

Generare il file binario FileSystem

Per far funzionare il tutto è necessario generare anche il file binario del filesystem.

Aggiungere un output dettagliato all’IDE di Arduino

Per comprendere meglio tutto il processo, abiliteremo l’output dettagliato sul nostro IDE Arduino. Puoi trovare queste opzioni su File -> Preferences e controllare i controlli Show verbose output.

Arduino IDE enable show verbose ouput
Arduino IDE enable show verbose ouput

Ora possiamo prendere e riutilizzare i comandi della console.

Prerequisito

Per il filesystem possiamo fare lo stesso, ma prima è necessario leggere la seguente guida WeMos D1 mini (esp8266), SPIFFS Filesystem integrato .

Ora aggiungeremo la directory data alla cartella degli sketch (con alcuni file) e utilizzeremo il plug-in per caricare.

ESP8266 Sketch Data Upload Menu

Nella console troviamo il comando giusto per generare questo file.

Ora possiamo vedere molte informazioni, inclusa la posizione del file e l’indirizzo.

Utilizzare l’interfaccia Web o l’SD per caricare

Ora con l’interfaccia Web puoi seguire questi passaggi per caricare

  • Prendi MksWifi_WebView.bin e carica la Web view;
  • Prendi MksWifi.bin e carica il firmware.

È importante seguire questi passaggi perché la mia versione del firmware richiede più risorse e se si carica prima il firmware non è possibile caricare il filesystem.

MKS WiFi firmware update page
MKS WiFi firmware update page

Quindi in futuro se vuoi caricare una nuova versione del filesystem devi caricare la versione precedente del firmware e ripetere il processo.

Puoi scaricare il firmware precompilato della versione Mischianti:

  • MksWifi.bin qui ;
  • MksWifi_WebView.bin qui .

Puoi anche scaricare il firmware originale precompilato:

  • MksWifi.bin qui;

Se hai un firmware come Flying Bear Ghost 5 puoi mettere questi file all’interno della SD, inserirla nella stampante, riavviarlo, attendere che il primo processo sia stato completato quindi riavviare di nuovo per caricare il file rimanente.

Grazie

  1. MKS WIFI per Makerbase Robin: schede e come collegare un esp12 o NodeMCU
  2. MKS WIFI per Makerbase Robin: PCB e come compilare e caricare il firmware
  3. MKS WIFI per Makerbase Robin: protocollo di comunicazione e plugin Cura
  4. MKS WIFI per Makerbase Robin: aggiornamento del firmware e nuova funzionalità Web Socket
  5. MKS WIFI per Makerbase Robin: interfaccia web BeePrint con fotocamera su Flying Bear Ghost
Tutto il codice è rilasciato sotto licenza Creative Commons License

Cummunity italiana


Spread the love

10 Risposte

  1. Massimo ha detto:

    Ciao ho una robin nano v3.1 ed ho messo il tuo firmware sulla esp. funziona! ti chiedo se hai esperienza di funzionamento impostando il display a COLOR_UI.

  2. CARLO ha detto:

    Davvero impressionante il lavoro fatto : non solo un software veramente funzionale, ma anche bello da usare!
    Volevo fare una domanda: potrebbe funzionare su una Ghost 4s? Ciao e ancora complimenti (che rivolgo anche alla tua realizzazione della chiusura…ho già acquistato il PETG rosso!!!)
    Carlo

    • Renzo Mischianti ha detto:

      Ciao Carlo,
      troppo buono. Si funziona, mi sono arrivati feedback anche da chi ha la Ghost 4s.
      Io per la chiusura ho usato il PLA. Soprattutto per i supporti, che sono necessari per creare le scanalature, il PETG può essere noioso.

      Dai un occhio anche a questo topic, credo possa interessare.
      FlyingBear: Copertura superiore problema dimensione pezzi

      Ciao Renzo

      • CARLO ha detto:

        L’enclosure la stamperò con una stampante grande formato, ma ti ringrazio per il suggerimento circa il materiale… il fatto è che quando ho incominciato a stampare, per motivi tecnici ho usato sempre il PETG e ora ho più confidenza con questo materiale che con il PLA (anche se in effetti con certi pezzi il PETG è un pò antipatico e -ora che mi hai messo la pulce nell’orecchio- devo anche ammettere che con il PLA la rifinitura è più piacevole).
        Inoltre volevo dirti che anche con la Ghost 4s il software va che è una meraviglia, anzi meglio di quello che credevo: ho finalmente potuto impostare l’IP statico (prima ogni volta che l’accendevo dovevo ricordarmi di cambiare il settaggio in Cura), cosa che ho sempre considerato una nota stonata in questa stampante….
        Sono convinto che nel 99,9% dei casi possa sostituire, grazie ad una maggior integrazione con il display della Ghost, l’amato Octoprint (non me ne vogliano i suoi estimatori), senza dover usare hardware aggiuntivo è perdere qualche oretta nell’installazione.
        Lo so, mi ripeto, ma davvero complimenti!!!!

  3. SirDuncan ha detto:

    Ciao, ho seguito la tua procedura, e ho provato a caricare i file dalla SD. va tutto liscio, ma poi dopo il secondo riavvio non si connette più alla WiFi, da sempre failed.

    Se rimetto i file originali si connette subito.

    Qualche idea? grazie mille

  4. Gabriele ha detto:

    Ciao ho seguito la procedura su una ghost 5 e funziona tutto!!! MI TI COOO!!
    Volevo chiederti, è possibile controllare la stampante da remoto (da un altra rete), lasciandola collegata la wifi di casa?

    Ti ringrazio

    Sei un grande!!

    Gabriele

    • Renzo Mischianti ha detto:

      Ciao Gabriele,
      sicuramente si, ma devi aprire la porta del websocket e quella web, perciò l’8081 e l’80.
      Perciò sul tuo router port forwarding delle 2 porte verso l’ip della stampante.
      E se metti un dynamic dns gli puoi assegnare un nome.
      Ma senza autenticazione anche gli altri potrebbero accedervi.
      Ciao Renzo

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *