{"id":7967,"date":"2022-09-08T08:12:58","date_gmt":"2022-09-08T15:12:58","guid":{"rendered":"https:\/\/visualgdb.com\/w\/?p=7967"},"modified":"2022-09-08T08:16:09","modified_gmt":"2022-09-08T15:16:09","slug":"using-the-raspberry-pi-pico-w-to-host-web-apps","status":"publish","type":"post","link":"https:\/\/visualgdb.com\/tutorials\/raspberry\/pico_w\/http\/","title":{"rendered":"Hosting Web Apps on Raspberry Pi Pico W with the HTTP Server"},"content":{"rendered":"<p>This tutorial shows how to turn the <strong>Raspberry Pi Pico W board<\/strong> into a Wi-Fi access point running a basic web app with its own CSS styles and API. We will start with our <a href=\"https:\/\/github.com\/sysprogs\/PicoHTTPServer\/\">Raspberry Pi Pico W HTTP Server<\/a> sample and will then extend it, adding a new dialog that can be used to make the on-board LED blink with the specified period.<\/p>\n<p>Before you begin, we advise installing VisualGDB 5.6R7 or later, however you can also achieve the same results by editing the files manually and building the project via command line.<\/p>\n<ol>\n<li>Clone the <a href=\"https:\/\/github.com\/sysprogs\/PicoHTTPServer\/\">https:\/\/github.com\/sysprogs\/PicoHTTPServer\/<\/a> repository to your machine and open the <strong>PicoHTTPServer.sln<\/strong> file in Visual Studio. If this is the first time you are using VisualGDB with Raspberry Pi Pico, it will suggest automatically downloading and installing the toolchain and the Pico SDK:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/01-resolve.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7968\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/01-resolve.png\" alt=\"\" width=\"786\" height=\"593\" \/><\/a><\/li>\n<li>Once the project loads, try building it. The initial build will fail with the missing &#8220;<strong>ip4_secondary_ip_address<\/strong>&#8221; error:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/02-error.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7970\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/02-error.png\" alt=\"\" width=\"1318\" height=\"889\" \/><\/a><\/li>\n<li>This happens because the lwIP stack does not support assigning multiple IP addresses to the same <strong>netconn<\/strong> object, which is necessary to generate the <a href=\"https:\/\/github.com\/sysprogs\/PicoHTTPServer\/blob\/master\/README.md#sign-into-network-message\">&#8220;sign into network&#8221; messages<\/a>. You can address it by applying the <a href=\"https:\/\/github.com\/sysprogs\/PicoHTTPServer\/blob\/master\/lwip_patch\/lwip.patch\">lwip.patch file<\/a> to the lwIP directory inside the Pico SDK, or by manually adding the 2 lines from it to the <strong>ip4.c<\/strong> file:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/02a-patched.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7971\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/02a-patched.png\" alt=\"\" width=\"1318\" height=\"889\" \/><\/a>Now the project should build without errors.<\/li>\n<li>If you have connected the SWD pins on your Pico W (see <a href=\"https:\/\/visualgdb.com\/tutorials\/raspberry\/pico_w\/\">this tutorial<\/a>), right-click on the <strong>PicoHTTPServer<\/strong> project in Solution Explorer, go to the <strong>Debug Settings<\/strong> page and make sure the configuration matches your setup:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/debug.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7972\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/debug.png\" alt=\"\" width=\"908\" height=\"615\" \/><\/a>You will then be able to program the FLASH memory and begin debugging by simply pressing F5.<br \/>\nIf not, you can deploy the built project into the Pico W by starting the board in the bootloader mode and copying the <strong>PicoHTTPServer.uf2<\/strong> file onto it.<\/li>\n<li>Once the firmware starts, locate the <strong>PicoHTTP<\/strong> network from your computer or smartphone and click the &#8220;sign into network&#8221; link:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/02-login.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7973\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/02-login.png\" alt=\"\" width=\"359\" height=\"235\" \/><\/a><\/li>\n<li>The link will lead to a simple web app allowing you to control the board pins from the browser: <a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/04-page.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7974\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/04-page.png\" alt=\"\" width=\"840\" height=\"972\" \/><\/a><\/li>\n<li>We will now modify the web app, adding a new modal dialog and an underlying API to control the LED blinking period. First of all, locate the <strong>index.html<\/strong> file in Solution Explorer (or open it manually from the <strong>www<\/strong> subdirectory) and go to the <strong>&lt;div&gt;<\/strong> with <strong>id=&#8221;popup_root&#8221;<\/strong>:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/06-html.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7975\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/06-html.png\" alt=\"\" width=\"1318\" height=\"889\" \/><\/a>The <strong>index.html<\/strong> file is automatically served by the Pico W each time it is accessed via the browser. It contains HTML, CSS and JavaScript code necessary to run a basic web app. The <strong>&lt;div&gt;<\/strong> element we selected corresponds the semi-transparent layer that is shown over the main page whenever any popup is shown.<\/li>\n<li>Add the following HTML code inside the <strong>popup_root<\/strong> div after the <strong>settings_popup<\/strong>:\n<pre class=\"\">&lt;div id=\"blink_popup\" class=\"blink_popup modal_popup\"&gt;\r\n    &lt;div class=\"popup_header\"&gt;\r\n        &lt;span&gt;LED Blinking Demo&lt;\/span&gt;\r\n        &lt;span class=\"close_button\" style=\"cursor: pointer;\" onclick=\"show_blink_popup(false)\"&gt;&amp;times;&lt;\/span&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div class=\"popup_body\"&gt;\r\n        &lt;div class=\"setting_table\"&gt;\r\n            &lt;span&gt;Blinking period:&lt;\/span&gt;\r\n            &lt;input id=\"blink_period\" value=\"1000\"\/&gt;\r\n            &lt;span&gt;ms&lt;\/span&gt;\r\n            &lt;button onclick=\"call_blink_api('period', document.getElementById('blink_period').value)\"&gt;Update&lt;\/button&gt;\r\n        &lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div class=\"popup_footer\"&gt;\r\n        &lt;button onclick=\"call_blink_api('start')\"&gt;Start&lt;\/button&gt;\r\n        &lt;button onclick=\"call_blink_api('stop')\"&gt;Stop&lt;\/button&gt;\r\n    &lt;\/div&gt;\r\n&lt;\/div&gt;<\/pre>\n<p>It defines a dialog consisting of a header with a close button, a body and a footer. It includes one input field for the period and 3 buttons for sending various requests to the device.<\/li>\n<li>Now we need to add the styles so that the dialog will look like actual modal window (will have a blue header, close button shown in the right place, and sufficient padding around the edges). Locate the CSS stylesheet inside the <strong>index.html<\/strong> file and add the following code to it:\n<pre class=\"\">.blink_popup {\r\n    width: 500px;\r\n    padding: 0;\r\n}\r\n\r\n.setting_table {\r\n    display: grid;\r\n    grid-gap: 5px;\r\n    grid-template-columns: auto 1fr auto auto;\r\n    align-items: center;\r\n}\r\n\r\n.popup_header {\r\n    background: blue;\r\n    margin: 0;\r\n    padding: 10px 20px;\r\n    color: white;\r\n    font-size: 22;\r\n    display: flex;\r\n    justify-content: space-between;\r\n}\r\n\r\n.popup_body {\r\n    margin: 0 20px;\r\n}\r\n\r\n.popup_footer {\r\n    margin: 0 20px 20px 20px;\r\n}<\/pre>\n<\/li>\n<li>Add a function to show and hide the popup inside the <strong>&lt;script&gt;<\/strong> element in <strong>index.html<\/strong>:\n<pre class=\"\">function show_blink_popup(show) {\r\n    document.getElementById(\"popup_root\").style.display = show ? \"block\" : \"none\";\r\n    document.getElementById(\"blink_popup\").style.display = show ? \"block\" : \"none\";\r\n}<\/pre>\n<\/li>\n<li>Finally, modify the header to show a &#8220;blink&#8221; button near the settings editing button:\n<pre class=\"\">&lt;h1&gt;Raspberry Pi Pico W Demo\r\n    &lt;a href=\"javascript:edit_settings()\" class=\"settings_link\"&gt;&lt;img style=\"width: 32px; height: 32px; margin: 0px;\" src=\"img\/configure.png\"&gt;&lt;\/a&gt;\r\n    &lt;a href=\"javascript:show_blink_popup(true)\" class=\"settings_link\"&gt;&lt;img style=\"width: 32px; height: 32px; margin: 0px;\" src=\"img\/stopwatch.png\"&gt;&lt;\/a&gt;\r\n&lt;\/h1&gt;<\/pre>\n<p>Also download the <a href=\"https:\/\/github.com\/sysprogs\/PicoHTTPServer\/raw\/blink\/PicoHTTPServer\/www\/img\/stopwatch.png\">stopwatch.png<\/a> icon and place it into the <strong>www\/img<\/strong> subdirectory.<\/li>\n<li>Build the project and start debugging it. The CMake build scripts will automatically invoke the <a href=\"https:\/\/github.com\/sysprogs\/PicoHTTPServer\/tree\/master\/tools\/SimpleFSBuilder\">SimpleFSBuilder<\/a> tool to pack the modified <strong>index.html<\/strong> and <strong>stopwatch.png<\/strong> into a simple archive readable by the Pico HTTP server. Make sure you are still connected to the Wi-Fi network and refresh the page in the browser, then click the new stopwatch button:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/07-blink.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7976\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/07-blink.png\" alt=\"\" width=\"813\" height=\"898\" \/><\/a><\/li>\n<li>If you still see the old index page, make sure the &#8220;<strong>Always Consider Outdated<\/strong>&#8221; setting is enabled in the CMake project properties:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/05-outdated-1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7978\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/05-outdated-1.png\" alt=\"\" width=\"1318\" height=\"889\" \/><\/a>Otherwise, changing the web server payload without modifying any of the source files will not trigger a rebuild.<\/li>\n<li>The buttons in the newly added dialog won&#8217;t work yet because we have not implemented the <strong>call_blink_api()<\/strong> function used by them. Add the following code inside the <strong>&lt;script&gt;<\/strong> element in <strong>index.html<\/strong>:\n<pre class=\"\">function call_blink_api(path, content = null) {\r\n    let xhr = new XMLHttpRequest();\r\n    xhr.open(content ? \"POST\" : \"GET\", '\/api\/blink\/' + path, true);\r\n    if (content)\r\n        xhr.setRequestHeader(\"Content-Type\", \"text\/plain\");\r\n\r\n    xhr.onloadend = function () {\r\n        if (this.responseText != \"OK\")\r\n            alert(this.responseText);\r\n    };\r\n\r\n    if (content)\r\n        xhr.send(content + \"\\r\\n\");\r\n    else\r\n        xhr.send();\r\n}<\/pre>\n<p>Each time you press either of the buttons, your browser will send a GET or POST request to the <strong>\/api\/blink\/&lt;&#8230;&gt;<\/strong> endpoint. If the request returns anything other than the &#8220;OK&#8221; message, the browser will display the returned text in an error message.<\/li>\n<li>The final thing we need to is handle these requests from the C code. Locate the calls to <strong>http_server_add_zone()<\/strong> in <strong>main.c<\/strong> and modify them as shown below:\n<pre class=\"\">static http_zone zone1, zone2, zone3;\r\nhttp_server_add_zone(server, &amp;zone1, \"\", do_retrieve_file, NULL);\r\nhttp_server_add_zone(server, &amp;zone2, \"\/api\", do_handle_api_call, NULL);\r\nhttp_server_add_zone(server, &amp;zone3, \"\/api\/blink\", do_handle_blink_api_call, NULL);<\/pre>\n<p>This ensures that each request to \/api\/blink\/&lt;&#8230;&gt; will get routed to the <strong>do_handle_blink_api_call()<\/strong> function. Make sure you register the <strong>\/api\/blink<\/strong> zone AFTER the generic <strong>\/api<\/strong> zone, as the HTTP server checks the zone list in the backward order.<\/li>\n<li>Add a function for actually handling the requests:\n<pre class=\"\">xSemaphoreHandle s_BlinkSemaphore;\r\nstatic int s_BlinkPeriod = 1000;\r\n\r\nstatic void blink_task(void *arg)\r\n{\r\n    static bool on = false;\r\n    for (;;)\r\n    {\r\n        xSemaphoreTake(s_BlinkSemaphore, pdMS_TO_TICKS(s_BlinkPeriod));\r\n        cyw43_arch_gpio_put(0, on = !on);\r\n    }\r\n}\r\n\r\nstatic bool do_handle_blink_api_call(http_connection conn, enum http_request_type type, char *path, void *context)\r\n{\r\n    static TaskHandle_t s_BlinkTask;\r\n    if (!strcmp(path, \"start\"))\r\n    {\r\n        if (!s_BlinkSemaphore)\r\n            s_BlinkSemaphore = xSemaphoreCreateCounting(1, 0);\r\n        if (!s_BlinkTask)\r\n\t    xTaskCreate(blink_task, \"Blink\", configMINIMAL_STACK_SIZE, NULL, TEST_TASK_PRIORITY, &amp;s_BlinkTask);\r\n        http_server_send_reply(conn, \"200 OK\", \"text\/plain\", \"OK\", -1);\r\n        return true;\r\n    }\r\n    else if (!strcmp(path, \"stop\"))\r\n    {\r\n        if (s_BlinkTask)\r\n        {\r\n            vTaskDelete(s_BlinkTask);\r\n            s_BlinkTask = NULL;\r\n        }\r\n        http_server_send_reply(conn, \"200 OK\", \"text\/plain\", \"OK\", -1);\r\n        cyw43_arch_gpio_put(0, false);\r\n        return true;\r\n    }\r\n    else if (!strcmp(path, \"period\"))\r\n    {\r\n        char *line = http_server_read_post_line(conn);\r\n        int period = line ? atoi(line) : 0;\r\n        if (period)\r\n        {\r\n            s_BlinkPeriod = period;\r\n            xSemaphoreGive(s_BlinkSemaphore);\r\n            http_server_send_reply(conn, \"200 OK\", \"text\/plain\", \"OK\", -1);\r\n        }\r\n        else\r\n            http_server_send_reply(conn, \"200 OK\", \"text\/plain\", \"Invalid period!\", -1);\r\n        return true;\r\n    }\r\n    else\r\n        return false;\r\n}<\/pre>\n<p>Note how it cerates the <strong>Blink<\/strong> task in response to the <strong>start<\/strong> call, deletes it in response to <strong>stop<\/strong>, and updates the period in response to the <strong>period<\/strong> call. Signaling the <strong>s_BlinkSemaphore<\/strong> after updating the period is done to immediately wake up <strong>blink_task()<\/strong> without waiting for the previously set period to elapse.<\/li>\n<li>Press F5 to build the project and start debugging it again. Open the blink dialog in the browser, click &#8220;Start&#8221; and try changing the period. The on-board LED will blink according to the entered value. If you try setting the period to 0 or a non-number, the browser will display the &#8220;Invalid period!&#8221; message relayed from the <strong>do_handle_blink_api_call()<\/strong> function: <a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/08-error.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7980\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/08-error.png\" alt=\"\" width=\"813\" height=\"898\" \/><\/a><\/li>\n<li>You can view various debugging output from the project by using the Fast Semihosting feature of VisualGDB. As RP2040 device does not support detecting whether a debugger is attached, the example project comes with the semihosting disabled. You can enable it by setting &#8220;<strong>When running without debugger: wait for debugger to attach<\/strong>&#8221; in VisualGDB Project Properties:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/09-wait.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7981\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/09-wait.png\" alt=\"\" width=\"1044\" height=\"715\" \/><\/a><\/li>\n<li>The debug output will appear in the <strong>Debug-&gt;Windows-&gt;VisualGDB Output<\/strong> window:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/10-live.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-7983\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2022\/09\/10-live.png\" alt=\"\" width=\"1318\" height=\"889\" \/><\/a>You can also view various lwIP statistics (e.g. number of processed packets) in real time via <strong>Debug-&gt;Windows-&gt;Live Watch-&gt;lwip_stats<\/strong>.<\/li>\n<\/ol>\n<p>You can find the code shown in this tutorial in the <a href=\"https:\/\/github.com\/sysprogs\/PicoHTTPServer\/tree\/blink\">blink branch<\/a> of our GitHub repository. See <a href=\"https:\/\/github.com\/sysprogs\/PicoHTTPServer\/commit\/a465c257b19c1e0658a9a2fb7d2121eb1c6414a3\">this commit<\/a> for the exact changes described above.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This tutorial shows how to turn the Raspberry Pi Pico W board into a Wi-Fi access point running a basic<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[18],"tags":[101,231,102],"_links":{"self":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts\/7967"}],"collection":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/comments?post=7967"}],"version-history":[{"count":5,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts\/7967\/revisions"}],"predecessor-version":[{"id":7986,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts\/7967\/revisions\/7986"}],"wp:attachment":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/media?parent=7967"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/categories?post=7967"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/tags?post=7967"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}