MKS WIFI per Makerbase Robin: protocollo di comunicazione e plugin Cura – 3

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 protocol communication and cura plugin
Makerbase MKS wifi protocol communication and cura plugin

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.

In questo articolo vorrei spiegare come Makerbase ha sviluppato il protocollo di comunicazione tramite messaggi TCP e una piccola introduzione al plugin MKS WiFi per Cura.

Introduzione

Il mio intento è quello di aggiungere le funzionalità di base di Octoprint che ho sulla mia AnetA8.

3d printed Modular System screw esp32-cam on octoprint
3d printed Modular System screw esp32-cam on octoprint

Ma il firmware del modulo WiFi MKS originale utilizza il protocollo UDP/TCP e non può essere utilizzato in una normale interfaccia web senza trucchi o funzionalità sperimentali.

Quindi per prima cosa aggiungerò il protocollo WebSocket, e scelgo la stessa libreria di questo tutorial “WebSocket su Arduino, esp8266 e esp32“.

Come per l’TCP il Web Socket lavora con pacchetti di dati asincroni e wrappo la gestione dei messaggi per utilizzare lo stesso protocollo con WebSocket e TCP contemporaneamente.

Protocollo WiFi MKS

MKS fornisce un documento approssimativo sul suo protocollo, le informazioni di base sono queste.

Carica comandi Gcode

Tipo = 0x01

Data=$Gcode Stringa di comando + ” \r\n

Response = ok\r\n(Ogni comando avrà una response con “ok”, e anche l’aggiunta delle informazioni necessarie.)

Ci sono anche alcuni comandi di personalizzazione mks wifi, che non sono i comandi di Marlin:

M20 xxx:Ottieni l’elenco dei file della stampante e l’MCU host restituirà i file e le cartelle gcode nel percorso del file del livello “xxx” (la stringa di caratteri provvisoria non supera i 1024 byte). Se è una cartella, aggiungerà automaticamente il suffisso .DIR. Viene restituito il seguente formato:    

Begin file list
abc.gcode
123.gcode
temp.DIR

M23 xxx.gcode:seleziona il file gcode

M24:Se la stampa non è iniziata, questo comando serve per avviare la stampa del file, se la stampa è sospesa, questo comando serve per riprendere la stampa

M25 : pausa stampa

M26:interrompere la stampa

M27 : per ottenere l’avanzamento della stampa, l’MCU host risponderà con questo formato: M27 30\r\n(30%)

M991:come M105, ottieni la temperatura. L’MCU host risponderebbe in questo formato:T:%d /%d B:%d /%d T0:%d /%d T1:%d /%d @:0 B@:0\r\n

M992:Ottieni l’ora stampata, l’MCU host risponderà in questo formato:M992 10:30:20\r\n

M994 : ottiene il nome e le dimensioni del file in fase di stampa, l’MCU host sarebbe reply as this format: M994 abc.gcode;size(size indica la dimensione effettiva in byte del file)

M997:Ottieni lo stato corrente della stampante, l’MCU host risponderà in base a diversi stati:

      IDLE: “M997 IDLE\r\n”
      PRINTING: “M997 PRINTING\r\n” 
      PAUSED: “M997 PAUSE\r\n”

M998 : cambia il filesystem su USB o Sdcard

M115 : Ottieni le informazioni sul firmware della scheda madre, l’host MCU risponderà in questo formato:FIRMWARE_NAME:MKS Robin Nano V2.1.0\r\n

Risposta: secondo quanto sopra

Carica il primo frammento del file

Tipo = 0x02

Dati:

SegmentoOffset (bytes)Lunghezza (bytes)Definizione
fileNameLen01La lunghezza del nome del file da trasferire, non più di 255
fileLen14La lunghezza del file da trasferire, non più di 2^32
fileName5fileNameLenLa stringa del nome del file da trasferire

Risposta: no

Carica frammenti di dati di file

Tipo = 0x03

Dati:

Segmento Offset (bytes) Lunghezza  (bytes)Definizione
fragment04Il numero di frammento di file, iniziano da 0. Da 0 ~ 14 
bit indicano il numero di frammento, il bit 15 indica se è l’ultimo frammento.
fileData4Lunghezza dei dati del file frammentoI dati del file non elaborato di questo frammento. 
La lunghezza dovrebbe corrispondere ai dati del file effettivamente letti.

Risposta: no

Carica l’elenco degli hotspot WiFi cercati.

Tipo = 0x04

Dati:

SegmentoOffset (byte)Lunghezza (byte)Definizione
hot spotNum01Number of scanned WiFi hotspot, no more than 20
hot spot Len111Length of 1st hot spot name, no more than 32
hot spot Str12hot spot Len11st Hotspot Name String
hot spot Rssi1hot spot Len1+211st hot spot  signal Strength, It’s a negative number, the higher it is, the higher the intensity.
hot spot Len2hot spot Len1+31Length of 2nd hot spot  name, no more than 32
hot spot Str2hot spot Len1+4hot spot Len22nd Hotspot Name String
hot spot Rssi2hot spot Len1+hot spot Len2+412nd hot spot  signal Strength, It’s a negative number, the higher it is, the higher the intensity.
hot spot LenNhot spot Len1+hot spot Len2+……+hot spot Len(N-1)+2*N-11Length of the “N” hot spot  name, no more than 32
hot spot StrNhot spot Len1+hot spot Len2+……+hot spot Len(N-1)+2*Nhot spot LenNThe “N” Hot spot Name String
hot spot RssiNhot spot Len1+hot spot Len2+……+hot spot LenN+2*N1The “N” hot spot signal Strength, It’s a negative number, the higher it is, the higher the intensity.

