From c0e774c08c4f8734465d156b416057db58d4a52a Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Tue, 13 Feb 2024 18:01:59 -0800 Subject: [PATCH] implement basic http server --- .gitignore | 3 +- CMakeLists.txt | 28 ++++++----- Makefile | 17 +++++-- bmc.c | 25 ---------- cgi.h | 32 +++++++++++++ html_files/index.shtml | 20 ++++++++ lwipopts.h | 15 +++--- main.c | 36 ++++++++++++++ makefsdata.py | 106 +++++++++++++++++++++++++++++++++++++++++ ssi.h | 51 ++++++++++++++++++++ template.secret.h | 4 +- 11 files changed, 286 insertions(+), 51 deletions(-) delete mode 100644 bmc.c create mode 100644 cgi.h create mode 100644 html_files/index.shtml create mode 100644 main.c create mode 100644 makefsdata.py create mode 100644 ssi.h diff --git a/.gitignore b/.gitignore index 6070fe8..e7516f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ **/build/* -**/secret.h \ No newline at end of file +**/secret.h +**/htmldata.c \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e0c38fb..5af8aa9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,21 @@ cmake_minimum_required(VERSION 3.13) +set(PROGRAM_NAME pico_bmc) +set(PICO_BOARD pico_w) include(pico_sdk_import.cmake) -project(bmc) -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 17) +project(pico_bmc) pico_sdk_init() -add_executable(bmc - bmc.c +add_executable(${PROGRAM_NAME} + main.c ) -target_link_libraries(bmc - pico_stdlib - pico_cyw43_arch_lwip_threadsafe_background +target_include_directories(${PROGRAM_NAME} PRIVATE + ${CMAKE_CURRENT_LIST_DIR} ) -pico_enable_stdio_usb(bmc 1) -pico_enable_stdio_uart(bmc 0) -pico_add_extra_outputs(bmc) -target_include_directories(bmc PRIVATE ${CMAKE_CURRENT_LIST_DIR}) \ No newline at end of file +target_link_libraries(${PROGRAM_NAME} + pico_cyw43_arch_lwip_threadsafe_background + pico_lwip_http + pico_stdlib + hardware_adc +) +pico_enable_stdio_usb(${PROGRAM_NAME} TRUE) +pico_enable_stdio_uart(${PROGRAM_NAME} FALSE) +pico_add_extra_outputs(${PROGRAM_NAME}) \ No newline at end of file diff --git a/Makefile b/Makefile index 6210152..dec91a7 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,16 @@ -TOPTARGETS := all clean +#TOPTARGETS := all clean -SUBDIRS := $(wildcard */.) +SUBDIRS := build + +all: makefsdata $(SUBDIRS) + +makefsdata: + python3 makefsdata.py + +clean: $(SUBDIRS) + rm -rf htmldata.c -$(TOPTARGETS): $(SUBDIRS) $(SUBDIRS): - $(MAKE) -C $@ $(MAKECMDGOALS) + $(MAKE) -C $@ $(MAKECMDGOALS) -.PHONY: $(TOPTARGETS) $(SUBDIRS) \ No newline at end of file +.PHONY: all makefsdata clean $(SUBDIRS) \ No newline at end of file diff --git a/bmc.c b/bmc.c deleted file mode 100644 index cbd608a..0000000 --- a/bmc.c +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include "pico/stdlib.h" -#include "pico/cyw43_arch.h" -#include "secret.h" - -int main() { - stdio_init_all(); - if (cyw43_arch_init()) { - printf("Wi-Fi init failed\n"); - return -1; - } - printf("Wi-Fi init succeeded\n"); - cyw43_arch_enable_sta_mode(); - if (cyw43_arch_wifi_connect_timeout_ms(ssid, pass, CYW43_AUTH_WPA2_AES_PSK, 10000)) { - printf("WiFi failed to connect\n"); - return -1; - } - printf("WiFi connected\n"); - while (true) { - cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1); - sleep_ms(1000); - cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0); - sleep_ms(1000); - } -} \ No newline at end of file diff --git a/cgi.h b/cgi.h new file mode 100644 index 0000000..3608d98 --- /dev/null +++ b/cgi.h @@ -0,0 +1,32 @@ +#include "lwip/apps/httpd.h" +#include "pico/cyw43_arch.h" + +// CGI handler which is run when a request for /led.cgi is detected +const char * cgi_led_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) +{ + // Check if an request for LED has been made (/led.cgi?led=x) + if (strcmp(pcParam[0] , "led") == 0){ + // Look at the argument to check if LED is to be turned on (x=1) or off (x=0) + if(strcmp(pcValue[0], "0") == 0) + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0); + else if(strcmp(pcValue[0], "1") == 0) + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1); + } + + // Send the index page back to the user + return "/index.shtml"; +} + +// tCGI Struct +// Fill this with all of the CGI requests and their respective handlers +static const tCGI cgi_handlers[] = { + { + // Html request for "/led.cgi" triggers cgi_handler + "/led.cgi", cgi_led_handler + }, +}; + +void cgi_init(void) +{ + http_set_cgi_handlers(cgi_handlers, 1); +} \ No newline at end of file diff --git a/html_files/index.shtml b/html_files/index.shtml new file mode 100644 index 0000000..1ce5ee8 --- /dev/null +++ b/html_files/index.shtml @@ -0,0 +1,20 @@ + + + + PicoW Webserver + +

PicoW Webserver Tutorial

+
+

This bit is SSI:

+

Voltage:

+

Temp: C

+

LED is:

+
+

This bit is CGI:

+ + +
+
+ Refresh + + \ No newline at end of file diff --git a/lwipopts.h b/lwipopts.h index 217cb13..b4ea9a3 100644 --- a/lwipopts.h +++ b/lwipopts.h @@ -1,9 +1,5 @@ -#ifndef _LWIPOPTS_EXAMPLE_COMMONH_H -#define _LWIPOPTS_EXAMPLE_COMMONH_H - - // Common settings used in most of the pico_w examples -// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details) +// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details)] // allow override in some examples #ifndef NO_SYS @@ -87,4 +83,11 @@ #define SLIP_DEBUG LWIP_DBG_OFF #define DHCP_DEBUG LWIP_DBG_OFF -#endif /* __LWIPOPTS_H__ */ +// This section enables HTTPD server with SSI, SGI +// and tells server which converted HTML files to use +#define LWIP_HTTPD 1 +#define LWIP_HTTPD_SSI 1 +#define LWIP_HTTPD_CGI 1 +#define LWIP_HTTPD_SSI_INCLUDE_TAG 0 +#define HTTPD_SERVER_PORT 80 +#define HTTPD_FSDATA_FILE "htmldata.c" \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..0580dc7 --- /dev/null +++ b/main.c @@ -0,0 +1,36 @@ +#include "lwip/apps/httpd.h" +#include "pico/stdlib.h" +#include "pico/cyw43_arch.h" +#include "lwipopts.h" +#include "ssi.h" +#include "cgi.h" +#include "secret.h" + +int main() { + stdio_init_all(); + + if (cyw43_arch_init()) { + printf("Wi-Fi init failed\n"); + return -1; + } + printf("Wi-Fi init succeeded\n"); + + cyw43_arch_enable_sta_mode(); + + if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASS, CYW43_AUTH_WPA2_AES_PSK, 30000)){ + printf("Wi-Fi failed to connect\n"); + return -1; + } + printf("Wi-Fi connected\n"); + + httpd_init(); + printf("HTTP Server initialized at %s on port %d\n", ip4addr_ntoa(netif_ip4_addr(netif_list)), HTTPD_SERVER_PORT); + + ssi_init(); + printf("SSI Handler initialized\n"); + + cgi_init(); + printf("CGI Handler initialised\n"); + + while(1); +} \ No newline at end of file diff --git a/makefsdata.py b/makefsdata.py new file mode 100644 index 0000000..d9bba5a --- /dev/null +++ b/makefsdata.py @@ -0,0 +1,106 @@ +#!/usr/bin/python3 + +# This script is by @rspeir on GitHub: +# https://github.com/krzmaz/pico-w-webserver-example/pull/1/files/4b3e78351dd236f213da9bebbb20df690d470476#diff-e675c4a367e382db6f9ba61833a58c62029d8c71c3156a9f238b612b69de279d +# Renamed output to avoid linking incorrect file + +import os +import binascii + +#Create file to write output into +output = open('htmldata.c', 'w') + +#Traverse directory, generate list of files +files = list() +os.chdir('./html_files') +for(dirpath, dirnames, filenames) in os.walk('.'): + files += [os.path.join(dirpath, file) for file in filenames] + +filenames = list() +varnames = list() + +#Generate appropriate HTTP headers +for file in files: + + if '404' in file: + header = "HTTP/1.0 404 File not found\r\n" + else: + header = "HTTP/1.0 200 OK\r\n" + + header += "Server: lwIP/pre-0.6 (http://www.sics.se/~adam/lwip/)\r\n" + + if '.html' in file: + header += "Content-type: text/html\r\n" + elif '.shtml' in file: + header += "Content-type: text/html\r\n" + elif '.jpg' in file: + header += "Content-type: image/jpeg\r\n" + elif '.gif' in file: + header += "Content-type: image/gif\r\n" + elif '.png' in file: + header += "Content-type: image/png\r\n" + elif '.class' in file: + header += "Content-type: application/octet-stream\r\n" + elif '.js' in file: + header += "Content-type: text/javascript\r\n" + elif '.css' in file: + header += "Content-type: text/css\r\n" + elif '.svg' in file: + header += "Content-type: image/svg+xml\r\n" + else: + header += "Content-type: text/plain\r\n" + + header += "\r\n" + + fvar = file[1:] #remove leading dot in filename + fvar = fvar.replace('/', '_') #replace *nix path separator with underscore + fvar = fvar.replace('\\', '_') #replace DOS path separator with underscore + fvar = fvar.replace('.', '_') #replace file extension dot with underscore + + output.write("static const unsigned char data{}[] = {{\n".format(fvar)) + output.write("\t/* {} */\n\t".format(file)) + + #first set of hex data encodes the filename + b = bytes(file[1:].replace('\\', '/'), 'utf-8') #change DOS path separator to forward slash + for byte in binascii.hexlify(b, b' ', 1).split(): + output.write("0x{}, ".format(byte.decode())) + output.write("0,\n\t") + + #second set of hex data is the HTTP header/mime type we generated above + b = bytes(header, 'utf-8') + count = 0 + for byte in binascii.hexlify(b, b' ', 1).split(): + output.write("0x{}, ".format(byte.decode())) + count = count + 1 + if(count == 10): + output.write("\n\t") + count = 0 + output.write("\n\t") + + #finally, dump raw hex data from files + with open(file, 'rb') as f: + count = 0 + while(byte := f.read(1)): + byte = binascii.hexlify(byte) + output.write("0x{}, ".format(byte.decode())) + count = count + 1 + if(count == 10): + output.write("\n\t") + count = 0 + output.write("};\n\n") + + filenames.append(file[1:]) + varnames.append(fvar) + +for i in range(len(filenames)): + prevfile = "NULL" + if(i > 0): + prevfile = "file" + varnames[i-1] + + output.write("const struct fsdata_file file{0}[] = {{{{ {1}, data{2}, ".format(varnames[i], prevfile, varnames[i])) + output.write("data{} + {}, ".format(varnames[i], len(filenames[i]) + 1)) + output.write("sizeof(data{}) - {}, ".format(varnames[i], len(filenames[i]) + 1)) + output.write("FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT}};\n") + +output.write("\n#define FS_ROOT file{}\n".format(varnames[-1])) +output.write("#define FS_NUMFILES {}\n".format(len(filenames))) \ No newline at end of file diff --git a/ssi.h b/ssi.h new file mode 100644 index 0000000..9d9b472 --- /dev/null +++ b/ssi.h @@ -0,0 +1,51 @@ +#include "lwip/apps/httpd.h" +#include "pico/cyw43_arch.h" +#include "hardware/adc.h" + +// SSI tags - tag length limited to 8 bytes by default +const char * ssi_tags[] = {"volt","temp","led"}; + +u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) { + size_t printed; + switch (iIndex) { + case 0: // volt + { + const float voltage = adc_read() * 3.3f / (1 << 12); + printed = snprintf(pcInsert, iInsertLen, "%f", voltage); + } + break; + case 1: // temp + { + const float voltage = adc_read() * 3.3f / (1 << 12); + const float tempC = 27.0f - (voltage - 0.706f) / 0.001721f; + printed = snprintf(pcInsert, iInsertLen, "%f", tempC); + } + break; + case 2: // led + { + bool led_status = cyw43_arch_gpio_get(CYW43_WL_GPIO_LED_PIN); + if(led_status == true){ + printed = snprintf(pcInsert, iInsertLen, "ON"); + } + else{ + printed = snprintf(pcInsert, iInsertLen, "OFF"); + } + } + break; + default: + printed = 0; + break; + } + + return (u16_t)printed; +} + +// Initialise the SSI handler +void ssi_init() { + // Initialise ADC (internal pin) + adc_init(); + adc_set_temp_sensor_enabled(true); + adc_select_input(4); + + http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags)); +} \ No newline at end of file diff --git a/template.secret.h b/template.secret.h index 78b37a7..2e3d414 100644 --- a/template.secret.h +++ b/template.secret.h @@ -1,7 +1,7 @@ #ifndef SECRET_H #define SECRET_H -char ssid[] = "wifi ssid"; -char pass[] = "wifi pass"; +const char WIFI_SSID[] = "ssid"; +const char WIFI_PASS[] = "pass"; #endif \ No newline at end of file