PWM through HTML (mbed 6)

Good morning,

Recently I found inspiration in this program, coded by @hudakz: WebSwitch_mbed-os - HTTP Server serving a simple webpage which enable… | Mbed

I thought to develop a new project from this code. Instead of having an ON/OFF switch, we would rather have a 0-100% bar which would be used to control the Duty Cycle of a PWM output of the LPC1768.

Similar to: https://www.mpi.nl/corpus/html/elan_ug/images/Media_controls.png

The bar could include 5 values: 0%, 25%, 50%, 75% and 100%. Each of these values would have a unique code that would be processed in the “switch-case” of the main() function. For each of the “switch-case” options, the correct Duty Cycle would be given to the PWM output.

My challenge is to develop the 0-100% HTML bar code and integrate it into my mbed 6 program. Anyone have an idea to share? Thanks!

Hi there

From my experiences it more about HTML/CSS/Javascript than a code in Mbed app. So, when you prepare a web page in requested format then adapting Zoltan’s code is easy.

BR, Jan

Hello Francois,

Below is an Mbed 6 code of an HTTP server controlling LED1 brightness via PWM duty cycle. The server IP address is assigned by DHCP and printed to the Mbed serial console at booting. To use a static IP address uncomment the line

    //net->set_network (IP, NETMASK, GATEWAY);  // include to use static IP address

Best regards,

Zoltan

#include "mbed.h"
#include "EthernetInterface.h"
#include "TCPSocket.h"

#define IP      "192.168.1.181"
#define GATEWAY "192.168.1.1"
#define NETMASK "255.255.255.0"
#define PORT    80

EthernetInterface*  net;
TCPSocket           server;
TCPSocket*          clientSocket;
SocketAddress       clientAddress;
char                httpBuf[1500] = { 0 };
char                httpHeader[256] = { 0 };
const char          PASSWORD[] = "secret";  // Change as you like
int                 volume = 0;
Thread              webServerThread;
void                webServerTask();
PwmOut              output(LED1);

/**
 * @brief   Analyses the received URL
 * @note    The string passed to this function will look like this:
 *          GET /HTTP/1.....
 *          GET /password HTTP/1.....
 *          GET /password/ HTTP/1.....
 *          GET /password/?volume=0 HTTP/1.....
 *          GET /password/?volume=75 HTTP/1.....
 * @param   url URL string
 * @retval -3 just refresh page
 *         -2 no command given but password valid
 *         -1 invalid password
 *      0:100 value of volume variable
 */