Risposta: no

Quando il modulo wifi riceve il comando “scansione hotspot wifi”, eseguirà la scansione e il feedback di questo messaggio.

Host MCU su ESP WIFI

Configura la rete

Tipo = 0x00

Dati:

Segmento Offset (bytes)Lunghezza  (bytes)Definizione
mode010x01:AP
0x02:Client
0x03:AP+Client(not used yet)
wifi_name_len11Length of wifi name
wifi_name2wifi_name_lenWifi name string, no more than 32 characters
wifi_key_len2+wifi_name_len1Length of wifi password
wifi_key3+wifi_name_lenwifi_keyWifi password string, no more than 64 characters

Risposta: no

Messaggio di risposta Gcode

Tipo = 0x02

Dati: la stringa non elaborata a cui rispondere

Risposta: no

Quando Host MCU riceve gcode da esp wifi, risponde “ok \ r \ n” e anche l’elenco dei file … utilizzando questo tipo di messaggio

Informazioni sulle eccezioni

Tipo = 0x03

Dati:

Segmento Offset (bytes)Lunghezza (bytes) Definizione
Exception code010x01: file transfer error
0x02: file transfer ok Just now only these two codes

Risposta: no

Configura le informazioni sul cloud

Tipo = 0x04

Dati:

Segmento Offset (bytes)Lunghezza (bytes)Definizione
cloud_enable010x0a: enable the cloud service
0x05: disable the cloud service
cloud_host_len11Length of Cloud_host
cloud_host2cloud_host_lenUrl of cloud server, no more than 96 characters
cloud_portcloud_host_len+22Port of cloud server

Risposta: no

Svincola l’utente dal modulo wifi

Tipo = 0x05

Dati: nessun dato

Risposta: no

Scansiona gli hotspot Wi-Fi

Tipo = 0x06

Dati: no

Risposta: no

Connessione Wi-Fi

Tipo = 0x09

Dati:

Segmento Offset (bytes)Lunghezza (bytes) Definizione
Operation code010x01: Connect to the wifi hotspot being configured(The WiFi module should determines that it is not connected and takes action)
0x02: Disconnect linking current WiFi hotspot 0X3: Forget the password of current Wifi

Risposta: no

Pacchetti TCP

Il firmare originale usa il pacchetto TCP per la comunicazione, e questo è davvero limitante.

Plugin WiFi MKS per Cura

Innanzitutto non puoi comunicare con un’interfaccia utente Web, quindi questo modulo può essere utilizzato solo con un client desktop, quindi l’implementazione del client che puoi trovare è il plugin per CURA.

Questo plugin è molto utile, su Cura vai al marketplace e seleziona il plugin MKS WiFi

MKS WiFi Cura plugin
MKS WiFi Cura plugin

Dopo l’installazione, vai su Manage printers -> MKS WiFi Plugin --> Add the correct IP.

FlyingBear Ghost 5 MKS WiFi Cura plugin config
FlyingBear Ghost 5 MKS WiFi Cura plugin config

Dopo la configurazione in Cura compare un pulsante da inviare direttamente via WiFi.

Cura FlyingBear Ghost 5 via MKS WiFi
Cura FlyingBear Ghost 5 via MKS WiFi

Esaminare il codice di comunicazione

Il plugin è scritto in python e per inviare usa messaggi TCP, prima di tutto fa la connessione:

        self._socket = QTcpSocket()
        self._socket.connectToHost(self._address, self._port)

usa questo comando:

    def _sendCommand(self, cmd):
        in_cmd = "G28" in cmd or "G0" in cmd
        if self._ischanging and in_cmd:
            return
        if self.isBusy() and "M20" in cmd:
            return
        if self._socket and self._socket.state() == 2 or self._socket.state(
        ) == 3:
            if isinstance(cmd, str):
                self._command_queue.put(cmd + "\r\n")
            elif isinstance(cmd, list):
                for each_command in cmd:
                    self._command_queue.put(each_command + "\r\n")

che mette in coda i messaggi e quando i dati della coda sono pronti chiama questa funzione che invia il messaggio al socket:

    def write_socket_data(self):
        _send_data = ("M105\r\nM997\r\n", "")[self._command_queue.qsize() > 0]
        if self.isBusy():
            _send_data += "M994\r\nM992\r\nM27\r\n"
        while self._command_queue.qsize() > 0:
            _queue_data = self._command_queue.get()
            if "M23" in _queue_data:
                self._socket.writeData(
                    _queue_data.encode(sys.getfilesystemencoding()))
                continue
            if "M24" in _queue_data:
                self._socket.writeData(
                    _queue_data.encode(sys.getfilesystemencoding()))
                continue
            if self.isBusy() and "M20" in _queue_data:
                continue
            _send_data += _queue_data
        Logger.log("d", "_send_data: \r\n%s" % _send_data)
        self._socket.writeData(_send_data.encode(sys.getfilesystemencoding()))
        self._socket.flush()

dal lato modulo WiFi MKS puoi trovare il socket tcp inizializzato sulla porta 8080

WiFiServer tcp(8080);

e nel loop trovi tutta la gestione dei messaggi descritta nel protocollo

			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;
						

						
						//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
						
					
						
						if(transfer_file_flag)
						{
						
							if(!verification_flag)
							{
								break;
							}
							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
												{	
													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();
								}
								
								
							}
						}
					
					}
				}
			}
		/*	if(strlen((const char *)dbgStr) > 0)
			{
				net_print((const uint8_t *) dbgStr, strlen((const char *)dbgStr));
				net_print((const uint8_t *) "ok\r\n", strlen((const char *)"ok\r\n"));
			}*/

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

Lascia un commento

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