Introduction

When creating Arduino-powered devices, there are some parameters that can be changed by user during operation (such as UART baud rate, schedules... ). It's inconvenient if we do the hard-coding these parameters. If you do so, each time users want to change a parameter, they have to make a change in code, and then re-upload Arduino code to the board again.

There is a solution for it. Let users set these parameters via Web any time they want. These parameters will be stored in EEPROM that information is not lost when the power is down.

This tutorial shows how to do it. I take the setting schedule via Web as an example. Let's start! Demonstration

Arduino blinks a LED if the current time is on a schedule. If not, Arduino turns off the LED.
Things we need
  • Arduino Uno
  • PHPoC WiFi Shield or PHPoC Shield
  • A LED

User Interface

Click image for larger version  Name:	ui.png Views:	1 Size:	141.5 KB ID:	1017




Working Flow of Arduino

Right after booting up (see setup() function in Arduino code)

Arduino wil load the existing schedules from EEPROM.

Schedule is stored in EEPROM in CSV format. Each schedule is stored in a line. Each parameter in a schedule is separated by comma. For example, There are two schedules: Monday, from 8h:00 to 15:00h and Friday, from 9:30h to 17:00h. They will be stored in EEPROM as follows:
Code:
1,08:00,15:00\n
5,08:00,15:00\n

where "1" and "5" represent for Monday and Friday, respectively.






In infinite loop (see loop() function in Arduino code)
  • Arduino check whether there is any the incoming setting from Web or not (see Setting Schedule via Web for detail).
  • Arduino check whether current time is on schedule or not. It get the current time, compare the time with time in the loaded schedules one by one. If the current time is on a schedule, it will do the some works. In my code, to make it simple, if the current time is on a schedule, Arduino will blink a LED. If not, Arduino turn off LED.
Code:
  
bool isOnSchedule(void) {
    int day, hour, minute;

    day        = datetime.dayofWeek();
    hour    = datetime.hour();
    minute    = datetime.minute();

    for(int i = 0; i < numSchedule; i++) {
        if(day == scheduleArray[i][DAY]) {
            int curTime, startTime, endTime;

            curTime        = hour * 60 + minute;
            startTime    = scheduleArray[i][START_HOUR] * 60 + scheduleArray[i][START_MINUTE];
            endTime        = scheduleArray[i][END_HOUR] * 60 + scheduleArray[i][END_MINUTE];

            if(curTime >= startTime && curTime < endTime)
                return true;
        }
    }

    return false;
}



Setting Schedule via Web


see scheduleLoop(void) function

This function keeps the role in communicating with Web application. It communication with Web via set of commands:

Command from Web to Arduino
  • CMD_ARDUINO_CLR : request Arduino to delete all existing schedules
  • CMD_ARDUINO_GET : Request Arduino to send all the existing schedules to Web.
  • CMD_ARDUINO_ADD + data : Request Arduino add a schedule.

Command from Arduinio to Web
  • CMD_WEB_CLR: Request Web to clear all the schedule on User Interface.
  • CMD_WEB_UPD + data : Send a schedule to Web.

Data Flow between Web and Arduino


When web app connects to Arduino via websocket:
  • Web sends CMD_ARDUINO_GET command to Arduino.
  • Arduino reads all schedule from EEPROM
  • Arduino send CMD_WEB_CLR command to Arduino
  • Web clear all all schedule in UI
  • Arduino send CMD_WEB_UPD command along with schedule to Arduino one by one
  • Web adds the schedule in a schedule list and displays the receiving schedule in Web UI

When user adds/deletes a schedule on Web:
  • Web adds/deletes the schedule from the schedule list and UI
  • Web sends CMD_ARDUINO_CLR to Arduino
  • Arduino deletes all existing schedules in EEPROM
  • Web sends CMD_ARDUINO_ADD along with schedule (one by one) to Arduino
  • Arduino write the schedule to EEPROM



Code

Source code include two files:
  • WebConfigSchedule.ino: is compiled and upload to Arduino via Arduino IDE
  • remote_config.php: this is web app code, it is uploaded to PHPoC shield via PHPoC Debugger.
<WebConfigSchedule.ino>
Code:
#include "SPI.h"
#include "Phpoc.h"
#include <EEPROM.h>

#ifndef EOF
#define EOF (-1)
#endif

#define CMD_ARDUINO_CLR 0
#define CMD_ARDUINO_GET 1
#define CMD_ARDUINO_ADD 2
#define CMD_WEB_CLR        5
#define CMD_WEB_UPD        6

#define MAX_SCHEDULE    10

#define    DAY                0
#define START_HOUR        1
#define START_MINUTE    2
#define END_HOUR        3
#define END_MINUTE        4

