<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Lesson 5. TF. Markdown web server]]></title><description><![CDATA[<h2>The purpose of this lesson</h2>
<p dir="auto">Hi! Today we will learn how to work with TF-card. One day in one of the lessons we learned how to raise a web server, but today we want more than just to give the text to the client. Markdown. A lightweight markup language created with the purpose of writing maximally readable and easy to edit text, but suitable for conversion to languages for advanced publications (HTML, Rich Text, etc.). We will write a Markdown Web server on the basis of M5STACK (Fig. 1).</p>
<p dir="auto"><img src="https://pp.userapi.com/c831508/v831508268/8749d/5i-Bmzdklr0.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 1.</p>
<p dir="auto">When the device is turned on, the user will be prompted to insert a memory card. The following files will be located on the memory card:</p>
<ul>
<li>a file with a known Wi-Fi networks (Wi-Fi.ini);</li>
<li>the style file (style.css);</li>
<li>A JS library that renders Markdown text (markdown.js);</li>
<li>at the root of the memory card will be custom markdown files (*.md), which will be available to customers (Fig. 1.1).</li>
</ul>
<p dir="auto"><img src="https://sun1-4.userapi.com/c840634/v840634810/5c425/sLwjvmx4Swk.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 1.1. Markdown editor</p>
<p dir="auto">After the user inserts the memory card, the device must be restarted.<br />
At startup, the device will download the Wi-Fi settings from the memory card and try to connect to the first available network. The device will then display the IP address on the screen. In order to view a web page you must use a modern browser that supports JS and CSS.</p>
<h2>Short help</h2>
<p dir="auto">MicroSD-memory card format (Fig. 2) designed for use in portable devices. Today it is widely used in digital cameras and video cameras, mobile phones, smartphones, e-books, GPS-navigators and M5STACK.</p>
<p dir="auto"><img src="https://pp.userapi.com/c834103/v834103268/c2c9f/GeOblJYGegE.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 2. microSD</p>
<p dir="auto">More information is available on Wikipedia <a href="https://en.wikipedia.org/wiki/Secure_Digital" title="https://en.wikipedia.org/wiki/Secure_Digital" target="_blank" rel="noopener noreferrer nofollow ugc">https://en.wikipedia.org/wiki/Secure_Digital</a></p>
<h2><strong>List of components for the lesson</strong></h2>
<ul>
<li>M5STACK;</li>
<li>USB-C cable from standard set ;</li>
<li>4GB MicroSD memory card.</li>
</ul>
<h2>Begin!</h2>
<h3>Step 1. Draw pictures</h3>
<p dir="auto">We'll need images to visualize the processes. Use any graphics editor convenient for you. We will use Paint (Fig. 3, 3.1).</p>
<p dir="auto"><img src="https://pp.userapi.com/c824601/v824601268/c410c/2vaLcTQRLcQ.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 3.</p>
<p dir="auto"><img src="https://pp.userapi.com/c841324/v841324268/71852/DAfN27NK6GA.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 3.1</p>
<p dir="auto">Similarly, draw icons: "insert memory card", "views", "failure", "timer" (Fig. 3.2). Next, we will make them arrays of pixels using the Converter (the link is given below in the Download section).</p>
<p dir="auto"><img src="https://pp.userapi.com/c840733/v840733248/5ddeb/EIp2-8NUZzU.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 3.2. Collection of drawings for the project</p>
<p dir="auto">In the future, we will connect the images to our new project:</p>
<pre><code>extern unsigned char timer_logo[];
extern unsigned char insertsd_logo[];
extern unsigned char error_logo[];
extern unsigned char wifi_logo[];
extern unsigned char views_logo[];
</code></pre>
<h3>Step 2. Wi-Fi client</h3>
<p dir="auto">One day, in one of the lessons we learned how to raise a Wi-Fi hotspot with a web server <a href="http://forum.m5stack.com/topic/60/lesson-3-1-wi-fi-access-point" target="_blank" rel="noopener noreferrer nofollow ugc">http://forum.m5stack.com/topic/60/lesson-3-1-wi-fi-access-point</a><br />
A distinctive feature of today's lesson is the Wi-Fi mode - we will use the "client" mode (Fig. 4).<br />
<img src="https://pp.userapi.com/c841622/v841622248/71bbd/2Z3Dq5c35hw.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 4. Wi-Fi in client mode</p>
<pre><code>WiFi.begin(char* ssid, char* password);
</code></pre>
<h3>Step 3. Preparing the memory card for use</h3>
<p dir="auto">Initialize an instance of the SD class, and at the same time check whether the memory card is installed in the slot or not.</p>
<pre><code>if (!SD.begin())
{
	M5.Lcd.fillRoundRect(0, 0, 320, 240, 7, 0xffff);
	M5.Lcd.drawBitmap(50, 70, 62, 115, (uint16_t *)insertsd_logo);
	M5.Lcd.setCursor(130, 70);
	M5.Lcd.print("INSERT");
	M5.Lcd.setCursor(130, 90);
	M5.Lcd.print("THE TF-CARD");
	M5.Lcd.setCursor(130, 110);
	M5.Lcd.print("AND TAP");
	M5.Lcd.setCursor(130, 130);
	M5.Lcd.setTextColor(0xe8e4);
	M5.Lcd.print("POWER");
	M5.Lcd.setTextColor(0x7bef);
	M5.Lcd.print(" BUTTON"); 
	while(true);
}
</code></pre>
<h3>Step 4. Read the file from the memory card</h3>
<p dir="auto">In order to read data from the memory card, it is necessary to call the open method of the SD class, previously passing it as the char* argument with the file address.</p>
<pre><code>String TFReadFile(String path) {
	File file = SD.open(strToChar(path));
	String buf = "";
	if (file)
	{
    	while (file.available())
    	{
      		buf += (char)file.read();
    	}
    	file.close();
  	}
  	return buf;
}
</code></pre>
<p dir="auto">Working with pointers is not very convenient for us, so we will write a simple function for" converting " String to char*:</p>
<pre><code>char* strToChar(String str) {
	int len = str.length() + 1;
	char* buf = new char[len];
	strcpy(buf, str.c_str());
	return buf;
}
</code></pre>
<p dir="auto">Function TFReadFile takes as a String argument the address of the file, tries to read it, and returns the file contents as a String, if the file read does not work, the function will return an empty string.</p>
<h3>Step 5. Write to a file on the memory card</h3>
<p dir="auto">In order to write to a file, it is necessary to open it by additionally informing the open method FILE_WRITE argument, if the method returns true, it is possible to overwrite the data using the print method of the File class.</p>
<pre><code>bool TFWriteFile(String path, String str) {
	File file = SD.open(strToChar(path), FILE_WRITE);
	bool res = false;
	if (file)
	{
		if (file.print(str)) res = true;
	}
	file.close();
	return false;
}
</code></pre>
<h3>Step 6. Make Wi-Fi setup using wifi.ini</h3>
<p dir="auto">It would be nice if you write to a file in each line of the known Wi-Fi network (Fig. 5, 5.1) and the device would be able to connect to the first available.</p>
<p dir="auto"><img src="https://pp.userapi.com/c841622/v841622513/70dad/0af0TS9a2-E.jpg" alt="" class=" img-fluid img-markdown" /><br />
Figure 5. The contents of the folder system</p>
<p dir="auto"><img src="https://pp.userapi.com/c841622/v841622513/70d70/vp5FGR7WNrU.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 5.1. Wi-Fi access.ini</p>
<p dir="auto">Do so! Tracking the connection state during the connection will be done by means of status method-class Wi-Fi. Let's write a timeout of 10 seconds for one network, I think it will be enough:</p>
<pre><code>bool configWifi() {
	/* Get WiFi SSID &amp; password from wifi.ini from TF-card */
	String file = TFReadFile("/system/wifi.ini");
	if (file != "")
	{	
		for (int i = 0; i &lt; cntChrs(file, '\n'); i++)
		{
			String wifi = parseString(i, '\n', file);
			wifi = wifi.substring(0, (wifi.length() - 1)); // remove last char '\r'
			String ssid = parseString(0, ' ', wifi);
			String pswd = parseString(1, ' ', wifi);
			char* ssid_ = strToChar(ssid);
			char* pswd_ = strToChar(pswd);
			if (WiFi.begin(ssid_, pswd_))
			{
				delay(10);
				unsigned long timeout = 10000;
				unsigned long previousMillis = millis();
				while (true)
				{
					unsigned long currentMillis = millis();
					if (currentMillis - previousMillis &gt; timeout) break;
				  	if (WiFi.status() == WL_CONNECTED) return true;
					delay(100);
				}
			  }
			}
  		}
  	return false;
}
</code></pre>
<h3>Step 7. Let's count views</h3>
<p dir="auto">Every time you open any page, the client will increase the view count by one.<br />
To store the counter, we will automatically create a views file in the system folder. Why will we do it without the file extension? It's for the device, not the computer. It would be better.</p>
<pre><code>int getViews() {
	String file = TFReadFile("/system/views");
	if (file != "") return file.toInt();
	return -1;
}

bool increaseViews() {
	int total = getViews();
	if (total != -1)
	{
		total++;
		if (TFWriteFile("/system/views", (String)(total))) return true;
	}
	else
	{
		if (TFWriteFile("/system/views", (String)(1))) return true;
	}
	return false;
}
</code></pre>
<h3>Step 8. We accept customer requests. A classic of the genre</h3>
<p dir="auto">It is extremely simple! Script and styles get from the memory card, the icon from the external site, and the content with *.md files. Simple as that! By the way, take a look at the openPage function.</p>
<pre><code>String openPage(String page) {
	page += ".md";
	String content = TFReadFile(page);
	if (content != "")
	{
		increaseViews();
		drawViews();
		return content;
	}
	return "# 404 NOT FOUND #\n### MARKDOWN WEB SERVER ON M5STACK  ###"; // if not found 404
}

void loop() {
	String currentString = "";
	bool readyResponse = false;
	WiFiClient client = server.available();
	while (client.connected())
	{
    	if (client.available())
    	{
      		char c = client.read();
     		if ((c != '\r') &amp;&amp; (c != '\n'))
      			currentString += c;
      		else
        		readyResponse = true;
        	
			if (readyResponse)
	      	{
				String GET = parseGET(currentString);
				String mrkdwnContent = openPage(GET);
				client.flush();
				client.println("HTTP/1.1 200 OK");
				client.println("Content-type:text/html");
				client.println();
				client.println("&lt;html&gt;");
				client.println("&lt;head&gt;");
				client.println("&lt;meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/&gt;");
				client.println("&lt;title&gt;Markdown page | M5STACK&lt;/title&gt;");
				client.println("&lt;link rel=\"icon\" type=\"image/x-icon\" href=\"http://m5stack.com/favicon.ico\"&gt;");
				client.println("&lt;script type=\"text/javascript\"&gt;" + TFReadFile("/system/markdown.js") + "&lt;/script&gt;");
				client.println("&lt;style type=\"text/css\"&gt;" + TFReadFile("/system/style.css") + "&lt;/style&gt;"); 
				client.println("&lt;/head&gt;");
				client.println("&lt;body&gt;");
				client.println("&lt;article&gt;&lt;/article&gt;");
				client.println("&lt;script type=\"text/javascript\"&gt;");
				client.println("const message = `" + mrkdwnContent + "`;");
				client.println("const article = document.querySelector('article');");
				client.println("article.innerHTML = markdown.toHTML(message);");
				client.println("&lt;/script&gt;");
				client.println("&lt;/body&gt;");
				client.print("&lt;/html&gt;");       
				client.println();
				client.println();
				readyResponse = false;
				currentString = "";
				client.stop();
	      	}
		}
	}
}
</code></pre>
<h3>Step 9. Run</h3>
<p dir="auto">Good job! The memory card is on the table and waiting to be installed in the slot, and the device meanwhile reminds us of it (Fig. 6).</p>
<p dir="auto"><img src="https://pp.userapi.com/c621509/v621509513/6e890/l2id5u0ooZ4.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 6. Insert the memory card</p>
<p dir="auto">And finally we installed the memory card and pressed the reset button on the device. Wait... At this point, the device searches for an available known wireless network and tries to connect to it (Fig. 6.1).</p>
<p dir="auto"><img src="https://pp.userapi.com/c824701/v824701513/c529f/d6BT35m8m0o.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 6.1. Wait...</p>
<p dir="auto">And here it is! Got! The device shows the address on its display. It is necessary to connect soon! (rice. 6.2).</p>
<p dir="auto"><img src="https://pp.userapi.com/c824701/v824701161/bac37/m-1SGGMNIIY.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 6.2. Ready for operation! First-run</p>
<p dir="auto">The 404 page that is pleasing to the eye (Fig. 6.3). Do not be afraid - we did not do index :)</p>
<p dir="auto"><img src="https://sun1-4.userapi.com/c840634/v840634810/5c42f/F5rfHc2J7_g.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 6.3. Page 404</p>
<p dir="auto">And now let's open what we did it for - factorial page.</p>
<blockquote>
<p dir="auto">Please note: we do not use the file extension (*.md) in the address bar of the browser, see openPage function (Fig. 6.4).</p>
</blockquote>
<p dir="auto"><img src="https://sun1-4.userapi.com/c840634/v840634810/5c439/_lWq4my4GN4.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Figure 6.4. That's fine!</p>
<h2>Download</h2>
<ul>
<li>Files to be copied to the root of the memory card: <a href="https://yadi.sk/d/I7YlKZT-3SdDfx" title="https://yadi.sk/d/I7YlKZT-3SdDfx" target="_blank" rel="noopener noreferrer nofollow ugc">https://yadi.sk/d/I7YlKZT-3SdDfx</a></li>
<li>Converter "image to array" permission 59x59 px: <a href="https://yadi.sk/d/Y0w1r1hR3SdTu7" title="https://yadi.sk/d/Y0w1r1hR3SdTu7" target="_blank" rel="noopener noreferrer nofollow ugc">https://yadi.sk/d/Y0w1r1hR3SdTu7</a></li>
<li>Sketch: <a href="https://yadi.sk/d/SCktJBQm3SdD9m" title="https://yadi.sk/d/SCktJBQm3SdD9m" target="_blank" rel="noopener noreferrer nofollow ugc">https://yadi.sk/d/SCktJBQm3SdD9m</a></li>
</ul>
]]></description><link>https://community.m5stack.com/topic/110/lesson-5-tf-markdown-web-server</link><generator>RSS for Node</generator><lastBuildDate>Wed, 29 Apr 2026 15:13:58 GMT</lastBuildDate><atom:link href="https://community.m5stack.com/topic/110.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 22 Feb 2018 07:31:46 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Lesson 5. TF. Markdown web server on Tue, 12 Jun 2018 13:05:56 GMT]]></title><description><![CDATA[<p dir="auto">So, I found my mistake, I put the 3 files in the SD card while it was put in the system file. first problem solved but now he still does not want to connect to my PA, he connects and then disconnects 5 seconds later</p>
]]></description><link>https://community.m5stack.com/post/1065</link><guid isPermaLink="true">https://community.m5stack.com/post/1065</guid><dc:creator><![CDATA[PépéMax]]></dc:creator><pubDate>Tue, 12 Jun 2018 13:05:56 GMT</pubDate></item><item><title><![CDATA[Reply to Lesson 5. TF. Markdown web server on Tue, 12 Jun 2018 09:37:23 GMT]]></title><description><![CDATA[<p dir="auto">hello I have a problem I think the M5Stack does not mark my SD card because it can not find the WIFI configuration file</p>
]]></description><link>https://community.m5stack.com/post/1064</link><guid isPermaLink="true">https://community.m5stack.com/post/1064</guid><dc:creator><![CDATA[PépéMax]]></dc:creator><pubDate>Tue, 12 Jun 2018 09:37:23 GMT</pubDate></item><item><title><![CDATA[Reply to Lesson 5. TF. Markdown web server on Thu, 01 Mar 2018 15:38:12 GMT]]></title><description><![CDATA[<p dir="auto">@jimit thank! I do everything to make new lessons better</p>
]]></description><link>https://community.m5stack.com/post/491</link><guid isPermaLink="true">https://community.m5stack.com/post/491</guid><dc:creator><![CDATA[Dimi]]></dc:creator><pubDate>Thu, 01 Mar 2018 15:38:12 GMT</pubDate></item><item><title><![CDATA[Reply to Lesson 5. TF. Markdown web server on Thu, 01 Mar 2018 15:02:22 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/dimi" aria-label="Profile: dimi">@<bdi>dimi</bdi></a> Hi Dimi,<br />
Thanks - great lesson ! Very useful.  Well documented ! The screen display asking for the TF card is a good addition.</p>
]]></description><link>https://community.m5stack.com/post/490</link><guid isPermaLink="true">https://community.m5stack.com/post/490</guid><dc:creator><![CDATA[JJ]]></dc:creator><pubDate>Thu, 01 Mar 2018 15:02:22 GMT</pubDate></item></channel></rss>