MKS WIFI for Makerbase Robin: communication protocol and Cura plugin – 3

Spread the love

I bought a FlyingBear Ghost 5 with integrated WiFi module, but I discover that there is no web interface. I don’t know why they can’t add a basic user interface. Then I went to see the web interface the high-end Makerbase cards and realized that it was better they didn’t develop it.

My solution was to modify the firmware to support the Web Socket and develop the Web interface. This feature is compatible with all Makerbase cards that have an MKS WiFi module.

Makerbase MKS wifi protocol communication and cura plugin
Makerbase MKS wifi protocol communication and cura plugin

The result of BeePrint Web interface is in this screenshot.

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

I also decided to explain all the development phases of the project and in this article here I would like to expose the hardware infrastructure of the MKS WiFi card that interfaces with the Makerbase Robin Nano.

In this article I explain how the Makerbase develop the communication protocol via TCP messages and a little introduction to MKS WiFi plugin for Cura.

Introduction

My intent is to add the basic features of Octoprint that I have on my AnetA8.

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

But the firmware of the original MKS WiFi module use UDP/TCP protocol and can’t be used in a normal web interface without trick or experimental features.

So for fist I’m going to add WebSocket protocol, and I choiche the same library of this tutorial “WebSocket on Arduino, esp8266 and esp32“.

As for TCP the Web Socket work with asynchronous data packet, and I wrap the message management to use the same protocol with WebSocket and TCP at the same time.

MKS WiFi protocol

MKS give a rough document about his protocol, the basic information are this.

Upload Gcode commands

Type = 0x01

Data=$Gcode Comand String + ”\r\n

Reply = ok\r\n (Every command will be replied with “ok”, and also addition necessary information.)

There also some customize commands of mks wifi, which maybe not the command commands with Marlin:

M20 xxx:Get the printer file list, and the Host MCU will return the gcode files and folders under the file path of the “xxx” layer (the tentative character string does not exceed 1024 bytes). If it is a folder, it will automatically add the .DIR suffix. The following format is returned:    

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

M23 xxx.gcode:select the gcode file

M24:If printing has not started, this command is to start printing the file, if printing is suspended, this command is to resume printing

M25: pause printing

M26:stop printing

M27:To get the printing progress, Host MCU would reply with this format: M27 30\r\n (30%)

M991:like M105, get the temperature. Host MCU would reply as this format:T:%d /%d B:%d /%d T0:%d /%d T1:%d /%d @:0 B@:0\r\n

M992:Get the time has print, Host MCU would reply as this format:M992 10:30:20\r\n

M994: Get the name and size of the file being printing, Host MCU would reply as this format: M994 abc.gcode;size(size means the actually size in bytes of file)

M997:Get the current printer state, Host MCU would reply according different states:

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

M998: switch the filesystem to USB or Sdcard

M115: Get the firmware information of mainboard, Host MCU would reply in this format: FIRMWARE_NAME:MKS Robin Nano V2.1.0\r\n

Reply: according to above

Upload file first fragment

Type = 0x02

Data:

SegmentOffset (bytes)Length (bytes)Definition
fileNameLen01The length of filename to be transferred, no more than 255 
fileLen14 The length of file to be transferred, no more than 2^32
fileName5fileNameLen The name string of file to be transferred

Reply: no

Upload file data fragments

Type = 0x03

Data:

SegmentOffset (bytes)Length (bytes)Definition
fragment04The file fragment number, begin from 0.  0~14th bits indicate the fragment number, the 15th bit indicates whether it is the last fragment.
fileData4Length of fragment file dataThe raw file data of this fragment. The length should be the file data actually read out.

Reply: no

Upload list of searched WiFi hotspot.

Type = 0x04

Data:

SegmentOffset (bytes)Length (bytes)Definition
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.

Reply: no

When the wifi module receive the “scan wifi hotspots” command, it will make the scanning and feekback this message.

Host MCU to ESP WIFI

Configure the network

Type = 0x00

Data:

SegmentOffset (bytes)Length (bytes)Definition
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

Reply: no

Gcode reply message

Type = 0x02

Data: the raw string to be replied

Reply: no

When Host MCU receive gcode from esp wifi, it replies “ok\r\n”, and also the file list…using this type of message

Exception information

Type = 0x03

Data:

SegmentOffset (bytes)Length (bytes)Definition
Exception code010x01: file transfer error
0x02: file transfer ok Just now only these two codes

Reply: no

Configure the cloud information

Type = 0x04

Data:

SegmentOffset (bytes)Length (bytes)Definition
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

Reply: no

Unbind the user to wifi module

Type = 0x05

Data: no data

Reply: no

Scan the wifi hot spots

Type = 0x06

Data: no

Reply: no

Wifi connect

Type = 0x09

Data:

SegmentOffset (bytes)Length (bytes)Definition
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

Reply: no

TCP packets

The original firmare use TCP packet for communication, and this is really limiting.

MKS WiFi Plugin for Cura

First you can’t comunicate to a Web user interface, and so this module can be used only with a desktop client, so the client implementation that you can find is the plugin for CURA.

This plugin is very usefully, on Cura go to marketplace and select MKS WiFi plugin

MKS WiFi Cura plugin
MKS WiFi Cura plugin

After the installation, go to 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

After configuration in Cura appear a button to sent directly via WiFi.

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

Examine communication code

The plugin is written in python and to send use TCP message, first of all do the connection:

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

use this command:

    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")

that put in a queue the messages and when the queue data are ready call this function that send message to the 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()

from the MKS WiFi module side you can find the tcp socket initialized on port 8080

WiFiServer tcp(8080);

and in the loop you can find all the management of the messages described in the protocol

			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"));
			}*/

Thanks

  1. MKS WIFI for Makerbase Robin: boards and how to wiring esp12 & NodeMCU
  2. MKS WIFI for Makerbase Robin: PCB and how to compile & upload firmware
  3. MKS WIFI for Makerbase Robin: communication protocol and Cura plugin
  4. MKS WIFI for Makerbase Robin: firmware upgrade and new Web Socket features
  5. MKS WIFI for Makerbase Robin: BeePrint web interface with Camera on Flying Bear Ghost
All the code is released under Creative Commons License
Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *