This week I tried to build the connections from my Arduino to server and show the data from Arduino in html page. Sadly I didn’t make the second part talk with each other. I'll document the process how I try to build these connections, my problems and what I've learnt from that.

//arduino side code modified from Don's sketch for divice to database
#include <WiFiNINA.h>
#include <ArduinoHttpClient.h>
#include "config.h"

#include <DHT.h>

#define DHTPIN 3          // Digital pin connected to the DHT sensor
#define DHTTYPE DHT11   // DHT 11
//#define DHTTYPE DHT22     // DHT 22  (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);

WiFiClient wifi;
HttpClient client = HttpClient(wifi, SERVER_ADDRESS, SERVER_PORT);

int wifi_status = WL_IDLE_STATUS;

void setup() {
  Serial.begin(9600);
  
  while (!Serial);
  dht.begin(); 
  connectWiFi();
}

void loop() {

  // read the sensor values
  float temperature = dht.readTemperature(true);
  float humidity    = dht.readHumidity();

  // print the values for debugging
  Serial.print(temperature);
  Serial.print("°F ");
  Serial.print(humidity);
  Serial.println("% RH");

  Serial.println("Sending data to server via HTTP POST");
  String contentType = "application/x-www-form-urlencoded";
  String postData = "temperature=" + String(temperature);
  postData += "&humidity=" + String(humidity);
  postData += "&device=" + String(DEVICE_ID);

  client.post("/", contentType, postData);

  // read the status code and body of the response
  int statusCode = client.responseStatusCode();
  String response = client.responseBody();

  Serial.print("Status code: ");
  Serial.println(statusCode);
  Serial.print("Response: ");
  Serial.println(response);

  Serial.println("Waiting for ten seconds\\n");
  delay(10000);
}

void connectWiFi() {

  Serial.print("WiFi firmware version ");
  Serial.println(WiFi.firmwareVersion());
  
  // attempt to connect to WiFi network
  while (wifi_status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(WIFI_SSID);
    wifi_status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

    // wait 3 seconds for connection
    delay(3000);
  }
  Serial.println("Connected to WiFi");
  printWiFiStatus();

}

void printWiFiStatus() {
  // print your WiFi IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);
}

//config.h
const char WIFI_SSID[] = "myrrh";
const char WIFI_PASSWORD[] = "1234567890";

const char SERVER_ADDRESS[] = "192.168.8.102";
const int  SERVER_PORT      = 3000;

const char DEVICE_ID[]      = "device_01";

From the sketch above, Arduino is sending data via WiFi to my server, using HTTP. "client.post("/", contentType, postData);" sending the data in string.

One thing to note here is to understand the differences between remote address, local wifi address and server address. The remote address is the IP address of the device; local wifi address is the local WiFi address used to send data, synchronized with the WIFI_SSID[] and WIFI_PASSWORD[]. The server address is the IP address where I host the server.

Even if I'm using local host to host the server, the WiFi IP address might be different from the server address. For example here the local WiFi address is 192.168.8.104; the local host server address is 192.168.8.102. And here comes the first question, how can I check my server address while running it in terminal?

The server side make response to post requests with the data the device just sent in json format, adding the remoteAddress and timestamp. Also you can try to send all the data from the messages array or send the database file. Why I made this response here is to make the html page as client more easier. I'm still fuzzy on the concept request.headers['x-forwarded-for'] and "bodyParser.urlencoded({ extended: true })".

Bodyparse: body.query里的string转化成json放在了body里

const express = require('express');
const app = express();
const bodyParser = require("body-parser");
var messages=[];

app.use(bodyParser.urlencoded({ extended: true }));
//parse the body to JSON
app.use(bodyParser.json());
// app.use(bodyParser.toString());

app.post("/", function (request, response) {
  // posted data is in the body
  let data = request.body; 
  // add a timestamp
  let dt = new Date();
  data.timestamp = dt.getTime();
  // add the remote address
  let remoteAddress = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
  data.remoteAddress = remoteAddress;
  messages.push(data);
  console.log(data);
  response.send(data);
});

// cannot be replaced with the code below if you want to call listener.addressed 
const listener = app.listen(process.env.PORT || '3000', function() {
  console.log('Server is listening on port ' + listener.address().port);
});

// app.listen(process.env.PORT || '3000', function() {
//   console.log('Server is listening on port ' + listener.address().port);
// });

The database version is below, and there are more FIXME to be solved... One thing is when the server response to get requests with sending files, there might be errors because of cannot reading that. I tried to transfer the format here with fs and JSON.parse() function.

const express = require('express');
const app = express();
var path = require('path')
var messages = [];

const bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

const fs = require('fs');
//magic variables..
const logFileName = path.join(__dirname + "/data.json");

// GET returns the data file
app.get('/', function(request, response) {
  //FIXME: how to set content type to json
  // const rawdata=fs.readFileSync(logFileName);
  // let dataToSend=JSON.parse(rawdata);
  // response.sendData(dataToSend);
  // console.log("Sent data:"+logFileName);

  //Try without database
  for(let i=0;i<messages.length;i++){
    response.sendData(messages[i]);
    console.log("send data!");
   };
});