class EepromCSV
{
    public:
        void clear(void);
        int line(void);
        String readLine(int line);
        void writeLine(String lineStr);
};

EepromCSV EepromCSV;
PhpocServer configServer(80);
PhpocDateTime datetime;

byte scheduleArray[MAX_SCHEDULE][5];
int numSchedule;

/* Varalble to blink a LED*/
const int ledPin = 7;
int ledState = LOW;
unsigned long previousMillis = 0;  
const long interval = 500;  

void setup() {
    Serial.begin(9600);
    while(!Serial)
        ;
    Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
    configServer.beginWebSocket("arduino_config");

    Serial.print("WebSocket server address : ");
    Serial.println(Phpoc.localIP());

    eepromToSchedule(); // load schedule from EEPROM

    pinMode(ledPin, OUTPUT);
}

void loop() {
    scheduleLoop(); // check whether there is any config command from Web or not.

    if(isOnSchedule()) { // check whether current time is on schedule or not
        /* TO DO */
        unsigned long currentMillis = millis();

        if(currentMillis - previousMillis >= interval) {
            previousMillis = currentMillis;

            if (ledState == LOW)
                ledState = HIGH;
            else
                ledState = LOW;

            digitalWrite(ledPin, ledState);
        }
    }
    else {
        if (ledState == HIGH) {
            ledState = LOW;
            digitalWrite(ledPin, ledState);
        }
    }
}

void eepromToSchedule(void) {
    String schedule;
    int lineCount = EepromCSV.line();

    if(lineCount > MAX_SCHEDULE)
        lineCount = MAX_SCHEDULE;

    for(int i = 0; i < lineCount; i++) {
        schedule = EepromCSV.readLine(i);

        scheduleArray[i][DAY]            = schedule.substring(0, 1).toInt();
        scheduleArray[i][START_HOUR]    = schedule.substring(2, 4).toInt();
        scheduleArray[i][START_MINUTE]    = schedule.substring(5, 7).toInt();
        scheduleArray[i][END_HOUR]        = schedule.substring(8, 10).toInt();
        scheduleArray[i][END_MINUTE]    = schedule.substring(11, 13).toInt();
    }

    numSchedule = lineCount;
}
void sendToWeb(String data) {
    char wbuf[18];

    data.toCharArray(wbuf, data.length() + 1);
    configServer.write(wbuf, data.length());
}
void scheduleLoop(void) {
    PhpocClient client = configServer.available();

    if(client) {
        if(client.available() >= 17) {
            char rbuf[17];
            String data, schedule;
            int cmd;

            client.read(rbuf, 17);
            data = String(rbuf);
            cmd = data.substring(0, 1).toInt();

           if(cmd == CMD_ARDUINO_GET) {
                int lineCount = EepromCSV.line();

                sendToWeb(String(CMD_WEB_CLR) + ",0,00:00,00:00\r\n"); /* tell web clear to load new schedule */

                for(int i = 0; i < lineCount; i++) {
                    schedule = EepromCSV.readLine(i); /* read schedule from EEPROM one by one */
                    sendToWeb(String(CMD_WEB_UPD) + "," + schedule + "\r\n"); /* send schedule to Web */
                }
            }
            else if(cmd == CMD_ARDUINO_CLR) {
                EepromCSV.clear(); /* clear to update new schedule */
                eepromToSchedule(); /* reload schedule from EEPROM */
            }
            else if(cmd == CMD_ARDUINO_ADD) {
                schedule = data.substring(2, 15);
                EepromCSV.writeLine(schedule); /* write schedule to EEPROM */
                eepromToSchedule(); /* reload schedule from EEPROM */
            }
        }
    }
}
bool isOnSchedule(void) {
    int day, hour, minute;

    day        = datetime.dayofWeek();
    hour    = datetime.hour();
    minute    = datetime.minute();

    for(int i = 0; i < numSchedule; i++) {
        if(day == scheduleArray[i][DAY]) {
            int curTime, startTime, endTime;

            curTime        = hour * 60 + minute;
            startTime    = scheduleArray[i][START_HOUR] * 60 + scheduleArray[i][START_MINUTE];
            endTime        = scheduleArray[i][END_HOUR] * 60 + scheduleArray[i][END_MINUTE];

            if(curTime >= startTime && curTime < endTime)
                return true;
        }
    }

    return false;
}