int8_t analyseURL(char* url)
{
    if (strlen(url) < (5 + strlen(PASSWORD) + 1))
        return(-1);

    //if (url.substr(5, PASSWORD.size()) != PASSWORD)
    if (strncmp(url + 5, PASSWORD, strlen(PASSWORD)) != 0)
        return(-1);

    uint8_t pos = 5 + strlen(PASSWORD);

    //if (url.substr(pos, 1) != "/")

    if (*(url + pos) != '/')
        return(-1);

    //if (url.substr(pos++, 1) == " ")
    if (*(url + pos++) == ' ')
        return(-2);

    //string  cmd(url.substr(pos, 5));
    *(url + pos + 11) = '\0';    // terminate the cmd string
    char*   cmd = ((url + pos));
    if (strcmp(cmd, "?volume=0 H") == 0)
        return(0);
    if (strcmp(cmd, "?volume=25 ") == 0)
        return(25);
    if (strcmp(cmd, "?volume=50 ") == 0)
        return(50);
    if (strcmp(cmd, "?volume=75 ") == 0)
        return(75);
    if (strcmp(cmd, "?volume=100") == 0)
        return(100);
    return(-3);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
char* movedPermanently(uint8_t flag)
{
    memset(httpBuf, 0, sizeof(httpBuf));
    if (flag == 1) {
        strcpy(httpBuf, "/");
        strcat(httpBuf, PASSWORD);
        strcat(httpBuf, "/");
    }

    strcat(httpBuf, "<h1>301 Moved Permanently</h1>\r\n");
    return(httpBuf);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
char* showWebPage(int value)
{
    char    strBuf[16];

    memset(httpBuf, 0, sizeof(httpBuf));

    /*$off*/
    strcat
        (
            httpBuf,
            "<head>"
                "<meta charset=\"utf-8\">"
                "<meta name=\"viewport\" content=\" initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0; user-scalable=0;\"/>"
                "<title>Smart Home</title>"
                "<link href='http://fonts.googleapis.com/css?family=Droid+Sans&v1' rel='stylesheet' type='text/css'>"
            "</head>"

            "<body>"
            "<h2><a href=\".\" title=\"Click to refresh the page\">PWM Control</a></h2>"
            "   <pre>Volume:</pre>"
        );
    strcat(httpBuf, "<a href='./?volume=0' id='volume_slider'>");
    strcat(httpBuf, "<input type='range' id='volume_range' min='0' max='100' step='25' value=");
    memset(strBuf, 0, sizeof(strBuf));
    sprintf(strBuf, "'%d'", value);
    strcat(httpBuf, strBuf);
    strcat
        (
            httpBuf,
            " oninput='myFunction(this.value);'>"
            "</a>"
            "<span id='volume_display'>"
        );

    memset(strBuf, 0, sizeof(strBuf));
    sprintf(strBuf, "%d", value);
    strcat(httpBuf, strBuf);

    strcat
        (
            httpBuf,
            "</span>"
            "<hr>"
            "<pre>MbedARM</pre>"
        );

    strcat
        (
            httpBuf,
            "<script type='text/javascript'>"
                "function myFunction(newVol) {"
                    "document.getElementById('volume_slider').href = './?volume='+newVol;"
                    "document.getElementById('volume_display').innerText = newVol;"
                "}"
            "</script>"
        );

    strcat(httpBuf, "</body>");

    return httpBuf;
    /*$on*/
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void sendHTTP(TCPSocket* client, char* header, char* content)
{
    char    content_length[10] = { };
    sprintf(content_length, "%u\r\n", strlen(content));
    strcat(header, "\r\nContent-Type: text/html\r\n");
    strcat(header, "Content-Length: ");
    strcat(header, content_length);
    strcat(header, "Pragma: no-cache\r\n");
    strcat(header, "Connection: About to close\r\n\r\n");

    char    c = content[0];
    memmove(httpBuf + strlen(header), httpBuf, strlen(content));    // make room for the header
    strcpy(httpBuf, header);                                        // copy the header on front of the content
    httpBuf[strlen(header)] = c;
    client->send((uint8_t*)httpBuf, strlen(httpBuf));
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void webServerTask()
{
    net = new EthernetInterface;

    if (!net) {
        printf("Error! No network inteface found.\n");
        return;
    }

    //net->set_network (IP, NETMASK, GATEWAY);  // include to use static IP address
    nsapi_size_or_error_t   r = net->connect();
    if (r != 0) {
        printf("Error! net->connect() returned: %d\n", r);
        return;
    }

    // Show the network address
    SocketAddress   ip;
    SocketAddress   netmask;
    SocketAddress   gateway;

    net->get_ip_address(&ip);
    net->get_netmask(&netmask);
    net->get_gateway(&gateway);

    ip.set_port(PORT);

    const char*     ipAddr = ip.get_ip_address();
    const char*     netmaskAddr = netmask.get_ip_address();
    const char*     gatewayAddr = gateway.get_ip_address();

    printf("IP address: %s\r\n", ipAddr ? ipAddr : "None");
    printf("Netmask: %s\r\n", netmaskAddr ? netmaskAddr : "None");
    printf("Gateway: %s\r\n\r\n", gatewayAddr ? gatewayAddr : "None");

    /* Open the server on ethernet stack */
    server.open(net);

    /* Bind the HTTP port (TCP 80) to the server */
    server.bind(ip);

    /* Can handle 5 simultaneous connections */
    server.listen(5);

    //listening for http GET request
    while (true) {
        printf("=========================================\r\n");

        nsapi_error_t   error = 0;

        clientSocket = server.accept(&error);
        if (error != 0) {
            printf("Connection failed!\r\n");
        }
        else {
            //clientSocket->set_timeout(200);
            clientSocket->getpeername(&clientAddress);
            printf("Client with IP address %s connected.\r\n\r\n", clientAddress.get_ip_address());
            error = clientSocket->recv(httpBuf, sizeof(httpBuf));

            switch (error) {
            case 0:
                printf("Recieved buffer is empty.\r\n");
                break;

            case -1:
                printf("Failed to read data from client.\r\n");
                break;

            default:
                printf("Recieved Data: %d\n\r\n\r%.*s\r\n\n\r", strlen(httpBuf), httpBuf);

                if (strncmp(httpBuf, "GET", 3) != 0) {
                    strcpy(httpHeader, "HTTP/1.0 200 OK");
                    strcpy(httpBuf, "<h1>200 OK</h1>");
                    sendHTTP(clientSocket, httpHeader, httpBuf);
                }
                else
                    if ((strncmp(httpBuf, "GET", 3) == 0) && (strncmp(httpBuf + 3, " / ", 3 == 0))) {
                    strcpy(httpHeader, "HTTP/1.0 200 OK");
                    strcpy(httpBuf, "<p>Usage: http://host_or_ip/password</p>\r\n");
                    sendHTTP(clientSocket, httpHeader, httpBuf);
                }
                else {
                    int result = analyseURL(httpBuf);
                    switch (result) {
                    case -3:
                        // update webpage
                        strcpy(httpHeader, "HTTP/1.0 200 OK");
                        sendHTTP(clientSocket, httpHeader, showWebPage(volume));
                        break;

                    case -2:
                        // redirect to the right base url
                        strcpy(httpHeader, "HTTP/1.0 301 Moved Permanently\r\nLocation: ");
                        sendHTTP(clientSocket, httpHeader, movedPermanently(1));
                        break;

                    case -1:
                        strcpy(httpHeader, "HTTP/1.0 401 Unauthorized");
                        strcpy(httpBuf, "<h1>401 Unauthorized</h1>");
                        sendHTTP(clientSocket, httpHeader, httpBuf);
                        break;

                    default:
                        volume = result;    // output on
                        output.write(volume / 100.0f);
                        strcpy(httpHeader, "HTTP/1.0 200 OK");
                        sendHTTP(clientSocket, httpHeader, showWebPage(volume));
                        break;
                    }
                }
                break;
            }
        }

        clientSocket->close();
        printf("Client socket closed\r\n");
    }
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int main(void)
{
    printf("Starting\r\n");
    webServerThread.start(callback(webServerTask));

    while (true) {
        ThisThread::sleep_for(200ms);
    }
}

Thanks Mr. Zoltan!

This is really what I was looking for. Works fine by the way :slight_smile:
May I ask you > if, on this same HTML page (/secret/), I would like to add an ON/OFF slide button as your first code i.e., “ON/OFF Heating switch example”: would it be possible by adding the HTML code & variables right after the “PWM slide bar” code, with a certain position offset in the web page?

Your responses do really help me to build a bridge between c++ mbed code (I/O control) and HTML integration.
Best Regards,
Francois

OK. Here you are. But now it’s you turn to extend the code further. :slight_smile:

#include "mbed.h"
#include "EthernetInterface.h"
#include "TCPSocket.h"

#define IP      "192.168.1.181"
#define GATEWAY "192.168.1.1"
#define NETMASK "255.255.255.0"
#define PORT    80

EthernetInterface*  net;
TCPSocket           server;
TCPSocket*          clientSocket;
SocketAddress       clientAddress;
char                httpBuf[2000] = { 0 };
char                httpHeader[256] = { 0 };
const int           OFF = 0;
const int           ON = 1;
const char          PASSWORD[] = "secret";  // Change as you like
int                 volume = 0;
PwmOut              output(LED1);
DigitalOut          relay(LED2);
Thread              webServerThread;
void                webServerTask();

/**
 * @brief   Analyses the received URL
 * @note    The string passed to this function will look like this:
 *          GET /HTTP/1.....
 *          GET /password HTTP/1.....
 *          GET /password/ HTTP/1.....
 *          GET /password/?volume=25 HTTP/1.....
 *          GET /password/?relay=102 HTTP/1.....
 * @param   url URL string
 * @retval -3 just refresh page
 *         -2 no command given but password valid
 *         -1 invalid password
 *      0:100 value of volume
 *    101,102 value of relay
 */
int8_t analyseURL(char* url)
{
    if (strlen(url) < (5 + strlen(PASSWORD) + 1))
        return(-1);

    //if (url.substr(5, PASSWORD.size()) != PASSWORD)
    if (strncmp(url + 5, PASSWORD, strlen(PASSWORD)) != 0)
        return(-1);

    uint8_t pos = 5 + strlen(PASSWORD);

    //if (url.substr(pos, 1) != "/")

    if (*(url + pos) != '/')
        return(-1);

    //if (url.substr(pos++, 1) == " ")
    if (*(url + pos++) == ' ')
        return(-2);

    //string  cmd(url.substr(pos, 5));
    *(url + pos + 11) = '\0';    // terminate the cmd string
    char*   cmd = ((url + pos));
    printf("cmd=%s\r\n", cmd);
    if (strcmp(cmd, "?volume=0 H") == 0)
        return(0);
    if (strcmp(cmd, "?volume=25 ") == 0)
        return(25);
    if (strcmp(cmd, "?volume=50 ") == 0)
        return(50);
    if (strcmp(cmd, "?volume=75 ") == 0)
        return(75);
    if (strcmp(cmd, "?volume=100") == 0)
        return(100);
    if (strcmp(cmd, "?relay=101 ") == 0)
        return(101);
    if (strcmp(cmd, "?relay=102 ") == 0)
        return(102);
    return(-3);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
char* movedPermanently(uint8_t flag)
{
    memset(httpBuf, 0, sizeof(httpBuf));
    if (flag == 1) {
        strcpy(httpBuf, "/");
        strcat(httpBuf, PASSWORD);
        strcat(httpBuf, "/");
    }

    strcat(httpBuf, "<h1>301 Moved Permanently</h1>\r\n");
    return(httpBuf);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
char* showWebPage()
{
    char    strBuf[16];

    memset(httpBuf, 0, sizeof(httpBuf));

    /*$off*/
    strcat
        (
            httpBuf,
            "<head>"
                "<meta charset=\"utf-8\">"
                "<meta name=\"viewport\" content=\" initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0; user-scalable=0;\"/>"
                "<title>Smart Home</title>"
                "<link href='http://fonts.googleapis.com/css?family=Droid+Sans&v1' rel='stylesheet' type='text/css'>"
        );

    strcat
        (
            httpBuf,
            "<style>"
                ".switch {"
                    "position: relative;"
                    "display: inline-block;"
                    "width: 60px;"
                    "height: 34px;"
                "}"
                ".switch input {display:none;}"
                ".slider {"
                    "position: absolute;"
                    "cursor: pointer;"
                    "top: 0;"
                    "left: 0;"
                    "right: 0;"
                    "bottom: 0;"
                    "border-radius: 34px;"
                    "background-color: #ccc;"
                    "-webkit-transition: .4s;"
                    "transition: .4s;"
                "}"
                ".slider:before {"
                    "position: absolute;"
                    "content: \"\";"
                    "height: 26px;"
                    "width: 26px;"
                    "left: 4px;"
                    "bottom: 4px;"
                    "border-radius: 50%;"
                    "background-color: white;"
                    "-webkit-transition: .4s;"
                    "transition: .4s;"
                "}"
                "input:checked + .slider {"
                    "background-color: #8ce196;"
                "}"
                    "input:focus + .slider {"
                    "box-shadow: 0 0 1px #8ce196;"
                "}"
                    "input:checked + .slider:before {"
                    "-webkit-transform: translateX(26px);"
                    "-ms-transform: translateX(26px);"
                    "transform: translateX(26px);"
                "}"
            "</style>"
        );

    strcat
        (
            httpBuf,
            "</head>"

            "<body>"
            "<h2><a href=\".\" title=\"Click to refresh the page\">PWM Control</a></h2>"
            "<pre>Volume:</pre>"
        );
    strcat(httpBuf, "<a href='./?volume=0' id='volume_slider'>");
    strcat(httpBuf, "<input type='range' id='volume_range' min='0' max='100' step='25' value=");
    memset(strBuf, 0, sizeof(strBuf));
    sprintf(strBuf, "'%d'", volume);
    strcat(httpBuf, strBuf);
    strcat
        (
            httpBuf,
            " oninput='myFunction(this.value);'>"
            "</a>"
            "<span id='volume_display'>"
        );

    memset(strBuf, 0, sizeof(strBuf));
    sprintf(strBuf, "%d", volume);
    strcat(httpBuf, strBuf);
    strcat(httpBuf, "</span>");
    strcat(httpBuf, "<br><br>");
    strcat(httpBuf, "<pre>Relay:\t");
    if(relay == 1) {
        strcat
            (
                httpBuf,
                "<a href='./?relay=101' class='switch'> "
                "<input type='checkbox' checked>"
            );
    }
    else {
        strcat
            (
                httpBuf,
                "<a href='./?relay=102' class='switch'> "
                "<input type='checkbox'>"
            );
    }
    strcat
        (
            httpBuf,
            "<div class=\"slider\"></div>"
            "</a>"
            "</pre>"
            "<hr>"
        );
    strcat(httpBuf, "<pre>MbedARM</pre>");
    strcat
        (
            httpBuf,
            "<script type='text/javascript'>"
                "function myFunction(newVol) {"
                    "document.getElementById('volume_slider').href = './?volume='+newVol;"
                    "document.getElementById('volume_display').innerText = newVol;"
                "}"
            "</script>"
        );
    strcat(httpBuf, "</body>");

    return httpBuf;
    /*$on*/
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void sendHTTP(TCPSocket* client, char* header, char* content)
{
    char    content_length[10] = { };
    sprintf(content_length, "%u\r\n", strlen(content));
    strcat(header, "\r\nContent-Type: text/html\r\n");
    strcat(header, "Content-Length: ");
    strcat(header, content_length);
    strcat(header, "Pragma: no-cache\r\n");
    strcat(header, "Connection: About to close\r\n\r\n");

    char    c = content[0];
    memmove(httpBuf + strlen(header), httpBuf, strlen(content));    // make room for the header
    strcpy(httpBuf, header);                                        // copy the header on front of the content
    httpBuf[strlen(header)] = c;
    client->send((uint8_t*)httpBuf, strlen(httpBuf));
}

//**
/**
 * @brief
 * @note
 * @param
 * @retval
 */
void webServerTask()
{
    net = new EthernetInterface;

    if (!net) {
        printf("Error! No network inteface found.\n");
        return;
    }

    //net->set_network (IP, NETMASK, GATEWAY);  // include to use static IP address
    nsapi_size_or_error_t   r = net->connect();
    if (r != 0) {
        printf("Error! net->connect() returned: %d\n", r);
        return;
    }

    // Show the network address
    SocketAddress   ip;
    SocketAddress   netmask;
    SocketAddress   gateway;

    net->get_ip_address(&ip);
    net->get_netmask(&netmask);
    net->get_gateway(&gateway);

    ip.set_port(PORT);

    const char*     ipAddr = ip.get_ip_address();
    const char*     netmaskAddr = netmask.get_ip_address();
    const char*     gatewayAddr = gateway.get_ip_address();

    printf("IP address: %s\r\n", ipAddr ? ipAddr : "None");
    printf("Netmask: %s\r\n", netmaskAddr ? netmaskAddr : "None");
    printf("Gateway: %s\r\n", gatewayAddr ? gatewayAddr : "None");

    /* Open the server on ethernet stack */
    server.open(net);

    /* Bind the HTTP port (TCP 80) to the server */
    server.bind(ip);

    /* Can handle 5 simultaneous connections */
    server.listen(5);

    printf("Usage: Type %s/%s/  into your web browser and hit ENTER\r\n", ipAddr, PASSWORD);

    //listening for http GET request
    while (true) {
        printf("================================================\r\n");

        nsapi_error_t   error = 0;

        clientSocket = server.accept(&error);
        if (error != 0) {
            printf("Connection failed!\r\n");
        }
        else {
            //clientSocket->set_timeout(200);
            clientSocket->getpeername(&clientAddress);
            printf("Client with IP address %s connected.\r\n", clientAddress.get_ip_address());
            error = clientSocket->recv(httpBuf, sizeof(httpBuf));

            switch (error) {
            case 0:
                printf("Recieved buffer is empty.\r\n");
                break;

            case -1:
                printf("Failed to read data from client.\r\n");
                break;

            default:
                //printf("Recieved Data: %d\n\r\n\r%.*s\r\n\n\r", strlen(httpBuf), httpBuf);

                if (strncmp(httpBuf, "GET", 3) != 0) {
                    strcpy(httpHeader, "HTTP/1.0 200 OK");
                    strcpy(httpBuf, "<h1>200 OK</h1>");
                    sendHTTP(clientSocket, httpHeader, httpBuf);
                }
                else
                    if ((strncmp(httpBuf, "GET", 3) == 0) && (strncmp(httpBuf + 3, " / ", 3) == 0)) {
                    strcpy(httpHeader, "HTTP/1.0 200 OK");
                    strcpy(httpBuf, "<p>Usage: http://host_or_ip/password</p>\r\n");
                    sendHTTP(clientSocket, httpHeader, httpBuf);
                }
                else {
                    int result = analyseURL(httpBuf);
                    switch (result) {
                    case -3:
                        // update webpage
                        strcpy(httpHeader, "HTTP/1.0 200 OK");
                        sendHTTP(clientSocket, httpHeader, showWebPage());
                        break;

                    case -2:
                        // redirect to the right base url
                        strcpy(httpHeader, "HTTP/1.0 301 Moved Permanently\r\nLocation: ");
                        sendHTTP(clientSocket, httpHeader, movedPermanently(1));
                        break;

                    case -1:
                        strcpy(httpHeader, "HTTP/1.0 401 Unauthorized");
                        strcpy(httpBuf, "<h1>401 Unauthorized</h1>");
                        sendHTTP(clientSocket, httpHeader, httpBuf);
                        break;

                    case 101:
                        relay = 0;
                        strcpy(httpHeader, "HTTP/1.0 200 OK");
                        sendHTTP(clientSocket, httpHeader, showWebPage());
                        break;

                    case 102:
                        relay = 1;
                        strcpy(httpHeader, "HTTP/1.0 200 OK");
                        sendHTTP(clientSocket, httpHeader, showWebPage());
                        break;

                    default:
                        volume = result;    // output on
                        output.write(volume / 100.0f);
                        strcpy(httpHeader, "HTTP/1.0 200 OK");
                        sendHTTP(clientSocket, httpHeader, showWebPage());
                        break;
                    }
                }
                break;
            }
        }

        clientSocket->close();
        printf("Client socket closed\r\n");
    }
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int main(void)
{
    printf("Starting\r\n");
    webServerThread.start(callback(webServerTask));

    while (true) {
        ThisThread::sleep_for(200ms);
    }
}
1 Like

Good evening Zoltan,
Once again, many thanks. HTML integration is way more clearer for me now.
I’ll now be able to advance my project using the LPC1768 through the mbed platform.
Best Regards,
Francois