// POST write data to the log
app.post("/", function (request, response) {
  // post data
  let data = request.body; 
  // add a timestamp
  let dt = new Date();
  data.timestamp = dt.getTime();
  // add the remote address
  let remoteAddress = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
  data.remoteAddress = remoteAddress;
  //push data
  messages.push(data+ '\\n');

  fs.appendFile(logFileName, JSON.stringify(data) + '\\n', err => {
    if(err) { throw err };
    console.log(data);
    response.send("OK");
  });
});

// GET /clear - clears the log file
app.get('/clear', function(request, response) {
  fs.writeFile(logFileName, '', err => {
    if(err) { throw err };
    response.send('ok\\n');
  });
});

// listen for requests :)
const listener = app.listen(process.env.PORT || '3000', function() {
  //FIXME: why we do that
  fs.closeSync(fs.openSync(logFileName, 'w'));
  console.log('Server is listening on port ' + listener.address().port);
});

The things I'm really struggling with is getting the html page and server talk. I tried three ways. Below is the code and there are several functions for connections:

  1. httpDo(url, 'GET', getData);
  2. async function fetchData();
  3. function fetchJSON()
  4. XMLHttpRequest
  5. Also I've tried web socket, but I think it's not compatible with the arduino http client.
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <!-- why p5.js lib is there is because that I've tried httpDo function and it's defined in p5.lib -->
    <!-- <script src="<https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/p5.min.js>"></script>
    <script src="<https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/addons/p5.dom.min.js>"></script>
    <script src="<https://unpkg.com/ml5@latest/dist/ml5.min.js>" type="text/javascript"></script> -->
    <script type="text/javascript">
        let url;
        let serverIP = "192.168.8.102";
        // let serverIP = "192.168.8.255";
        let port = 3000;

        function setup() {
            console.log('page is loaded');

            let clearButton = document.getElementById('clear');
            clearButton.addEventListener('click', clearOff);

            let hSlider = document.getElementById('humidity');
            hSlider.addEventListener('change', changeSlider);

            // fetchJSON();
            // fetchData();
            // url = "http://" + serverIP + ":" + port;
            // console.log(url);
            // httpDo(url, 'GET', getData);

            // connect();
            const xhr = new XMLHttpRequest();
            // Set the options of the request including the METHOD, URL, and if the operation should be Asynchronous or not.
            xhr.open("GET", url, true);
            // Here we handle the results of the request.
            // Each XHR request goes through 4 stages,
            // Stage 4 of the .readyState is DONE
            xhr.onload = function (e) {
                if (xhr.readyState === 4) {
                    // if our request status is 200 - it is a great success!
                    if (xhr.status === 200) {
                        console.log(xhr.responseText);
                    } else {
                        console.error(xhr.statusText);
                    }
                }
            };
            // Here we do our error handling
            xhr.onerror = function (e) {
                console.error(xhr.statusText);
            };
            // and FINAlly can SEND the request
            xhr.send(null)

            setInterval(setTime, 1000);
        }
        window.addEventListener('DOMContentLoaded', setup);

        function clearOff(event) {
            let thisSpan = document.getElementById('#resultDiv');
            thisSpan.innerHTML = "cleared";
        }

        function changeSlider(event) {
     
            let results = document.getElementById('resultDiv');
            let thisSpan = document.getElementById('humidity');
            thisSpan.innerHTML = results.humidity.value;
        }

        function setTime() {
            // get current date and time as a string:
            let now = new Date().toTimeString();
            // get the time span element and put the time in it:
            let timeSpan = document.getElementById('time');
            timeSpan.innerHTML = now;
        }

        function connect() {
            url = "http://" + serverIP + ":" + port;
            console.log(url);
            httpDo(url, 'GET', getData);
        }

        function getData(result) {
            select('#resultDiv').html(result);
        }

        async function fetchData() {
            url = "http://" + serverIP + ":" + port;
            const options = {
                method: "GET",
                mode: 'no-cors'
            }
            
            // let users = await fetch(url, options);
            // users = await users.json();

            // console.log(users);

            fetch(url, options)
                .then(response => response.text())  // convert response to text
                .then(data => getResponse(data))    // get the body of the response
                .catch(error => getResponse(error));// if there is an error
        }

        function fetchJSON() {
            url = "http://" + serverIP + ":" + port;
            let params = {
                mode: 'no-cors'
            }
            fetch(url, params)
                .then(response => response.json())  // convert response to JSON
                .then(data => getResponse(JSON.stringify(data)))   // get the body of the response
                .catch(error => getResponse(error));// if there is an error
        }

        function fetchText() {
            url = "http://" + serverIP + ":" + port;
            let params = {
                mode: 'no-cors',
                // headers: {    
                //     'accept': 'application/text'
                // }
            }
            // make the HTTP/S call:
            fetch(url, params)
                .then(response => response.text())  // convert response to text
                .then(data => getResponse(data),console.log(data))    // get the body of the response
                .catch(error => getResponse(error));// if there is an error
        }

        function getResponse(data) {
            document.getElementById('resultDiv').innerHTML = data;
        }

    </script>
    <title>T</title>
</head>

<body>
    <div id="resultDiv"></div>
    <label for="time">Time: </label><span id="time"></span><br>
    <br>
    <label for="humidity">Humidity: </label><input type="range" id="humidity" min="0" max="10" value="0"><br>
    <button id="clear"></button>
    <span id="humidity"></span><br>
</body>

</html>