void EepromCSV::clear(void) {
    EEPROM.write(0, EOF);
}
int EepromCSV::line(void) {
    int lineCount   = 0;
    int address     = 0;
    char value;

    while(1) {
        value = EEPROM.read(address++);

        if(value == '\n')
            lineCount++;
        else if(value == EOF)
            return lineCount;
    }
}
String EepromCSV::readLine(int line) {
    String retStr   = "";
    char value;
    int lineIndx    = 0;
    int address     = 0;

    while(lineIndx < line) {
        value = EEPROM.read(address++);
        if(value == '\n')
            lineIndx++;
        else if (value == EOF) {
            return "";
        }
    }

    while(1) {
        value = EEPROM.read(address++);

        if(value == '\n' || value == EOF)
            return retStr;
        else
            retStr += value;
    }
}
void EepromCSV::writeLine(String lineStr) { /* add new line at the end of CSV file*/
    int address, length;

    address    = 0;
    length    = lineStr.length();

    while((char)EEPROM.read(address++) != EOF)
        ;
    address--;

    for(int i = 0; i < length; i++)
        EEPROM.write(address++, lineStr.charAt(i));

    EEPROM.write(address++, '\n');
    EEPROM.write(address, EOF);
}
<remote_config.php>
PHP Code:
<!DOCTYPE html>
<html>
<head>
<title>Arduino - Configure via Web</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7, maximum-scale=0.7">
<meta charset="utf-8">
<style>
body { text-align: center; font-size: 100%; font-family: Roboto;}
#header { background-color: #00979d; color: white; padding: 5px; margin-bottom: 15px}
h2 { font-weight: bold; font-size: 120%; }
table th { font-weight: bold; font-size: 120%; border-bottom: 8px solid #00979d; }
table td { font-weight: bold; font-size: 120%; height: 40px; }
select, input { font-size: 100%; font-family: Roboto;}
#add { border-top: 10px solid white; font-size:130%; background-color: #00979d; color:white;}
button { font-weight: bold; font-size: 100%; }
</style>
<script>
var CMD_ARDUINO_CLR    = 0;
var CMD_ARDUINO_GET    = 1;
var CMD_ARDUINO_ADD    = 2;
var CMD_WEB_CLR        = 5;
var CMD_WEB_UPD        = 6;
var dayLookupTable = ["", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
var schedule_list = new Array();
var ws = null;
var buffer = "";

function connect_onclick() {
    if(ws == null) {
        var ws_host_addr = "<?echo _SERVER("HTTP_HOST")?>";
        if((navigator.platform.indexOf("Win") != -1) && (ws_host_addr.charAt(0) == "[")) {
            // network resource identifier to UNC path name conversion
            ws_host_addr = ws_host_addr.replace(/[\[\]]/g, '');
            ws_host_addr = ws_host_addr.replace(/:/g, "-");
            ws_host_addr += ".ipv6-literal.net";
        }

        ws = new WebSocket("ws://" + ws_host_addr + "/arduino_config", "text.phpoc");
        document.getElementById("ws_state").innerHTML = "CONNECTING";
        ws.onopen = ws_onopen;
        ws.onclose = ws_onclose;
        ws.onmessage = ws_onmessage;
    }
    else
        ws.close();
}
function ws_onopen() {
    document.getElementById("ws_state").innerHTML = "<font color='blue'>CONNECTED</font>";
    document.getElementById("bt_connect").innerHTML = "Disconnect";

    setTimeout(function() {
        ws.send(CMD_ARDUINO_GET + ",0,00:00,00:00\r\n");
    }, 100);
}
function ws_onclose() {
    document.getElementById("ws_state").innerHTML = "<font color='gray'>CLOSED</font>";
    document.getElementById("bt_connect").innerHTML = "Connect";
    ws.onopen = null;
    ws.onclose = null;
    ws.onmessage = null;
    ws = null;
}
function ws_onmessage(e_msg) {
    e_msg = e_msg || window.event; // MessageEvent

    buffer += e_msg.data;
    buffer = buffer.replace(/\r\n/g, "\n");
    buffer = buffer.replace(/\r/g, "\n");

    while(buffer.indexOf("\n") == 0)
        buffer = buffer.substr(1);

    while(buffer.indexOf("\n") >= 0) { // because multiple update data may come at the same time
        var data = buffer.substr(0, buffer.indexOf("\n"));
        buffer = buffer.substr(buffer.indexOf("\n") + 1);

        var arr = data.split(",");
        var cmd = parseInt(arr[0]);

        if(cmd == CMD_WEB_CLR) {
            schedule_list = new Array();

            var scheduleTable = document.getElementById("scheduleTable");
            while( scheduleTable.rows.length > 2)
                scheduleTable.deleteRow(1);
        }
        else if(cmd == CMD_WEB_UPD) {
            var day            = arr[1];
            var startTime    = arr[2];
            var endTime        = arr[3];

            schedule_list.push([day, startTime, endTime]);

            var scheduleTable = document.getElementById("scheduleTable");
            var numRow = scheduleTable.rows.length;
            var newRow = scheduleTable.insertRow(numRow - 1);
            newRow.insertCell().innerHTML = dayLookupTable[parseInt(day)];
            newRow.insertCell().innerHTML = startTime;
            newRow.insertCell().innerHTML = endTime;
            newRow.insertCell().innerHTML = '<span style="font-size:120%; color:red;" onclick="deleteSchedule(this)">&#x2718;</span>';
            newRow.style.color = "#1E90FF";
        }
    }
}
function deleteSchedule(event) {
    var rowIndex = event.parentElement.parentElement.rowIndex;
    document.getElementById("scheduleTable").deleteRow(rowIndex);

    schedule_list.splice(rowIndex - 1, 1);
    sendUpdateToArduino();
}
function addSchedule(event) {
    var scheduleTable = document.getElementById("scheduleTable");
    var rowIndex = event.parentElement.parentElement.rowIndex;
    var row  = scheduleTable.rows[rowIndex];
    var days = row.cells[0].childNodes[0];
    var day = days.options[days.selectedIndex].value;
    var startTime = row.cells[1].childNodes[0].value;
    var endTime = row.cells[2].childNodes[0].value;

    row.cells[0].innerHTML = dayLookupTable[parseInt(day)];
    row.cells[1].innerHTML = startTime;
    row.cells[2].innerHTML = endTime;
    row.cells[3].innerHTML = '<span style="font-size:120%; color:red;" onclick="deleteSchedule(this)">&#x2718;</span>';
    row.style.color = "#1E90FF";

    schedule_list.push([day, startTime, endTime]);
    sendUpdateToArduino();
}
function newSchedule() {
    var scheduleTable = document.getElementById("scheduleTable");
    var numRow = scheduleTable.rows.length;
    var newRow = scheduleTable.insertRow(numRow - 1);
    var cell0 = newRow.insertCell();
    var cell1 = newRow.insertCell();
    var cell2 = newRow.insertCell();
    var cell3 = newRow.insertCell();

    var select = '<select>';
    select += '<option value="1">Monday</option>';
    select += '<option value="2">Tuesday</option>';
    select += '<option value="3">Wednesday</option>';
    select += '<option value="4">Thursday</option>';
    select += '<option value="5">Friday</option>';
    select += '<option value="6">Saturday</option>';
    select += '<option value="7">Sunday</option>';
    select += '</select>';
    cell0.innerHTML = select;
    cell1.innerHTML = '<input type="time" name="usr_time" value="09:00">';
    cell2.innerHTML = '<input type="time" name="usr_time" value="19:00">';
    cell3.innerHTML = '<span style="font-size:120%; color:red;" onclick="addSchedule(this)">&#x2714;</span>';
}
function sendUpdateToArduino() {
    if(ws != null && ws.readyState == 1) {
        ws.send(CMD_ARDUINO_CLR + ",0,00:00,00:00\r\n");

        for(var i = 0; i < schedule_list.length; i++) {
            var schedule = schedule_list[i].join();
            ws.send(CMD_ARDUINO_ADD + "," + schedule + "\r\n");
        }
    }
}
</script>
</head>
<body>
<div id="header">
<span style="font-size:150%; font-weight: bold;">Arduino - Configure via Web</span><br>
<span style="font-size:130%; font-weight: normal;">Scheduler</span>
</div>
<table id="scheduleTable"  style="width:100%; table-layout:fixed;">
    <tr>
        <th>DAY</th>
        <th>FROM</th>
        <th>TO</th>
        <th>ADD<span style="font-size:120%; color:red;">&#x2714;</span> / DELETE<span style="font-size:120%; color:red;">&#x2718;</span></th>
    </tr>
    <tr>
        <td id="add" colspan="4" onclick="newSchedule(this)">NEW</td>
    </tr>
</table>
<br>
<h2>
<button id="bt_connect" type="button" onclick="connect_onclick();">Connect</button>
WebSocket : <span id="ws_state">null</span>
</h2>
</body>
</html>




What We Need to Do
  • Set Wifi information for PHPoC shield or PHPoC WiFi Shield (SSID and password)
  • Upload remote_config.php to PHPoC shield
  • Write Arduino code

Setting Wifi Information for PHPoC Shield

See this instruction.

Upload new Web UI to PHPoC Shield
  • Download PHPoC source code remote_config.php (on code section).

Write Arduino Code
  • See source code in code section.
  • Compile and upload to Arduino via Arduino IDE
Try it
  • Click serial button on Arduino IDE to see the IP address.
  • Open web browser, type http://replace_ip_address/remote_config.php
  • Click connect button and test it.